Index: head/contrib/bmake/ChangeLog =================================================================== --- head/contrib/bmake/ChangeLog (revision 367862) +++ head/contrib/bmake/ChangeLog (revision 367863) @@ -1,2999 +1,3074 @@ +2020-11-17 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20201117 + Merge with NetBSD make, pick up + o fix some unit-tests when dash is .SHELL + o rename Targ_NewGN to GNode_New + o make some GNode functions const + o main.c: call Targ_Init before Var_Init + cleanup PrintOnError, getTmpdir and ParseBoolean + o var.c: fix error message of failed :!cmd! modifier + +2020-11-14 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20201114 + Merge with NetBSD make, pick up + o replace a few HashTable_CreateEntry with HashTable_Set + o clean up cached_stats + o rename DEFAULT to defaultNode + o remove redundant struct make_stat + o cond.c: in lint mode, check for ".else " + use bitset for IfState + replace large switch with if-else in Cond_EvalLine + o job.c: clean up JobExec, JobStart, JobDoOutput + use stderr for error message about failed touch + clean up Job_Touch + replace macro DBPRINTF with JobPrintln + rename JobState to JobStatus + main.c: switch cache for realpath from GNode to HashTable + clean up Fatal + clean up InitDefSysIncPath + use progname instead of hard-coded 'make' in warning + rename Main_SetVarObjdir to SetVarObjdir + make.1: document the -S option + make.c: fix debug output for GNode details + use symbolic names in debug output of GNodes + +2020-11-12 Simon J Gerraty + + * configure.in: fix --with-force-machine-arch + + * VERSION (_MAKE_VERSION): 20201112 + Merge with NetBSD make, pick up + o allow env var MAKE_OBJDIR_CHECK_WRITABLE=no to skip writable + checks in InitObjdir. Explicit .OBJDIR target always allows + read-only directory. + o cond.c: clean up Cond_EvalLine + +2020-11-11 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20201111 + Merge with NetBSD make, pick up + o more unit-tests + o style cleanup + remove redundant parentheses from sizeof operator + replace character literal 0 with '\0'. + replace pointer literal 0 with NULL. + remove redundant parentheses. + replace (expr & mask) == 0 with !(expr & mask). + use strict typing in conditions of the form !var + o rename Make_OODate to GNode_IsOODate + o rename Make_TimeStamp to GNode_UpdateYoungestChild + o rename Var_Set_with_flags to Var_SetWithFlags + o rename dieQuietly to shouldDieQuietly + o buf.c: make API of Buf_Init simpler + o compat.c: clean up Compat_Make, Compat_RunCommand, + CompatDeleteTarget and CompatInterrupt + o cond.c: in lint mode, only allow '&&' and '||', not '&' and '|' + clean up CondParser_Comparison + o main.c: rename getBoolean and s2Boolean + rename MAKEFILE_PREFERENCE for consistency + o parse.c: replace strstr in ParseMaybeSubMake with optimized code + o var.c: rename VARE_ASSIGN to VARE_KEEP_DOLLAR + replace emptyString with allocated empty string + error out on unclosed expressions after the colon + 2020-11-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201101 Merge with NetBSD make, pick up o negate NoExecute to GNode_ShouldExecute o job.c: rename JobMatchShell to FindShellByName extract EscapeShellDblQuot from JobPrintCommand extract ParseRunOptions from JobPrintCommand o var.c: extract ApplyModifiersIndirect from ApplyModifiers treat malformed :range, :ts and :[...] as errors add tests for the variable modifiers :[words] and :range 2020-10-31 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201031 Merge with NetBSD make, pick up o format #include directives consistently o do not look up local variables like .TARGET anywhere else o main.c: Main_SetObjdir is first called for curdir which may be readonly reduce the scope where recursive expressions are detected remove redundant :tl from getBoolean clean up mkTempFile o meta.c: simplify memory allocation in meta_create and meta_oodate o parse.c: extract loadedfile_mmap from loadfile o trace.c: document possible undefined behavior with .CURDIR o var.c: make parsing of the :gmtime and :localtime modifiers stricter rename ismeta to is_shell_metachar remove debug logging for the :Q variable modifier rename VarIsDynamic to VarnameIsDynamic use consistent parameter order in varname parsing functions extract ParseVarnameLong from Var_Parse extract ParseVarnameShort from Var_Parse fix type of ParseModifierPart parameter delim extract IsEscapedModifierPart from ParseModifierPart clean up ModifyWords add test for combining the :@ and :? variable modifiers 2020-10-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201030 Merge with NetBSD make, pick up o change char * to void * in Var_Value o make iterating over HashTable simpler o rename VAR_CMD to VAR_CMDLINE o cond.c: clean up is_separator fix parse error in string literal in conditional o main.c: do not use objdir that is not writable in lint mode, exit with error status on errors o parse.c: clean up StrContainsWord fix out-of-bounds pointer in ParseTrackInput o var.c: rename Str_SYSVMatch and its parameters remove unsatisfiable conditions in Var_Set_with_flags document where the variable name is expanded fix documentation for VARP_SUB_ONE rename VAR_EXPORTED_YES to VAR_EXPORTED_SOME document VAR_READONLY prevent appending to read-only variables extract MayExport from Var_Export1 remove redundant evaluations in VarFind replace VarFindFlags with a simple Boolean rename FIND_CMD to FIND_CMDLINE, to match VAR_CMDLINE 2020-10-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201028 Merge with NetBSD make, pick up o rename defIncPath to defSysIncPath o initialize all CmdOpts fields o lst.c: inline Vector_Get o main.c: refactor main extract InitMaxJobs,InitObjdir,InitVarMake,InitRandom, ReadMakefiles,CleanUp,InitVpath,ReadBuiltinRules, InitDefIncPath,CmdOpts_Init,UnlimitFiles o parse.c: merge curFile into includes rename predecessor to order_pred sort ParseSpecial alphabetically remove unused, undocumented .NOEXPORT rename ParseSpecial enum values consistently rename some fields of struct IFile 2020-10-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201026 Merge with NetBSD make, pick up o group the command line options and arguments into a struct o rename GNode.cmgn to youngestChild o rename hash functions to identify the type name o negate OP_NOP and rename it to GNode_IsTarget o add GNode_Path to access the path of a GNode o remove macros MIN and MAX o remove unused Lst_Find and Lst_FindFrom o arch.c: and make Arch_FindLib simpler clean up code layout make Arch_ParseArchive simpler o cond.c: inline CondFindStrMatch into FuncMake o dir.c: replace Dir_CopyDir with Dir_CopyDirSearchPath omit trailing space in debug output for expanding file patterns refactor DirMatchFiles document that the SearchPath of Dir_FindFile may be NULL remove UNCONST from Dir_Expand inline DirFindName o for.c: clean up code for handling .for loops o hash.c: print hash in debug log with fixed width clean up hash table functions reduce amount of string hashing o job.c: refactor JobDeleteTarget use proper enum constants for aborting convert result of JobStart from macros to enum convert abort reason macros to enum rework Job_CheckCommands to reduce indentation rename Shell fields add field names in declaration of DEFSHELL_CUSTOM convert JobState and JobFlags to enum types move handling of the "..." command to JobPrintCommands o lst.c: clean up refactor LstNodeNew remove Lst_Open, Lst_Next, Lst_Close remove code for circular lists from Lst_Next o main.c: do not attempt to read .MAKE.DEPENFILE if set to /dev/null or anything starting with "no" convert macros for debug flags into enum o make.c: inline Lst_Copy in Make_ExpandUse o meta.c: inline Lst_Find in meta_oodate make Lst_RemoveIf simpler in meta_oodate o parse.c: convert error level for Parse_Error to an enum o suff.c: properly terminate debug output with newline add more details to DEBUG_SRC log replace Dir_CopyDir with Dir_CopyDirSearchPath don't modify GNode name while rebuilding the suffix graph o var.c: reduce duplicate code in VarFind 2020-10-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201022 Merge with NetBSD make, pick up o more refactoring and simplification to reduce code size o var.c: extract CanonicalVarname from VarFind o make.c: extract UpdateImplicitParentsVars from Make_Update o main.c: extract PrintVar from doPrintVars extract HandlePWD from main o lst.c: inline simple Lst getters remove unused Lst_ForEach o job.c: move struct Shell from job.h to job.c o more unit tests 2020-10-19 Simon J Gerraty * configure.in: remove inappropriate use of AC_INCLUDES_DEFAULT 2020-10-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201018 Merge with NetBSD make, pick up o remove USE_IOVEC o rename some Hash_* apis to Hash* o replace execError with execDie o rename Lst_Init to Lst_New o add tags to enum types o rename Stack to Vector o parse.c: more refactoring o unit-tests: make some tests use line buffered stdout o unit-tests/Makefile: in meta mode do not make all tests depend on Makefile, it isn't necessary. 2020-10-10 Simon J Gerraty * main.c: check for CTL_HW being defined. * unit-tests/Makefile: ensure export tests output are POSIX compliant disable opt-debug-jobs test until it works on ubuntu * VERSION (_MAKE_VERSION): 20201010 Merge with NetBSD make, pick up o dir.c: remove pathname limit for Dir_FindHereOrAbove o hash.c: replace strcpy with memcpy in Hash_CreateEntry o main.c: extract init_machine and init_machine_arch from main allow to disable debug logging options o parse.c: enable format string truncation warnings extract parsing of sources from ParseDoDependency split ParseDoSrc into smaller functions hide implementation details from Parse_DoVar clean up parsing of variable assignments split Parse_DoVar into manageable pieces don't modify the given line during Parse_DoVar fix out-of-bounds memory access in Parse_DoVar fix parsing of the :sh assignment modifier o var.c: rework memory allocation for the name of variables extract ApplyModifier_Literal into separate function in lint mode, reject modifiers without delimiter do not export variable names starting with '-' o fix double-free bug in -DCLEANUP mode o more cleanup to enable higher warnings level o more unit tests 2020-10-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201002 Merge with NetBSD make, pick up o dir.c: use hash table for looking up open directories by name o main.c: clean up option handling o parse.c: add missing const for Parse_AddIncludeDir o var.c: ApplyModifier_To, update pp in each branch o remove redundant function prototypes o more unit tests 2020-10-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201001 Merge with NetBSD make, pick up o compat.c: comment about "..." 2020-09-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200930 Merge with NetBSD make, pick up o job.c: split Job.jobPipe into 2 separate fields replace Lst_Open with direct iteration o lst.c: remove redundant assertions o targ.c: replace Lst_Open with direct iteration o var.c: fix bug in evaluation of indirect variable modifiers extract ApplyModifier_Quote into separate function o make debug logging simpler 2020-09-27 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200927 Merge with NetBSD make, pick up o parse.c: ensure parse errors result in 'stopped in' message. o compat.c: make parameter of Compat_RunCommand const o main.c: extract InitVarTarget from main o parse.c: rename ParseFinishLine to FinishDependencyGroup refactor ParseDoDependency o var.c: Var_Subst no longer returns string result rename Var_ParsePP back to Var_Parse in lint mode, improve error handling for undefined variables extract ParseVarname from Var_Parse o rename Lst_ForEach to Lst_ForEachUntil o inline Lst_ForEachUntil in several cases o clean up API for finding and creating GNodes o fix assertion failure in -j mode with .END node o inline and remove LstNode_Prev and LstNode_Next o use fine-grained type names for lists and their nodes o more unit tests 2020-09-11 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200911 Merge with NetBSD make, pick up o cond.c: split EvalComparison into smaller functions reorder parameters of condition parsing functions reduce code size in CondParser_Eval rename CondGetString to CondParser_String add CondLexer_SkipWhitespace group the condition parsing state into a struct in CondGetString, replace repeated Buf_Add with Buf_AddStr o migrate Var_Parse to Var_ParsePP o add wrappers around ctype.h functions o lst.c: use a stack instead of a list for the nested include path o more unit tests 2020-09-04 Simon J Gerraty * make-bootstrap.sh.in: adjust object list 2020-09-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200902 Merge with NetBSD make, pick up o use make_stat to ensure no confusion over valid fields returned by cached_stat o var.c: make VarQuote const-correct o add unit tests for .for 2020-09-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200901 Merge with NetBSD make, pick up o rename Hash_Table fields o make data types in Dir_HasWildcards more precise 2020-08-31 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200831 Merge with NetBSD make, pick up o suff.c: fix unbalanced Lst_Open/Lst_Close in SuffFindCmds o lst.c: Lst_Open renable assert that list isn't open o unit test for .TARGET dependent flags o var.c: fix aliasing bug in VarUniq o more unit tests for :u 2020-08-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200830 Merge with NetBSD make, pick up o allow for strict type checking for Boolean o Var_Parse never returns NULL o Var_Subst never returns NULL o Lst_Find now takes boolean match function o rename Lst_Memeber to Lst_FindDatum o rename LstNode functions to match their type o rename GNode.iParents to implicitParents o fix assertion failure for .SUFFIXES in archives o compat.c: clean up documentation for CompatInterrupt and Compat_Run remove unreachable code from CompatRunCommand o main.c: simplify getBoolean o stc.c: replace brk_string with simpler Str_Words o suff.c: add debug macros 2020-08-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200828 Merge with NetBSD make, pick up o lst.c: inline LstIsValid and LstNodeIsValid o remove trailing S from Lst function names after migration complete o more comment cleanup/clarification o suff.c: clean up suffix handling o more unit tests 2020-08-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200826 Merge with NetBSD make, pick up o enum.c: distinguish between bitsets containing flags and ordinary enums o var.c: fix error message for ::!= modifier with shell error o fix bugs in -DCLEANUP mode 2020-08-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200824 Merge with NetBSD make, pick up o in debug mode, print GNode details in symbols 2020-08-23 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200823 Merge with NetBSD make, pick up o lst.c: more asserts, make args to Lst_Find match others. o var.c: pass flags to VarAdd o arch.c: use Buffer o str.c: brk_string return size_t for nwords o more unit tests 2020-08-22 Simon J Gerraty * VERSION (_MAKE_VERSION): Merge with NetBSD make, pick up o var.c: support for read-only variables eg .SHELL being the shell used to run scripts. o lst.c: more simplification o more documentation and style cleanup o more unit tests o ensure unit-test/Makefile is run by TEST_MAKE o reduce duplication of header inclusion 2020-08-21 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200821 Merge with NetBSD make, pick up o lst.c: revert invalid assertion - but document it o dir.c: split Dir_Init into two functions 2020-08-20 Simon J Gerraty * lst.c: needs inttypes.h on Linux * VERSION (_MAKE_VERSION): 20200820 Merge with NetBSD make, pick up o make.1: clarify some passages o var.c: more cleanup, clarify comments o make_malloc.c: remove unreachable code o cond.c: make CondGetString easier to debug o simplify list usage o unit-tests: more 2020-08-16 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200816 Merge with NetBSD make, pick up o refactor unit-tests to be more fine grained not all tests moved yet 2020-08-14 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200814 Merge with NetBSD make, pick up o more str_concat variants o more enums for flags o var.c: cleanup for higher warnings level 2020-08-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200810 Merge with NetBSD make, pick up o more unit tests o general comment and style cleanup 2020-08-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200808 Merge with NetBSD make, pick up o enum.[ch]: streamline, enums for use in flags and debug output o cond.c: cleanup o var.c: reduce duplicate code for modifiers debug logging for Var_Parse more detailed debug output o more unit tests 2020-08-06 Simon J Gerraty * unit-tests/Makefile: -r for recursive and include Makefile.inc so I can run tests in meta mode supress extra noise if in meta mode * VERSION (_MAKE_VERSION): 20200806 Merge with NetBSD make, pick up o parse.c: remove VARE_WANTRES for LINT we just want to check parsing (for now). 2020-08-05 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200805 Merge with NetBSD make, pick up o make.1: Rework the description of dependence operators 2020-08-03 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200803 Merge with NetBSD make, pick up o revert some C99 usage, for max portability o unit-tests/lint 2020-08-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200802 Merge with NetBSD make, pick up o more unit tests 2020-08-01 Simon J Gerraty * Remove NetBSD specific plumbing from unit-tests/Makefile * VERSION (_MAKE_VERSION): 20200801 Merge with NetBSD make, pick up o make Var_Value return const o size_t for buf sizes o optimize some buffer operations - avoid strlen 2020-07-31 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200731 Merge with NetBSD make, pick up o var.c: fix undefinded behavior for incomplete :t modifier fixes unit-test/moderrs on Ubuntu o parse.c: When parsing variable assignments other than := if DEBUG(LINT) test substition of value, so we get a file and line number in the resulting error. o dir.c: fix parsing of nested braces in dependency lines add unit-tests 2020-07-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200730 Merge with NetBSD make, pick up o var.c: minor cleanup o unit-tests: more tests to improve code coverage 2020-07-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200728 Merge with NetBSD make, pick up o var.c: more optimizations 2020-07-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200726 Merge with NetBSD make, pick up o collapse lsd.lib into lst.c - reduce code size and allow inlining o lots of function comment updates o var.c: more optimizations o make return of Var_Parse const 2020-07-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200720 Merge with NetBSD make, pick up o DEBUG_HASH report stats at end and tone down the noise o var.c: each flag type gets its own prefix. move SysV string matching to var.c make ampersand in ${VAR:from=to&} an ordinary character cleanup and simplify implementation of modifiers o make.1: move documentation for assignment modifiers 2020-07-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200718 Merge with NetBSD make, pick up o DEBUG_HASH to see how well the hash tables are working 2020-07-11 Simon J Gerraty * bsd.after-import.mk: make sure we update unit-tests/Makefile 2020-07-10 Simon J Gerraty * configure.in: use AC_INCLUDES_DEFAULT rather than AC_HEADER_STDC * VERSION (_MAKE_VERSION): 20200710 Merge with NetBSD make, pick up o filemon/filemon_dev.c: use O_CLOEXEC rather than extra syscall o meta.c: target flagged .META is out-of-date if meta file missing 2020-07-09 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200709 Merge with NetBSD make, pick up o cond.c: fix for compare_expression when doEval=0 o unit-tests/Makefile: rework o filemon/filemon_dev.c: ensure filemon fd is closed on exec. 2020-07-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200704 Merge with NetBSD make, pick up (most of this by rillig@) o lots of style and white-space cleanup o lots more unit tests for variable modifiers o simplified description of some functions o str.c: refactor Str_Match o var.c: debugging output for :@ constify VarModify parameter fix :hash modifier on 16-bit platforms remove unnecessary forward declarations refactor ApplyModifier_SysV to have less indentation simplify code for :E and :R clean up code for :H and :T refactor ApplyModifiers * var.c: we need stdint.h on some platforms to get uint32_t * unit-test/Makefile: we need to supress the specific error for RE substitution error in modmisc, since it varies accross different OS. 2020-07-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200702 Merge with NetBSD make, pick up o var.c: more improvements to avoiding unnecessary evaluation use enums for flags o remove flags arg to Var_Set which outside of var.c is always 0 2020-07-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200701 Merge with NetBSD make, pick up o var.c: with change to cond.c; ensure that nested variables within a variable name are expanded. o unit-tests/varmisc.mk: test for nested varname 2020-06-29 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200629 Merge with NetBSD make, pick up o cond.c: do not eval unnecessary terms of conditionals. 2020-06-25 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200625 Merge with NetBSD make, pick up o meta.c: report error if lseek in filemon_read fails 2020-06-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200622 Merge with NetBSD make, pick up o dieQuietly: ignore OP_SUBMAKE as too aggressive 2020-06-19 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200619 Merge with NetBSD make, pick up o str.c: performance improvement for Str_Match for multiple '*' o dieQuietly: supress the failure output from make when failing node is a sub-make or a sibling failed. This cuts down greatly on unhelpful noise at the end of build log. Disabled by -dj or .MAKE.DIE_QUIETLY=no 2020-06-10 Simon J Gerraty * FILES: add LICENSE to appease some packagers. This is an attempt to fairly represent the license on almost 200 files, which are almost all BSD-3-Clause The few exceptions being more liberal. * VERSION (_MAKE_VERSION): 20200610 Merge with NetBSD make, pick up o unit test for :Or 2020-06-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200606 Merge with NetBSD make, pick up o make.1: cleanup * Makefile: fix depends for main.o which broke MAKE_VERSION 2020-06-05 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200605 Merge with NetBSD make, pick up o dir.c: cached_stats - don't confuse stat and lstat results. o var.c: add :Or for reverse sort. 2020-05-24 Simon J Gerraty * configure.in: add AC_PROG_CC_C99 for mipspro compiler also if --with-filemon= specifies path to filemon.h set use_filemon=dev * dirname.c: remove include of namespace.h 2020-05-17 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200517 Merge with NetBSD make, pick up o modified dollar tests to avoid shell dependencies o new tests for .INCLUDEFROM 2020-05-16 Simon J Gerraty * unit-tests/dollar.mk: tweak '1 dollar literal' test to not depend so much on shell behavior 2020-05-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200510 Merge with NetBSD make, pick up o unit test for dollar handling 2020-05-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200506 Merge with NetBSD make, pick up o str.c: empty string does not match % pattern plus unit-test changes 2020-05-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200504 May the 4th be with you Merge with NetBSD make, pick up o var.c: import handling of old sysV style modifier using '%' o str.c: refactor brk_string o unit-tests: add test case for lazy conditions 2020-04-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200418 * configure.in: use_makefile=no for cygwin et al. case insensitive filesystems just don't work if both makefile and Makefile exist. NOTE: bmake does not support cygwin and likely never will, but if brave souls want to try it - help them out. 2020-04-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200402 Merge with NetBSD make, pick up o meta.c: meta_oodate, CHECK_VALID_META is too aggressive for CMD a blank command is perfectly valid. 2020-03-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200330 Merge with NetBSD make, pick up o make.h: extern debug_file 2020-03-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200318 Merge with NetBSD make, pick up o meta.c: meta_oodate, check for corrupted meta file earlier and more often. 2020-02-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200220 2020-02-19 Simon J Gerraty * boot-strap: unset MAKEFLAGS 2020-02-12 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200212 * meta.c: meta_compat_parent check for USE_FILEMON patch from Soeren Tempel 2020-02-05 Simon J Gerraty * VERSION: 20200205 Merge with NetBSD make, pick up o meta.c: fix compat mode, need to call meta_job_output() o job.c: extra fds for meta mode not needed if using filemon_dev 2020-01-22 Simon J Gerraty * VERSION: 20200122 Merge with NetBSD make, pick up o meta.c: avoid passing NULL to filemon_*() when meta_needed() returns FALSE. 2020-01-21 Simon J Gerraty * VERSION: 20200121 Merge with NetBSD make, pick up o filemon/filemon_{dev,ktrace}.c: allow selection of filemon implementation. filemon_dev.c uses the kernel module while filemon_ktrace.c leverages the fktrace api available in NetBSD. filemon_ktrace.c can hopefully form the basis for adding support for other tracing mechanisms such as strace on Linux. o meta.c: when target is out-of-date per normal make rules record value of .OODATE in meta file. 2019-09-26 Simon J Gerraty * VERSION: 20190926 Merge with NetBSD make, pick up o parse.c: don't pass NULL to realpath(3) some versions cannot handle it. 2019-04-09 Simon J Gerraty * VERSION: 20190409 Merge with NetBSD make, pick up o parse.c: ParseDoDependency: free paths rather than assert 2018-12-22 Simon J Gerraty * VERSION: 20181222 * configure.in: add --without-makefile to avoid generating makefile and make-bootstrap.sh * include Makefile.inc if it exists * Use Makefile and Makefile.config.in in unit-tests so we can use just: make obj && make && make test when bmake is already available. We add --without-makefile to CONFIGURE_ARGS in this case. * tweak bsd.after-import.mk (captures Makefile.config etc after import to FreeBSD for example) to cope with all the above. 2018-12-21 Simon J Gerraty * VERSION: 20181221 Merge with NetBSD make, pick up o parse.c: ParseVErrorInternal use .PARSEDIR and apply if relative, and then use .PARSEFILE for consistent result. 2018-12-20 Simon J Gerraty * VERSION: 20181220 Merge with NetBSD make, pick up o parse.c: ParseVErrorInternal use .CURDIR if .PARSEDIR is relative o var.c: avoid SEGFAULT in .unexport-env when MAKELEVEL is not set 2018-12-16 Simon J Gerraty * VERSION: 20181216 Merge with NetBSD make, pick up o fix for unit-tests/varquote.mk on Debian 2018-09-21 Simon J. Gerraty * VERSION: 20180919 Merge with NetBSD make, pick up o var.c: add :q o dir.c: cleanup caching of stats 2018-09-21 Simon J Gerraty * Makefile.config.in: use += where it makes sense. 2018-05-12 Simon J. Gerraty * VERSION: 20180512 Merge with NetBSD make, pick up o job.c: skip polling job token pipe 2018-04-05 Simon J. Gerraty * VERSION: 20180405 Merge with NetBSD make, pick up o parse.c: be more cautious about detecting depenency line rather than sysV style include. 2018-02-22 Simon J. Gerraty * VERSION: 20180222 Merge with NetBSD make, pick up o parse.c: avoid calling sysconf for every call to loadfile 2018-02-18 Simon J. Gerraty * VERSION: 20180218 Merge with NetBSD make, pick up o var.c: Var_Set handle NULL value anytime. 2018-02-12 Simon J. Gerraty * VERSION: 20180212 Merge with NetBSD make, pick up o parse.c: do not treat .info as warning with -W 2017-12-07 Simon J. Gerraty * VERSION: 20171207 Merge with NetBSD make, pick up o var.c: Var_Append use Var_Set if var not previously set so that VAR_CMD is handled correctly. Add a suitable unit-test. 2017-11-26 Simon J. Gerraty * VERSION (_MAKE_VERSION): 20171126 * aclocal.m4: use AC_LINK_IFELSE for AC_C___ATTRIBUTE__ since AC_TRY_COMPILE puts input inside main() which upsets modern compilers. 2017-11-18 Simon J. Gerraty * VERSION: 20171118 Merge with NetBSD make, pick up o var.c: do not append to variable set on command line add unit-test to catch this. 2017-10-28 Simon J. Gerraty * VERSION: 20171028 Merge with NetBSD make, pick up o main.c: ignore empty MAKEOBJDIR * Makefile.config.in: make @prefix@ @machine*@ and @default_sys_path@ defaults. 2017-10-05 Simon J. Gerraty * VERSION: 20171005 * unit-tests/dotwait.mk: redirect stderr through pipe for more consistent result on some platforms. 2017-08-13 Simon J. Gerraty * machine.sh: entry for AIX 2017-08-12 Simon J. Gerraty * VERSION (_MAKE_VERSION): Move the setting of _MAKE_VERSION to a file that can be included by configure as well as make. This allows configure to set set _MAKE_VERSION in make-bootstrap.sh 2017-08-10 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170810 Merge with NetBSD make, pick up o meta.c: if target is in subdir we only need subdir name in meta_name. 2017-07-20 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170720 Merge with NetBSD make, pick up o compat.c: pass SIGINT etc onto child and wait for it to exit before we self-terminate. 2017-07-11 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170711 forgot to update after merge on 20170708 ;-) o main.c: refactor to reduce size of main function. add -v option to always fully expand values. o meta.c: ensure command output in meta file has ending newline even when filemon not being used. When matching ${.MAKE.META.IGNORE_PATTERNS} do not use pathname via ':L' since any ':' in pathname breaks that. Instead set a '${.p.}' to pathname in the target context and use that. 2017-05-10 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170510 Merge with NetBSD make, pick up o main.c: Main_SetObjdir: ensure buf2 is in scope 2017-05-08 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170505 see mk/ChangeLog 2017-05-05 Simon J. Gerraty * parse.c: not everyone has stdint.h 2017-05-01 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170501 see mk/ChangeLog 2017-04-21 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170421 Merge with NetBSD make, pick up o str.c: Str_Match: fix closure tests for [^] and add unit-test. 2017-04-20 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170420 Merge with NetBSD make, pick up o main.c: only use -C arg "as is" if it contains no relative component. 2017-04-18 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170418 Merge with NetBSD make, pick up o main.c: fix Main_SetObjdir() for relative paths (eg obj). 2017-04-17 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170417 Merge with NetBSD make, pick up o fixes a number of coverity complaints - check return value of fseek, fcntl - plug memory leak in Dir_FindFile, Var_LoopExpand, JobPrintCommand, ParseTraditionalInclude - use bmake_malloc() where NULL is not tollerated - use MAKE_ATTR_UNUSED rather that kludges like return(unused ? 0 : 0) - use purge_cached_realpaths() rather than abuse cached_realpath() 2017-04-13 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170413 Merge with NetBSD make, pick up o main.c: when setting .OBJDIR ignore '$' in paths. * job.c: use MALLOC_OPTIONS to set malloc_options. 2017-04-11 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170411 Merge with NetBSD make, pick up o str.c: Str_Match: allow [^a-z] to behave as expected. 2017-03-26 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170326 Merge with NetBSD make, pick up o main.c: purge relative paths from realpath cache when .OBJDIR is changed. 2017-03-11 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170311 Merge with NetBSD make, pick up o main.c: only use -C arg "as is" if it starts with '/'. 2017-03-01 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170301 Merge with NetBSD make, pick up o main.c: use -C arg "as is" rather than getcwd() if they identify the same directory. o parse.c: ensure loadfile buffer is \n terminated in non-mmap case 2017-02-01 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170201 Merge with NetBSD make, pick up o var.c: allow :_=var and avoid use of special context. 2017-01-30 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170130 Merge with NetBSD make, pick up o var.c: add :range and :_ o main.c: partially initialize Dir_* before MainParseArgs() can be called. If -V, skip Main_ExportMAKEFLAGS() 2017-01-14 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170114 Merge with NetBSD make, pick up o var.c: allow specifying the utc value used by :{gm,local}time 2016-12-12 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20161212 Merge with NetBSD make, pick up o main.c: look for obj.${MACHINE}-${MACHINE_ARCH} too. 2016-12-09 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20161209 Merge with NetBSD make, pick up o main.c: cleanup setting of .OBJDIR o parse.c: avoid coredump from (var)=val 2016-11-26 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20161126 Merge with NetBSD make, pick up o make.c: Make_OODate: report src node name if path not set 2016-09-26 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160926 Merge with NetBSD make, pick up o support for .DELETE_ON_ERROR: (remove targets that fail) 2016-09-26 Simon J. Gerraty * Makefile MAN: tweak .Dt to match ${PROG} 2016-08-18 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160818 its a neater number; pick up whitespace fixes to man page. 2016-08-17 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160817 Merge with NetBSD make, pick up o meta.c: move handling of .MAKE.META.IGNORE_* to meta_ignore() so we can call it before adding entries to missingFiles. Thus we do not track files we have been told to ignore. 2016-08-15 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160815 Merge with NetBSD make, pick up o meta_oodate: apply .MAKE.META.IGNORE_FILTER (if defined) to pathnames, and skip if the expansion is empty. Useful for dirdeps.mk when checking DIRDEPS_CACHE. 2016-08-12 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160812 Merge with NetBSD make, pick up o meta.c: remove all missingFiles entries that match a deleted dir. o main.c: set .ERROR_CMD if possible. 2016-06-06 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160606 Merge with NetBSD make, pick up o dir.c: extend mtimes cache to others via cached_stat() 2016-06-04 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160604 Merge with NetBSD make, pick up o meta.c: missing filemon data is only relevant if we read a meta file. Also do not return oodate for a missing metafile if gn->path points to .CURDIR 2016-06-02 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160602 Merge with NetBSD make, pick up o cached_realpath(): avoid hitting filesystem more than necessary. o meta.c: refactor need_meta decision, add knobs for missing meta file and filemon data wrt out-of-datedness. 2016-05-28 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160528 * boot-strap, make-bootstrap.sh.in: Makefile now uses _MAKE_VERSION 2016-05-12 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160512 Merge with NetBSD make, pick up o meta.c: ignore paths that match .MAKE.META.IGNORE_PATTERNS this is useful for gcov builds. o propagate errors from filemon(4). 2016-05-09 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160509 Merge with NetBSD make, pick up o remove use of non-standard types u_int etc. o meta.c: apply realpath() before matching against metaIgnorePaths 2016-04-04 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160404 Merge with NetBSD make, pick up o allow makefile to set .MAKE.JOBS * Makefile (PROG_NAME): use ${_MAKE_VERSION} 2016-03-15 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160315 Merge with NetBSD make, pick up o fix handling of archive members 2016-03-13 Simon J. Gerraty * Makefile (_MAKE_VERSION): rename variable to avoid interference with checks for ${MAKE_VERSION} 2016-03-10 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160310 Merge with NetBSD make, pick up o meta.c: treat missing Read file same as Write, incase we Delete it. 2016-03-07 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160307 Merge with NetBSD make, pick up o var.c: fix :ts\nnn to be octal by default. o meta.c: meta_finish() to cleanup memory. 2016-02-26 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160226 Merge with NetBSD make, pick up o meta.c: allow meta file for makeDepend if makefiles want it. 2016-02-19 Simon J. Gerraty * var.c: default .MAKE.SAVE_DOLLARS to FALSE for backwards compatability. * Makefile (MAKE_VERSION): 20160220 Merge with NetBSD make, pick up o var.c: add knob to control handling of '$$' in := 2016-02-18 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160218 Merge with NetBSD make, pick up o var.c: add .export-literal allows us to fix sys.clean-env.mk post the changes to Var_Subst. Var_Subst now takes flags, and does not consume '$$' in := 2016-02-17 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160217 Merge with NetBSD make, pick up o var.c: preserve '$$' in := o parse.c: add .dinclude for handling included makefile like .depend 2015-12-20 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151220 Merge with NetBSD make, pick up o suff.c: re-initialize suffNull when clearing suffixes. 2015-12-01 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151201 Merge with NetBSD make, pick up o cond.c: CondCvtArg: avoid access beyond end of empty buffer. o meta.c: meta_oodate: use lstat(2) for checking link target in case it is a symlink. o var.c: avoid calling brk_string and Var_Export1 with empty strings. 2015-11-26 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151126 Merge with NetBSD make, pick up o parse.c: ParseTrackInput don't access beyond end of old value. 2015-10-22 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151022 * Add support for BSD/OS which lacks inttypes.h and really needs sys/param.h for sys/sysctl.h also 'type' is not a shell builtin. * var.c: eliminate uint32_t and need for inttypes.h * main.c: PrintOnError flush stdout before run .ERROR * parse.c: cope with _SC_PAGESIZE not being defined. 2015-10-20 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151020 Merge with NetBSD make, pick up o var.c: fix uninitialized var 2015-10-12 Simon J. Gerraty * var.c: the conditional expressions used with ':?' can be expensive, if already discarding do not evaluate or expand anything. 2015-10-10 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151010 Merge with NetBSD make, pick up o Add Boolean wantit flag to Var_Subst and Var_Parse when FALSE we know we are discarding the result and can skip operations like Cmd_Exec. 2015-10-09 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151009 Merge with NetBSD make, pick up o var.c: don't check for NULL before free() o meta.c: meta_oodate, do not hard code ignore of makeDependfile 2015-09-10 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150910 Merge with NetBSD make, pick up o main.c: with -w print Enter/Leaving messages for objdir too if necessary. o centralize shell metachar handling * FILES: add metachar.[ch] 2015-06-06 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150606 Merge with NetBSD make, pick up o make.1: document .OBJDIR target 2015-05-05 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150505 Merge with NetBSD make, pick up o cond.c: be strict about lhs of comparison when evaluating .if but less so when called from variable expansion. o unit-tests/cond2.mk: test various error conditions 2015-05-04 Simon J. Gerraty * machine.sh (MACHINE): Add Bitrig patch from joerg@netbsd.org 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 ::= 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 367862) +++ head/contrib/bmake/FILES (revision 367863) @@ -1,750 +1,770 @@ ChangeLog FILES LICENSE Makefile Makefile.config.in PSD.doc/Makefile PSD.doc/tutorial.ms README VERSION 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 enum.c enum.h filemon/filemon.h filemon/filemon_dev.c filemon/filemon_ktrace.c find_lib.sh for.c getopt.c hash.c hash.h install-sh job.c job.h lst.c lst.h machine.sh main.c make-bootstrap.sh.in make-conf.h make.1 make.c make.h make_malloc.c make_malloc.h makefile.in meta.c meta.h metachar.c metachar.h missing/sys/cdefs.h mkdeps.sh nonints.h os.sh parse.c pathnames.h ranlib.h realpath.c setenv.c sigcompat.c str.c stresep.c strlcpy.c suff.c targ.c trace.c trace.h unit-tests/Makefile unit-tests/Makefile.config.in unit-tests/archive-suffix.exp unit-tests/archive-suffix.mk unit-tests/archive.exp unit-tests/archive.mk +unit-tests/cmd-errors-lint.exp +unit-tests/cmd-errors-lint.mk +unit-tests/cmd-errors.exp +unit-tests/cmd-errors.mk unit-tests/cmd-interrupt.exp unit-tests/cmd-interrupt.mk +unit-tests/cmdline-undefined.exp +unit-tests/cmdline-undefined.mk unit-tests/cmdline.exp unit-tests/cmdline.mk unit-tests/comment.exp unit-tests/comment.mk unit-tests/cond-cmp-numeric-eq.exp unit-tests/cond-cmp-numeric-eq.mk unit-tests/cond-cmp-numeric-ge.exp unit-tests/cond-cmp-numeric-ge.mk unit-tests/cond-cmp-numeric-gt.exp unit-tests/cond-cmp-numeric-gt.mk unit-tests/cond-cmp-numeric-le.exp unit-tests/cond-cmp-numeric-le.mk unit-tests/cond-cmp-numeric-lt.exp unit-tests/cond-cmp-numeric-lt.mk unit-tests/cond-cmp-numeric-ne.exp unit-tests/cond-cmp-numeric-ne.mk unit-tests/cond-cmp-numeric.exp unit-tests/cond-cmp-numeric.mk unit-tests/cond-cmp-string.exp unit-tests/cond-cmp-string.mk unit-tests/cond-cmp-unary.exp unit-tests/cond-cmp-unary.mk unit-tests/cond-func-commands.exp unit-tests/cond-func-commands.mk unit-tests/cond-func-defined.exp unit-tests/cond-func-defined.mk unit-tests/cond-func-empty.exp unit-tests/cond-func-empty.mk unit-tests/cond-func-exists.exp unit-tests/cond-func-exists.mk unit-tests/cond-func-make.exp unit-tests/cond-func-make.mk unit-tests/cond-func-target.exp unit-tests/cond-func-target.mk unit-tests/cond-func.exp unit-tests/cond-func.mk unit-tests/cond-late.exp unit-tests/cond-late.mk +unit-tests/cond-op-and-lint.exp +unit-tests/cond-op-and-lint.mk unit-tests/cond-op-and.exp unit-tests/cond-op-and.mk unit-tests/cond-op-not.exp unit-tests/cond-op-not.mk +unit-tests/cond-op-or-lint.exp +unit-tests/cond-op-or-lint.mk unit-tests/cond-op-or.exp unit-tests/cond-op-or.mk unit-tests/cond-op-parentheses.exp unit-tests/cond-op-parentheses.mk unit-tests/cond-op.exp unit-tests/cond-op.mk unit-tests/cond-short.exp unit-tests/cond-short.mk unit-tests/cond-token-number.exp unit-tests/cond-token-number.mk unit-tests/cond-token-plain.exp unit-tests/cond-token-plain.mk unit-tests/cond-token-string.exp unit-tests/cond-token-string.mk unit-tests/cond-token-var.exp unit-tests/cond-token-var.mk unit-tests/cond-undef-lint.exp unit-tests/cond-undef-lint.mk unit-tests/cond1.exp unit-tests/cond1.mk unit-tests/counter-append.exp unit-tests/counter-append.mk unit-tests/counter.exp unit-tests/counter.mk unit-tests/dep-colon-bug-cross-file.exp unit-tests/dep-colon-bug-cross-file.mk unit-tests/dep-colon.exp unit-tests/dep-colon.mk unit-tests/dep-double-colon-indep.exp unit-tests/dep-double-colon-indep.mk unit-tests/dep-double-colon.exp unit-tests/dep-double-colon.mk unit-tests/dep-exclam.exp unit-tests/dep-exclam.mk unit-tests/dep-none.exp unit-tests/dep-none.mk unit-tests/dep-percent.exp unit-tests/dep-percent.mk unit-tests/dep-var.exp unit-tests/dep-var.mk unit-tests/dep-wildcards.exp unit-tests/dep-wildcards.mk unit-tests/dep.exp unit-tests/dep.mk unit-tests/depsrc-end.exp unit-tests/depsrc-end.mk unit-tests/depsrc-exec.exp unit-tests/depsrc-exec.mk unit-tests/depsrc-ignore.exp unit-tests/depsrc-ignore.mk unit-tests/depsrc-made.exp unit-tests/depsrc-made.mk unit-tests/depsrc-make.exp unit-tests/depsrc-make.mk unit-tests/depsrc-meta.exp unit-tests/depsrc-meta.mk unit-tests/depsrc-nometa.exp unit-tests/depsrc-nometa.mk unit-tests/depsrc-nometa_cmp.exp unit-tests/depsrc-nometa_cmp.mk unit-tests/depsrc-nopath.exp unit-tests/depsrc-nopath.mk unit-tests/depsrc-notmain.exp unit-tests/depsrc-notmain.mk unit-tests/depsrc-optional.exp unit-tests/depsrc-optional.mk unit-tests/depsrc-phony.exp unit-tests/depsrc-phony.mk unit-tests/depsrc-precious.exp unit-tests/depsrc-precious.mk unit-tests/depsrc-recursive.exp unit-tests/depsrc-recursive.mk unit-tests/depsrc-silent.exp unit-tests/depsrc-silent.mk unit-tests/depsrc-use.exp unit-tests/depsrc-use.mk unit-tests/depsrc-usebefore-double-colon.exp unit-tests/depsrc-usebefore-double-colon.mk unit-tests/depsrc-usebefore.exp unit-tests/depsrc-usebefore.mk unit-tests/depsrc-wait.exp unit-tests/depsrc-wait.mk unit-tests/depsrc.exp unit-tests/depsrc.mk unit-tests/deptgt-begin.exp unit-tests/deptgt-begin.mk unit-tests/deptgt-default.exp unit-tests/deptgt-default.mk unit-tests/deptgt-delete_on_error.exp unit-tests/deptgt-delete_on_error.mk unit-tests/deptgt-end-jobs.exp unit-tests/deptgt-end-jobs.mk unit-tests/deptgt-end.exp unit-tests/deptgt-end.mk unit-tests/deptgt-error.exp unit-tests/deptgt-error.mk unit-tests/deptgt-ignore.exp unit-tests/deptgt-ignore.mk unit-tests/deptgt-interrupt.exp unit-tests/deptgt-interrupt.mk unit-tests/deptgt-main.exp unit-tests/deptgt-main.mk unit-tests/deptgt-makeflags.exp unit-tests/deptgt-makeflags.mk unit-tests/deptgt-no_parallel.exp unit-tests/deptgt-no_parallel.mk unit-tests/deptgt-nopath.exp unit-tests/deptgt-nopath.mk unit-tests/deptgt-notparallel.exp unit-tests/deptgt-notparallel.mk unit-tests/deptgt-objdir.exp unit-tests/deptgt-objdir.mk unit-tests/deptgt-order.exp unit-tests/deptgt-order.mk unit-tests/deptgt-path-suffix.exp unit-tests/deptgt-path-suffix.mk unit-tests/deptgt-path.exp unit-tests/deptgt-path.mk unit-tests/deptgt-phony.exp unit-tests/deptgt-phony.mk unit-tests/deptgt-precious.exp unit-tests/deptgt-precious.mk unit-tests/deptgt-shell.exp unit-tests/deptgt-shell.mk unit-tests/deptgt-silent.exp unit-tests/deptgt-silent.mk unit-tests/deptgt-stale.exp unit-tests/deptgt-stale.mk unit-tests/deptgt-suffixes.exp unit-tests/deptgt-suffixes.mk unit-tests/deptgt.exp unit-tests/deptgt.mk unit-tests/dir-expand-path.exp unit-tests/dir-expand-path.mk unit-tests/dir.exp unit-tests/dir.mk unit-tests/directive-dinclude.exp unit-tests/directive-dinclude.mk unit-tests/directive-elif.exp unit-tests/directive-elif.mk unit-tests/directive-elifdef.exp unit-tests/directive-elifdef.mk unit-tests/directive-elifmake.exp unit-tests/directive-elifmake.mk unit-tests/directive-elifndef.exp unit-tests/directive-elifndef.mk unit-tests/directive-elifnmake.exp unit-tests/directive-elifnmake.mk unit-tests/directive-else.exp unit-tests/directive-else.mk unit-tests/directive-endif.exp unit-tests/directive-endif.mk unit-tests/directive-error.exp unit-tests/directive-error.mk unit-tests/directive-export-env.exp unit-tests/directive-export-env.mk unit-tests/directive-export-gmake.exp unit-tests/directive-export-gmake.mk unit-tests/directive-export-literal.exp unit-tests/directive-export-literal.mk unit-tests/directive-export.exp unit-tests/directive-export.mk unit-tests/directive-for-generating-endif.exp unit-tests/directive-for-generating-endif.mk unit-tests/directive-for.exp unit-tests/directive-for.mk unit-tests/directive-hyphen-include.exp unit-tests/directive-hyphen-include.mk +unit-tests/directive-if-nested.exp +unit-tests/directive-if-nested.mk unit-tests/directive-if.exp unit-tests/directive-if.mk unit-tests/directive-ifdef.exp unit-tests/directive-ifdef.mk unit-tests/directive-ifmake.exp unit-tests/directive-ifmake.mk unit-tests/directive-ifndef.exp unit-tests/directive-ifndef.mk unit-tests/directive-ifnmake.exp unit-tests/directive-ifnmake.mk unit-tests/directive-include-fatal.exp unit-tests/directive-include-fatal.mk unit-tests/directive-include.exp unit-tests/directive-include.mk unit-tests/directive-info.exp unit-tests/directive-info.mk unit-tests/directive-sinclude.exp unit-tests/directive-sinclude.mk unit-tests/directive-undef.exp unit-tests/directive-undef.mk unit-tests/directive-unexport-env.exp unit-tests/directive-unexport-env.mk unit-tests/directive-unexport.exp unit-tests/directive-unexport.mk unit-tests/directive-warning.exp unit-tests/directive-warning.mk unit-tests/directive.exp unit-tests/directive.mk -unit-tests/directives.exp -unit-tests/directives.mk unit-tests/dollar.exp unit-tests/dollar.mk unit-tests/doterror.exp unit-tests/doterror.mk unit-tests/dotwait.exp unit-tests/dotwait.mk unit-tests/envfirst.exp unit-tests/envfirst.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-variants.exp unit-tests/export-variants.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/gnode-submake.exp +unit-tests/gnode-submake.mk unit-tests/hanoi-include.exp unit-tests/hanoi-include.mk unit-tests/impsrc.exp unit-tests/impsrc.mk unit-tests/include-main.exp unit-tests/include-main.mk unit-tests/include-sub.mk unit-tests/include-subsub.mk +unit-tests/job-flags.exp +unit-tests/job-flags.mk unit-tests/job-output-long-lines.exp unit-tests/job-output-long-lines.mk unit-tests/lint.exp unit-tests/lint.mk unit-tests/make-exported.exp unit-tests/make-exported.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/modts.exp unit-tests/modts.mk unit-tests/modword.exp unit-tests/modword.mk +unit-tests/objdir-writable.exp +unit-tests/objdir-writable.mk unit-tests/opt-backwards.exp unit-tests/opt-backwards.mk unit-tests/opt-chdir.exp unit-tests/opt-chdir.mk unit-tests/opt-debug-all.exp unit-tests/opt-debug-all.mk unit-tests/opt-debug-archive.exp unit-tests/opt-debug-archive.mk unit-tests/opt-debug-cond.exp unit-tests/opt-debug-cond.mk unit-tests/opt-debug-curdir.exp unit-tests/opt-debug-curdir.mk unit-tests/opt-debug-dir.exp unit-tests/opt-debug-dir.mk unit-tests/opt-debug-errors.exp unit-tests/opt-debug-errors.mk unit-tests/opt-debug-file.exp unit-tests/opt-debug-file.mk unit-tests/opt-debug-for.exp unit-tests/opt-debug-for.mk unit-tests/opt-debug-graph1.exp unit-tests/opt-debug-graph1.mk unit-tests/opt-debug-graph2.exp unit-tests/opt-debug-graph2.mk unit-tests/opt-debug-graph3.exp unit-tests/opt-debug-graph3.mk unit-tests/opt-debug-hash.exp unit-tests/opt-debug-hash.mk unit-tests/opt-debug-jobs.exp unit-tests/opt-debug-jobs.mk unit-tests/opt-debug-lint.exp unit-tests/opt-debug-lint.mk unit-tests/opt-debug-loud.exp unit-tests/opt-debug-loud.mk unit-tests/opt-debug-making.exp unit-tests/opt-debug-making.mk unit-tests/opt-debug-meta.exp unit-tests/opt-debug-meta.mk unit-tests/opt-debug-no-rm.exp unit-tests/opt-debug-no-rm.mk unit-tests/opt-debug-parse.exp unit-tests/opt-debug-parse.mk unit-tests/opt-debug-suff.exp unit-tests/opt-debug-suff.mk unit-tests/opt-debug-targets.exp unit-tests/opt-debug-targets.mk unit-tests/opt-debug-var.exp unit-tests/opt-debug-var.mk unit-tests/opt-debug-varraw.exp unit-tests/opt-debug-varraw.mk unit-tests/opt-debug-x-trace.exp unit-tests/opt-debug-x-trace.mk unit-tests/opt-debug.exp unit-tests/opt-debug.mk unit-tests/opt-define.exp unit-tests/opt-define.mk unit-tests/opt-env.exp unit-tests/opt-env.mk unit-tests/opt-file.exp unit-tests/opt-file.mk unit-tests/opt-ignore.exp unit-tests/opt-ignore.mk unit-tests/opt-include-dir.exp unit-tests/opt-include-dir.mk unit-tests/opt-jobs-internal.exp unit-tests/opt-jobs-internal.mk unit-tests/opt-jobs.exp unit-tests/opt-jobs.mk unit-tests/opt-keep-going.exp unit-tests/opt-keep-going.mk unit-tests/opt-m-include-dir.exp unit-tests/opt-m-include-dir.mk unit-tests/opt-no-action-at-all.exp unit-tests/opt-no-action-at-all.mk unit-tests/opt-no-action.exp unit-tests/opt-no-action.mk unit-tests/opt-query.exp unit-tests/opt-query.mk unit-tests/opt-raw.exp unit-tests/opt-raw.mk unit-tests/opt-silent.exp unit-tests/opt-silent.mk +unit-tests/opt-touch-jobs.exp +unit-tests/opt-touch-jobs.mk unit-tests/opt-touch.exp unit-tests/opt-touch.mk unit-tests/opt-tracefile.exp unit-tests/opt-tracefile.mk unit-tests/opt-var-expanded.exp unit-tests/opt-var-expanded.mk unit-tests/opt-var-literal.exp unit-tests/opt-var-literal.mk unit-tests/opt-warnings-as-errors.exp unit-tests/opt-warnings-as-errors.mk unit-tests/opt-where-am-i.exp unit-tests/opt-where-am-i.mk unit-tests/opt-x-reduce-exported.exp unit-tests/opt-x-reduce-exported.mk unit-tests/opt.exp unit-tests/opt.mk unit-tests/order.exp unit-tests/order.mk unit-tests/parse-var.exp unit-tests/parse-var.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/recursive.exp unit-tests/recursive.mk unit-tests/sh-dots.exp unit-tests/sh-dots.mk unit-tests/sh-jobs-error.exp unit-tests/sh-jobs-error.mk unit-tests/sh-jobs.exp unit-tests/sh-jobs.mk unit-tests/sh-leading-at.exp unit-tests/sh-leading-at.mk unit-tests/sh-leading-hyphen.exp unit-tests/sh-leading-hyphen.mk unit-tests/sh-leading-plus.exp unit-tests/sh-leading-plus.mk unit-tests/sh-meta-chars.exp unit-tests/sh-meta-chars.mk unit-tests/sh-multi-line.exp unit-tests/sh-multi-line.mk unit-tests/sh-single-line.exp unit-tests/sh-single-line.mk unit-tests/sh.exp unit-tests/sh.mk unit-tests/shell-csh.exp unit-tests/shell-csh.mk unit-tests/shell-custom.exp unit-tests/shell-custom.mk unit-tests/shell-ksh.exp unit-tests/shell-ksh.mk unit-tests/shell-sh.exp unit-tests/shell-sh.mk unit-tests/suff-add-later.exp unit-tests/suff-add-later.mk unit-tests/suff-clear-regular.exp unit-tests/suff-clear-regular.mk unit-tests/suff-clear-single.exp unit-tests/suff-clear-single.mk unit-tests/suff-lookup.exp unit-tests/suff-lookup.mk unit-tests/suff-main.exp unit-tests/suff-main.mk unit-tests/suff-rebuild.exp unit-tests/suff-rebuild.mk +unit-tests/suff-self.exp +unit-tests/suff-self.mk unit-tests/suff-transform-endless.exp unit-tests/suff-transform-endless.mk unit-tests/suff-transform-expand.exp unit-tests/suff-transform-expand.mk unit-tests/suff-transform-select.exp unit-tests/suff-transform-select.mk unit-tests/sunshcmd.exp unit-tests/sunshcmd.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/use-inference.exp unit-tests/use-inference.mk unit-tests/var-class-cmdline.exp unit-tests/var-class-cmdline.mk unit-tests/var-class-env.exp unit-tests/var-class-env.mk unit-tests/var-class-global.exp unit-tests/var-class-global.mk unit-tests/var-class-local-legacy.exp unit-tests/var-class-local-legacy.mk unit-tests/var-class-local.exp unit-tests/var-class-local.mk unit-tests/var-class.exp unit-tests/var-class.mk unit-tests/var-op-append.exp unit-tests/var-op-append.mk unit-tests/var-op-assign.exp unit-tests/var-op-assign.mk unit-tests/var-op-default.exp unit-tests/var-op-default.mk unit-tests/var-op-expand.exp unit-tests/var-op-expand.mk unit-tests/var-op-shell.exp unit-tests/var-op-shell.mk unit-tests/var-op-sunsh.exp unit-tests/var-op-sunsh.mk unit-tests/var-op.exp unit-tests/var-op.mk unit-tests/var-recursive.exp unit-tests/var-recursive.mk unit-tests/varcmd.exp unit-tests/varcmd.mk unit-tests/vardebug.exp unit-tests/vardebug.mk unit-tests/varfind.exp unit-tests/varfind.mk unit-tests/varmisc.exp unit-tests/varmisc.mk unit-tests/varmod-assign.exp unit-tests/varmod-assign.mk unit-tests/varmod-defined.exp unit-tests/varmod-defined.mk unit-tests/varmod-edge.exp unit-tests/varmod-edge.mk unit-tests/varmod-exclam-shell.exp unit-tests/varmod-exclam-shell.mk unit-tests/varmod-extension.exp unit-tests/varmod-extension.mk unit-tests/varmod-gmtime.exp unit-tests/varmod-gmtime.mk unit-tests/varmod-hash.exp unit-tests/varmod-hash.mk unit-tests/varmod-head.exp unit-tests/varmod-head.mk unit-tests/varmod-ifelse.exp unit-tests/varmod-ifelse.mk unit-tests/varmod-l-name-to-value.exp unit-tests/varmod-l-name-to-value.mk unit-tests/varmod-localtime.exp unit-tests/varmod-localtime.mk unit-tests/varmod-loop.exp unit-tests/varmod-loop.mk unit-tests/varmod-match-escape.exp unit-tests/varmod-match-escape.mk unit-tests/varmod-match.exp unit-tests/varmod-match.mk unit-tests/varmod-no-match.exp unit-tests/varmod-no-match.mk unit-tests/varmod-order-reverse.exp unit-tests/varmod-order-reverse.mk unit-tests/varmod-order-shuffle.exp unit-tests/varmod-order-shuffle.mk unit-tests/varmod-order.exp unit-tests/varmod-order.mk unit-tests/varmod-path.exp unit-tests/varmod-path.mk unit-tests/varmod-quote-dollar.exp unit-tests/varmod-quote-dollar.mk unit-tests/varmod-quote.exp unit-tests/varmod-quote.mk unit-tests/varmod-range.exp unit-tests/varmod-range.mk unit-tests/varmod-remember.exp unit-tests/varmod-remember.mk unit-tests/varmod-root.exp unit-tests/varmod-root.mk unit-tests/varmod-select-words.exp unit-tests/varmod-select-words.mk unit-tests/varmod-shell.exp unit-tests/varmod-shell.mk unit-tests/varmod-subst-regex.exp unit-tests/varmod-subst-regex.mk unit-tests/varmod-subst.exp unit-tests/varmod-subst.mk unit-tests/varmod-sysv.exp unit-tests/varmod-sysv.mk unit-tests/varmod-tail.exp unit-tests/varmod-tail.mk unit-tests/varmod-to-abs.exp unit-tests/varmod-to-abs.mk unit-tests/varmod-to-lower.exp unit-tests/varmod-to-lower.mk unit-tests/varmod-to-many-words.exp unit-tests/varmod-to-many-words.mk unit-tests/varmod-to-one-word.exp unit-tests/varmod-to-one-word.mk unit-tests/varmod-to-separator.exp unit-tests/varmod-to-separator.mk unit-tests/varmod-to-upper.exp unit-tests/varmod-to-upper.mk unit-tests/varmod-undefined.exp unit-tests/varmod-undefined.mk unit-tests/varmod-unique.exp unit-tests/varmod-unique.mk unit-tests/varmod.exp unit-tests/varmod.mk unit-tests/varname-dollar.exp unit-tests/varname-dollar.mk unit-tests/varname-dot-alltargets.exp unit-tests/varname-dot-alltargets.mk unit-tests/varname-dot-curdir.exp unit-tests/varname-dot-curdir.mk unit-tests/varname-dot-includedfromdir.exp unit-tests/varname-dot-includedfromdir.mk unit-tests/varname-dot-includedfromfile.exp unit-tests/varname-dot-includedfromfile.mk unit-tests/varname-dot-includes.exp unit-tests/varname-dot-includes.mk unit-tests/varname-dot-libs.exp unit-tests/varname-dot-libs.mk unit-tests/varname-dot-make-dependfile.exp unit-tests/varname-dot-make-dependfile.mk unit-tests/varname-dot-make-expand_variables.exp unit-tests/varname-dot-make-expand_variables.mk unit-tests/varname-dot-make-exported.exp unit-tests/varname-dot-make-exported.mk unit-tests/varname-dot-make-jobs-prefix.exp unit-tests/varname-dot-make-jobs-prefix.mk unit-tests/varname-dot-make-jobs.exp unit-tests/varname-dot-make-jobs.mk unit-tests/varname-dot-make-level.exp unit-tests/varname-dot-make-level.mk unit-tests/varname-dot-make-makefile_preference.exp unit-tests/varname-dot-make-makefile_preference.mk unit-tests/varname-dot-make-makefiles.exp unit-tests/varname-dot-make-makefiles.mk unit-tests/varname-dot-make-meta-bailiwick.exp unit-tests/varname-dot-make-meta-bailiwick.mk unit-tests/varname-dot-make-meta-created.exp unit-tests/varname-dot-make-meta-created.mk unit-tests/varname-dot-make-meta-files.exp unit-tests/varname-dot-make-meta-files.mk unit-tests/varname-dot-make-meta-ignore_filter.exp unit-tests/varname-dot-make-meta-ignore_filter.mk unit-tests/varname-dot-make-meta-ignore_paths.exp unit-tests/varname-dot-make-meta-ignore_paths.mk unit-tests/varname-dot-make-meta-ignore_patterns.exp unit-tests/varname-dot-make-meta-ignore_patterns.mk unit-tests/varname-dot-make-meta-prefix.exp unit-tests/varname-dot-make-meta-prefix.mk unit-tests/varname-dot-make-mode.exp unit-tests/varname-dot-make-mode.mk unit-tests/varname-dot-make-path_filemon.exp unit-tests/varname-dot-make-path_filemon.mk unit-tests/varname-dot-make-pid.exp unit-tests/varname-dot-make-pid.mk unit-tests/varname-dot-make-ppid.exp unit-tests/varname-dot-make-ppid.mk unit-tests/varname-dot-make-save_dollars.exp unit-tests/varname-dot-make-save_dollars.mk unit-tests/varname-dot-makeoverrides.exp unit-tests/varname-dot-makeoverrides.mk unit-tests/varname-dot-newline.exp unit-tests/varname-dot-newline.mk unit-tests/varname-dot-objdir.exp unit-tests/varname-dot-objdir.mk unit-tests/varname-dot-parsedir.exp unit-tests/varname-dot-parsedir.mk unit-tests/varname-dot-parsefile.exp unit-tests/varname-dot-parsefile.mk unit-tests/varname-dot-path.exp unit-tests/varname-dot-path.mk unit-tests/varname-dot-shell.exp unit-tests/varname-dot-shell.mk unit-tests/varname-dot-targets.exp unit-tests/varname-dot-targets.mk unit-tests/varname-empty.exp unit-tests/varname-empty.mk unit-tests/varname-make.exp unit-tests/varname-make.mk unit-tests/varname-make_print_var_on_error-jobs.exp unit-tests/varname-make_print_var_on_error-jobs.mk unit-tests/varname-make_print_var_on_error.exp unit-tests/varname-make_print_var_on_error.mk unit-tests/varname-makefile.exp unit-tests/varname-makefile.mk unit-tests/varname-makeflags.exp unit-tests/varname-makeflags.mk unit-tests/varname-pwd.exp unit-tests/varname-pwd.mk unit-tests/varname-vpath.exp unit-tests/varname-vpath.mk unit-tests/varname.exp unit-tests/varname.mk unit-tests/varparse-dynamic.exp unit-tests/varparse-dynamic.mk +unit-tests/varparse-errors.exp +unit-tests/varparse-errors.mk unit-tests/varparse-mod.exp unit-tests/varparse-mod.mk unit-tests/varparse-undef-partial.exp unit-tests/varparse-undef-partial.mk unit-tests/varquote.exp unit-tests/varquote.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 367862) +++ head/contrib/bmake/Makefile (revision 367863) @@ -1,214 +1,220 @@ -# $Id: Makefile,v 1.113 2020/10/26 17:55:09 sjg Exp $ +# $Id: Makefile,v 1.114 2020/11/13 21:47:25 sjg Exp $ PROG= bmake SRCS= \ arch.c \ buf.c \ compat.c \ cond.c \ dir.c \ enum.c \ for.c \ hash.c \ job.c \ lst.c \ main.c \ make.c \ make_malloc.c \ meta.c \ metachar.c \ parse.c \ str.c \ suff.c \ targ.c \ trace.c \ util.c \ var.c .-include "VERSION" .-include "Makefile.inc" # 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}\"" + +.for x in FORCE_MACHINE FORCE_MACHINE_ARCH +.ifdef $x +COPTS.main.c+= "-D$x=\"${$x}\"" +.endif +.endfor # meta mode can be useful even without filemon # should be set by now USE_FILEMON ?= no .if ${USE_FILEMON:tl} != "no" .PATH: ${srcdir}/filemon SRCS+= filemon_${USE_FILEMON}.c COPTS.meta.c+= -DUSE_FILEMON -DUSE_FILEMON_${USE_FILEMON:tu} COPTS.job.c+= ${COPTS.meta.c} .if ${USE_FILEMON} == "dev" FILEMON_H ?= /usr/include/dev/filemon/filemon.h .if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h" COPTS.filemon_dev.c += -DHAVE_FILEMON_H -I${FILEMON_H:H} .endif .endif # USE_FILEMON == dev .endif # USE_FILEMON .PATH: ${srcdir} .if make(obj) || make(clean) SUBDIR+= unit-tests .endif # start-delete1 for bsd.after-import.mk # we skip a lot of this when building as part of FreeBSD etc. # list of OS's which are derrived from BSD4.4 BSD44_LIST= NetBSD FreeBSD OpenBSD DragonFly MirBSD Bitrig # we are... OS := ${.MAKE.OS:U${uname -s:L:sh}} # 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_NAME} ${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: @(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 '/^.Dt/s/MAKE/${PROG:tu}/' \ -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= ${SHAREDIR.bmake:U${prefix}/share} BINDIR= ${BINDIR.bmake:U${prefix}/bin} MANDIR= ${MANDIR.bmake:U${SHAREDIR}/man} .if !exists(.depend) ${OBJS}: config.h .endif # start-delete2 for bsd.after-import.mk # make sure that MAKE_VERSION gets updated. main.o: ${srcdir}/VERSION .if ${MK_AUTOCONF_MK} == "yes" CONFIGURE_DEPS += ${.CURDIR}/VERSION # we do not need or want the generated makefile CONFIGURE_ARGS += --without-makefile .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 TEST_MAKE ?= ${.OBJDIR}/${PROG:T} accept test: cd ${.CURDIR}/unit-tests && \ MAKEFLAGS= ${TEST_MAKE} -r -m / ${.TARGET} ${TESTS:DTESTS=${TESTS:Q}} Index: head/contrib/bmake/Makefile.config.in =================================================================== --- head/contrib/bmake/Makefile.config.in (revision 367862) +++ head/contrib/bmake/Makefile.config.in (revision 367863) @@ -1,22 +1,22 @@ # things set by configure _MAKE_VERSION?=@_MAKE_VERSION@ prefix?= @prefix@ srcdir= @srcdir@ CC?= @CC@ -MACHINE?= @machine@ -MACHINE_ARCH?= @machine_arch@ +@force_machine@MACHINE?= @machine@ +@force_machine_arch@MACHINE_ARCH?= @machine_arch@ DEFAULT_SYS_PATH?= @default_sys_path@ CPPFLAGS+= @CPPFLAGS@ CFLAGS+= ${CPPFLAGS} @DEFS@ LDFLAGS+= @LDFLAGS@ LIBOBJS+= @LIBOBJS@ LDADD+= @LIBS@ USE_META?= @use_meta@ USE_FILEMON?= @use_filemon@ FILEMON_H?= @filemon_h@ BMAKE_PATH_MAX?= @bmake_path_max@ # used if MAXPATHLEN not defined CPPFLAGS+= -DBMAKE_PATH_MAX=${BMAKE_PATH_MAX} Index: head/contrib/bmake/VERSION =================================================================== --- head/contrib/bmake/VERSION (revision 367862) +++ head/contrib/bmake/VERSION (revision 367863) @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20201101 +_MAKE_VERSION=20201117 Index: head/contrib/bmake/arch.c =================================================================== --- head/contrib/bmake/arch.c (revision 367862) +++ head/contrib/bmake/arch.c (revision 367863) @@ -1,1158 +1,1123 @@ -/* $NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $ */ +/* $NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig 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. */ -/*- - * arch.c -- - * Functions to manipulate libraries, archives and their members. +/* Manipulate libraries, archives and their members. * - * Once again, cacheing/hashing comes into play in the manipulation - * of archives. The first time an archive is referenced, all of its members' - * headers are read and hashed and the archive closed again. All hashed - * archives are kept on a list which is searched each time an archive member - * is referenced. + * The first time an archive is referenced, all of its members' headers are + * read and cached and the archive closed again. All cached archives are kept + * on a list which is searched each time an archive member is referenced. * * The interface to this module is: + * + * Arch_Init Initialize this module. + * + * Arch_End Clean up this module. + * * Arch_ParseArchive - * Given an archive specification, return a list - * of GNode's, one for each member in the spec. - * FALSE is returned if the specification is - * invalid for some reason. + * Parse an archive specification such as + * "archive.a(member1 member2)". * * Arch_Touch Alter the modification time of the archive * member described by the given node to be - * the current time. + * the time when make was started. * * Arch_TouchLib Update the modification time of the library * described by the given node. This is special * because it also updates the modification time * of the library's table of contents. * - * Arch_MTime Find the modification time of a member of - * an archive *in the archive*. The time is also - * placed in the member's GNode. Returns the - * modification time. + * Arch_UpdateMTime + * Find the modification time of a member of + * an archive *in the archive* and place it in the + * member's GNode. * - * Arch_MemTime Find the modification time of a member of + * Arch_UpdateMemberMTime + * Find the modification time of a member of * an archive. Called when the member doesn't * already exist. Looks in the archive for the * modification time. Returns the modification * time. * * Arch_FindLib Search for a library along a path. The * library name in the GNode should be in * -l format. * - * Arch_LibOODate Special function to decide if a library node - * is out-of-date. - * - * Arch_Init Initialize this module. - * - * Arch_End Clean up this module. + * Arch_LibOODate Decide if a library node is out-of-date. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #ifdef HAVE_AR_H #include #else struct ar_hdr { char ar_name[16]; /* name */ char ar_date[12]; /* modification time */ char ar_uid[6]; /* user id */ char ar_gid[6]; /* group id */ char ar_mode[8]; /* octal file permissions */ char ar_size[10]; /* size in bytes */ #ifndef ARFMAG #define ARFMAG "`\n" #endif char ar_fmag[2]; /* consistency check */ }; #endif #if defined(HAVE_RANLIB_H) && !(defined(__ELF__) || defined(NO_RANLIB)) #include #endif #ifdef HAVE_UTIME_H #include #endif #include "make.h" #include "dir.h" /* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $"); +MAKE_RCSID("$NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $"); -#ifdef TARGET_MACHINE -#undef MAKE_MACHINE -#define MAKE_MACHINE TARGET_MACHINE -#endif -#ifdef TARGET_MACHINE_ARCH -#undef MAKE_MACHINE_ARCH -#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH -#endif - typedef struct List ArchList; typedef struct ListNode ArchListNode; static ArchList *archives; /* The archives we've already examined */ typedef struct Arch { char *name; /* Name of archive */ HashTable members; /* All the members of the archive described * by key/value pairs */ char *fnametab; /* Extended name table strings */ size_t fnamesize; /* Size of the string table */ } Arch; static FILE *ArchFindMember(const char *, const char *, struct ar_hdr *, const char *); #if defined(__svr4__) || defined(__SVR4) || defined(__ELF__) #define SVR4ARCHIVES static int ArchSVR4Entry(Arch *, char *, size_t, FILE *); #endif #if defined(_AIX) # define AR_NAME _ar_name.ar_name # define AR_FMAG _ar_name.ar_fmag # define SARMAG SAIAMAG # define ARMAG AIAMAG # define ARFMAG AIAFMAG #endif #ifndef AR_NAME # define AR_NAME ar_name #endif #ifndef AR_DATE # define AR_DATE ar_date #endif #ifndef AR_SIZE # define AR_SIZE ar_size #endif #ifndef AR_FMAG # define AR_FMAG ar_fmag #endif #ifndef ARMAG # define ARMAG "!\n" #endif #ifndef SARMAG # define SARMAG 8 #endif #ifdef CLEANUP static void ArchFree(void *ap) { Arch *a = ap; HashIter hi; /* Free memory from hash entries */ HashIter_Init(&hi, &a->members); while (HashIter_Next(&hi) != NULL) free(hi.entry->value); free(a->name); free(a->fnametab); HashTable_Done(&a->members); free(a); } #endif -/*- - *----------------------------------------------------------------------- - * Arch_ParseArchive -- - * Parse the archive specification in the given line and find/create - * the nodes for the specified archive members, placing their nodes - * on the given list. +/* + * Parse an archive specification such as "archive.a(member1 member2.${EXT})", + * adding nodes for the expanded members to nodeLst. Nodes are created as + * necessary. * * Input: - * linePtr Pointer to start of specification - * nodeLst Lst on which to place the nodes - * ctxt Context in which to expand variables + * pp The start of the specification. + * nodeLst The list on which to place the nodes. + * ctxt The context in which to expand variables. * - * Results: - * TRUE if it was a valid specification. The linePtr is updated - * to point to the first non-space after the archive spec. The - * nodes for the members are placed on the given list. - *----------------------------------------------------------------------- + * Output: + * return TRUE if it was a valid specification. + * *pp Points to the first non-space after the archive spec. + * *nodeLst Nodes for the members have been added. */ Boolean -Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) +Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt) { char *cp; /* Pointer into line */ GNode *gn; /* New node */ char *libName; /* Library-part of specification */ + char *libName_freeIt = NULL; char *memName; /* Member-part of specification */ char saveChar; /* Ending delimiter of member-name */ - Boolean subLibName; /* TRUE if libName should have/had - * variable substitution performed on it */ + Boolean expandLibName; /* Whether the parsed libName contains + * variable expressions that need to be + * expanded */ - libName = *linePtr; + libName = *pp; + expandLibName = FALSE; - subLibName = FALSE; - for (cp = libName; *cp != '(' && *cp != '\0';) { if (*cp == '$') { /* * Variable spec, so call the Var module to parse the puppy * so we can safely advance beyond it... */ const char *nested_p = cp; void *result_freeIt; const char *result; Boolean isError; - (void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES, + /* XXX: is expanded twice: once here and once below */ + (void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR, &result, &result_freeIt); /* TODO: handle errors */ isError = result == var_Error; free(result_freeIt); if (isError) return FALSE; - subLibName = TRUE; + expandLibName = TRUE; cp += nested_p - cp; } else cp++; } *cp++ = '\0'; - if (subLibName) { - (void)Var_Subst(libName, ctxt, VARE_UNDEFERR|VARE_WANTRES, &libName); + if (expandLibName) { + (void)Var_Subst(libName, ctxt, VARE_WANTRES | VARE_UNDEFERR, &libName); /* TODO: handle errors */ + libName_freeIt = libName; } for (;;) { /* * First skip to the start of the member's name, mark that * place and skip to the end of it (either white-space or * a close paren). */ Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */ pp_skip_whitespace(&cp); memName = cp; while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) { if (*cp == '$') { /* * Variable spec, so call the Var module to parse the puppy * so we can safely advance beyond it... */ void *freeIt; const char *result; Boolean isError; const char *nested_p = cp; - (void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES, + (void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR, &result, &freeIt); /* TODO: handle errors */ isError = result == var_Error; free(freeIt); if (isError) return FALSE; doSubst = TRUE; cp += nested_p - cp; } else { cp++; } } /* * If the specification ends without a closing parenthesis, * chances are there's something wrong (like a missing backslash), * so it's better to return failure than allow such things to happen */ if (*cp == '\0') { - printf("No closing parenthesis in archive specification\n"); + Parse_Error(PARSE_FATAL, "No closing parenthesis in archive specification"); return FALSE; } /* * If we didn't move anywhere, we must be done */ if (cp == memName) { break; } saveChar = *cp; *cp = '\0'; /* * XXX: This should be taken care of intelligently by * SuffExpandChildren, both for the archive and the member portions. */ /* * If member contains variables, try and substitute for them. * This will slow down archive specs with dynamic sources, of course, * since we'll be (non-)substituting them three times, but them's * the breaks -- we need to do this since SuffExpandChildren calls * us, otherwise we could assume the thing would be taken care of * later. */ if (doSubst) { char *buf; char *sacrifice; char *oldMemName = memName; - (void)Var_Subst(memName, ctxt, VARE_UNDEFERR|VARE_WANTRES, + (void)Var_Subst(memName, ctxt, VARE_WANTRES | VARE_UNDEFERR, &memName); /* TODO: handle errors */ /* * Now form an archive spec and recurse to deal with nested * variables and multi-word variable values.... The results * are just placed at the end of the nodeLst we're returning. */ buf = sacrifice = str_concat4(libName, "(", memName, ")"); - if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) { + if (strchr(memName, '$') != NULL && + strcmp(memName, oldMemName) == 0) { /* * Must contain dynamic sources, so we can't deal with it now. * Just create an ARCHV node for the thing and let * SuffExpandChildren handle it... */ gn = Targ_GetNode(buf); gn->type |= OP_ARCHV; Lst_Append(nodeLst, gn); } else if (!Arch_ParseArchive(&sacrifice, nodeLst, ctxt)) { /* Error in nested call. */ free(buf); return FALSE; } free(buf); } else if (Dir_HasWildcards(memName)) { StringList *members = Lst_New(); Dir_Expand(memName, dirSearchPath, members); while (!Lst_IsEmpty(members)) { char *member = Lst_Dequeue(members); char *fullname = str_concat4(libName, "(", member, ")"); free(member); gn = Targ_GetNode(fullname); free(fullname); gn->type |= OP_ARCHV; Lst_Append(nodeLst, gn); } Lst_Free(members); } else { char *fullname = str_concat4(libName, "(", memName, ")"); gn = Targ_GetNode(fullname); free(fullname); /* * We've found the node, but have to make sure the rest of the * world knows it's an archive member, without having to * constantly check for parentheses, so we type the thing with * the OP_ARCHV bit before we place it on the end of the * provided list. */ gn->type |= OP_ARCHV; Lst_Append(nodeLst, gn); } if (doSubst) { free(memName); } *cp = saveChar; } - /* - * If substituted libName, free it now, since we need it no longer. - */ - if (subLibName) { - free(libName); - } + free(libName_freeIt); cp++; /* skip the ')' */ - /* We promised that linePtr would be set up at the next non-space. */ + /* We promised that pp would be set up at the next non-space. */ pp_skip_whitespace(&cp); - *linePtr = cp; + *pp = cp; return TRUE; } /* Locate a member of an archive, given the path of the archive and the path * of the desired member. * * Input: * archive Path to the archive * member Name of member; only its basename is used. - * hash TRUE if archive should be hashed if not already so. + * addToCache TRUE if archive should be cached if not already so. * * Results: - * The ar_hdr for the member. + * The ar_hdr for the member, or NULL. + * + * See ArchFindMember for an almost identical copy of this code. */ static struct ar_hdr * -ArchStatMember(const char *archive, const char *member, Boolean hash) +ArchStatMember(const char *archive, const char *member, Boolean addToCache) { -#define AR_MAX_NAME_LEN (sizeof(arh.AR_NAME) - 1) +#define AR_MAX_NAME_LEN (sizeof arh.AR_NAME - 1) FILE *arch; /* Stream to archive */ size_t size; /* Size of archive member */ char magic[SARMAG]; ArchListNode *ln; Arch *ar; /* Archive descriptor */ struct ar_hdr arh; /* archive-member header for reading archive */ char memName[MAXPATHLEN + 1]; /* Current member name while hashing. */ /* * Because of space constraints and similar things, files are archived * using their basename, not the entire path. */ const char *lastSlash = strrchr(member, '/'); if (lastSlash != NULL) member = lastSlash + 1; for (ln = archives->first; ln != NULL; ln = ln->next) { - const Arch *archPtr = ln->datum; - if (strcmp(archPtr->name, archive) == 0) + const Arch *a = ln->datum; + if (strcmp(a->name, archive) == 0) break; } if (ln != NULL) { struct ar_hdr *hdr; ar = ln->datum; hdr = HashTable_FindValue(&ar->members, member); if (hdr != NULL) return hdr; { /* Try truncated name */ char copy[AR_MAX_NAME_LEN + 1]; size_t len = strlen(member); if (len > AR_MAX_NAME_LEN) { len = AR_MAX_NAME_LEN; snprintf(copy, sizeof copy, "%s", member); + hdr = HashTable_FindValue(&ar->members, copy); } - hdr = HashTable_FindValue(&ar->members, copy); return hdr; } } - if (!hash) { + if (!addToCache) { /* - * Caller doesn't want the thing hashed, just use ArchFindMember + * Caller doesn't want the thing cached, just use ArchFindMember * to read the header for the member out and close down the stream - * again. Since the archive is not to be hashed, we assume there's + * again. Since the archive is not to be cached, we assume there's * no need to allocate extra room for the header we're returning, * so just declare it static. */ static struct ar_hdr sarh; arch = ArchFindMember(archive, member, &sarh, "r"); if (arch == NULL) return NULL; fclose(arch); return &sarh; } /* * We don't have this archive on the list yet, so we want to find out * everything that's in it and cache it so we can get at it quickly. */ arch = fopen(archive, "r"); if (arch == NULL) return NULL; /* * We use the ARMAG string to make sure this is an archive we * can handle... */ - if ((fread(magic, SARMAG, 1, arch) != 1) || - (strncmp(magic, ARMAG, SARMAG) != 0)) { - fclose(arch); + if (fread(magic, SARMAG, 1, arch) != 1 || + strncmp(magic, ARMAG, SARMAG) != 0) { + (void)fclose(arch); return NULL; } - ar = bmake_malloc(sizeof(Arch)); + ar = bmake_malloc(sizeof *ar); ar->name = bmake_strdup(archive); ar->fnametab = NULL; ar->fnamesize = 0; HashTable_Init(&ar->members); memName[AR_MAX_NAME_LEN] = '\0'; - while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) { - if (strncmp(arh.AR_FMAG, ARFMAG, sizeof(arh.AR_FMAG)) != 0) { - /* - * The header is bogus, so the archive is bad - * and there's no way we can recover... - */ + while (fread(&arh, sizeof arh, 1, arch) == 1) { + char *nameend; + + /* If the header is bogus, there's no way we can recover. */ + if (strncmp(arh.AR_FMAG, ARFMAG, sizeof arh.AR_FMAG) != 0) goto badarch; - } else { - char *nameend; - /* - * We need to advance the stream's pointer to the start of the - * next header. Files are padded with newlines to an even-byte - * boundary, so we need to extract the size of the file from the - * 'size' field of the header and round it up during the seek. - */ - arh.AR_SIZE[sizeof(arh.AR_SIZE) - 1] = '\0'; - size = (size_t)strtol(arh.ar_size, NULL, 10); + /* + * We need to advance the stream's pointer to the start of the + * next header. Files are padded with newlines to an even-byte + * boundary, so we need to extract the size of the file from the + * 'size' field of the header and round it up during the seek. + */ + arh.AR_SIZE[sizeof arh.AR_SIZE - 1] = '\0'; + size = (size_t)strtol(arh.AR_SIZE, NULL, 10); - memcpy(memName, arh.AR_NAME, sizeof(arh.AR_NAME)); - nameend = memName + AR_MAX_NAME_LEN; - while (*nameend == ' ') { - nameend--; - } - nameend[1] = '\0'; + memcpy(memName, arh.AR_NAME, sizeof arh.AR_NAME); + nameend = memName + AR_MAX_NAME_LEN; + while (nameend > memName && *nameend == ' ') + nameend--; + nameend[1] = '\0'; #ifdef SVR4ARCHIVES + /* + * svr4 names are slash terminated. Also svr4 extended AR format. + */ + if (memName[0] == '/') { /* - * svr4 names are slash terminated. Also svr4 extended AR format. + * svr4 magic mode; handle it */ - if (memName[0] == '/') { - /* - * svr4 magic mode; handle it - */ - switch (ArchSVR4Entry(ar, memName, size, arch)) { - case -1: /* Invalid data */ - goto badarch; - case 0: /* List of files entry */ - continue; - default: /* Got the entry */ - break; - } - } else { - if (nameend[0] == '/') - nameend[0] = '\0'; + switch (ArchSVR4Entry(ar, memName, size, arch)) { + case -1: /* Invalid data */ + goto badarch; + case 0: /* List of files entry */ + continue; + default: /* Got the entry */ + break; } + } else { + if (nameend[0] == '/') + nameend[0] = '\0'; + } #endif #ifdef AR_EFMT1 - /* - * BSD 4.4 extended AR format: #1/, with name as the - * first bytes of the file - */ - if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && - ch_isdigit(memName[sizeof(AR_EFMT1) - 1])) { + /* + * BSD 4.4 extended AR format: #1/, with name as the + * first bytes of the file + */ + if (strncmp(memName, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 && + ch_isdigit(memName[sizeof AR_EFMT1 - 1])) { - int elen = atoi(&memName[sizeof(AR_EFMT1) - 1]); + int elen = atoi(memName + sizeof AR_EFMT1 - 1); - if ((unsigned int)elen > MAXPATHLEN) - goto badarch; - if (fread(memName, (size_t)elen, 1, arch) != 1) - goto badarch; - memName[elen] = '\0'; - if (fseek(arch, -elen, SEEK_CUR) != 0) - goto badarch; - if (DEBUG(ARCH) || DEBUG(MAKE)) { - debug_printf("ArchStat: Extended format entry for %s\n", - memName); - } - } + if ((unsigned int)elen > MAXPATHLEN) + goto badarch; + if (fread(memName, (size_t)elen, 1, arch) != 1) + goto badarch; + memName[elen] = '\0'; + if (fseek(arch, -elen, SEEK_CUR) != 0) + goto badarch; + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("ArchStatMember: Extended format entry for %s\n", + memName); + } #endif - { - HashEntry *he; - he = HashTable_CreateEntry(&ar->members, memName, NULL); - HashEntry_Set(he, bmake_malloc(sizeof(struct ar_hdr))); - memcpy(HashEntry_Get(he), &arh, sizeof(struct ar_hdr)); - } + { + struct ar_hdr *cached_hdr = bmake_malloc(sizeof *cached_hdr); + memcpy(cached_hdr, &arh, sizeof arh); + HashTable_Set(&ar->members, memName, cached_hdr); } + if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0) goto badarch; } fclose(arch); Lst_Append(archives, ar); /* * Now that the archive has been read and cached, we can look into - * the hash table to find the desired member's header. + * the addToCache table to find the desired member's header. */ return HashTable_FindValue(&ar->members, member); badarch: fclose(arch); HashTable_Done(&ar->members); free(ar->fnametab); free(ar); return NULL; } #ifdef SVR4ARCHIVES /*- *----------------------------------------------------------------------- * ArchSVR4Entry -- * Parse an SVR4 style entry that begins with a slash. * If it is "//", then load the table of filenames * If it is "/", then try to substitute the long file name * from offset of a table previously read. * If a table is read, the file pointer is moved to the next archive * member. * * Results: * -1: Bad data in archive * 0: A table was loaded from the file * 1: Name was successfully substituted from table * 2: Name was not successfully substituted from table *----------------------------------------------------------------------- */ static int -ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) +ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch) { #define ARLONGNAMES1 "//" #define ARLONGNAMES2 "/ARFILENAMES" size_t entry; char *ptr, *eptr; - if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 || - strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) { + if (strncmp(inout_name, ARLONGNAMES1, sizeof ARLONGNAMES1 - 1) == 0 || + strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) { if (ar->fnametab != NULL) { DEBUG0(ARCH, "Attempted to redefine an SVR4 name table\n"); return -1; } /* * This is a table of archive names, so we build one for * ourselves */ ar->fnametab = bmake_malloc(size); ar->fnamesize = size; if (fread(ar->fnametab, size, 1, arch) != 1) { DEBUG0(ARCH, "Reading an SVR4 name table failed\n"); return -1; } eptr = ar->fnametab + size; for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++) if (*ptr == '/') { entry++; *ptr = '\0'; } DEBUG1(ARCH, "Found svr4 archive name table with %lu entries\n", (unsigned long)entry); return 0; } - if (name[1] == ' ' || name[1] == '\0') + if (inout_name[1] == ' ' || inout_name[1] == '\0') return 2; - entry = (size_t)strtol(&name[1], &eptr, 0); - if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) { - DEBUG1(ARCH, "Could not parse SVR4 name %s\n", name); + entry = (size_t)strtol(&inout_name[1], &eptr, 0); + if ((*eptr != ' ' && *eptr != '\0') || eptr == &inout_name[1]) { + DEBUG1(ARCH, "Could not parse SVR4 name %s\n", inout_name); return 2; } if (entry >= ar->fnamesize) { DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n", - name, (unsigned long)ar->fnamesize); + inout_name, (unsigned long)ar->fnamesize); return 2; } - DEBUG2(ARCH, "Replaced %s with %s\n", name, &ar->fnametab[entry]); + DEBUG2(ARCH, "Replaced %s with %s\n", inout_name, &ar->fnametab[entry]); - snprintf(name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]); + snprintf(inout_name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]); return 1; } #endif -/*- - *----------------------------------------------------------------------- - * ArchFindMember -- - * Locate a member of an archive, given the path of the archive and - * the path of the desired member. If the archive is to be modified, - * the mode should be "r+", if not, it should be "r". - * The passed struct ar_hdr structure is filled in. +static Boolean +ArchiveMember_HasName(const struct ar_hdr *hdr, + const char *name, size_t namelen) +{ + const size_t ar_name_len = sizeof hdr->AR_NAME; + const char *ar_name = hdr->AR_NAME; + + if (strncmp(ar_name, name, namelen) != 0) + return FALSE; + + if (namelen >= ar_name_len) + return namelen == ar_name_len; + + /* hdr->AR_NAME is space-padded to the right. */ + if (ar_name[namelen] == ' ') + return TRUE; + + /* In archives created by GNU binutils 2.27, the member names end with + * a slash. */ + if (ar_name[namelen] == '/' && + (namelen == ar_name_len || ar_name[namelen + 1] == ' ')) + return TRUE; + + return FALSE; +} + +/* Locate a member of an archive, given the path of the archive and the path + * of the desired member. * * Input: * archive Path to the archive * member Name of member. If it is a path, only the last * component is used. - * arhPtr Pointer to header structure to be filled in - * mode The mode for opening the stream + * out_arh Archive header to be filled in + * mode "r" for read-only access, "r+" for read-write access * - * Results: - * An FILE *, opened for reading and writing, positioned at the - * start of the member's struct ar_hdr, or NULL if the member was - * nonexistent. The current struct ar_hdr for member. - *----------------------------------------------------------------------- + * Output: + * return The archive file, positioned at the start of the + * member's struct ar_hdr, or NULL if the member doesn't + * exist. + * *out_arh The current struct ar_hdr for member. + * + * See ArchStatMember for an almost identical copy of this code. */ static FILE * -ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, +ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh, const char *mode) { FILE *arch; /* Stream to archive */ int size; /* Size of archive member */ char magic[SARMAG]; size_t len, tlen; const char *lastSlash; arch = fopen(archive, mode); if (arch == NULL) return NULL; /* * We use the ARMAG string to make sure this is an archive we * can handle... */ - if ((fread(magic, SARMAG, 1, arch) != 1) || - (strncmp(magic, ARMAG, SARMAG) != 0)) { + if (fread(magic, SARMAG, 1, arch) != 1 || + strncmp(magic, ARMAG, SARMAG) != 0) { fclose(arch); return NULL; } /* * Because of space constraints and similar things, files are archived * using their basename, not the entire path. */ lastSlash = strrchr(member, '/'); if (lastSlash != NULL) member = lastSlash + 1; len = tlen = strlen(member); - if (len > sizeof(arhPtr->AR_NAME)) { - tlen = sizeof(arhPtr->AR_NAME); + if (len > sizeof out_arh->AR_NAME) { + tlen = sizeof out_arh->AR_NAME; } - while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) { + while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) { - if (strncmp(arhPtr->AR_FMAG, ARFMAG, sizeof(arhPtr->AR_FMAG)) != 0) { + if (strncmp(out_arh->AR_FMAG, ARFMAG, sizeof out_arh->AR_FMAG) != 0) { /* * The header is bogus, so the archive is bad * and there's no way we can recover... */ fclose(arch); return NULL; } - if (strncmp(member, arhPtr->AR_NAME, tlen) == 0) { - /* - * If the member's name doesn't take up the entire 'name' field, - * we have to be careful of matching prefixes. Names are space- - * padded to the right, so if the character in 'name' at the end - * of the matched string is anything but a space, this isn't the - * member we sought. - */ - if (tlen != sizeof arhPtr->AR_NAME && arhPtr->AR_NAME[tlen] != ' ') - goto skip; + DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n", + archive, + (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME, + (int)sizeof out_arh->ar_date, out_arh->ar_date); + if (ArchiveMember_HasName(out_arh, member, len)) { /* - * To make life easier, we reposition the file at the start + * To make life easier for callers that want to update the + * archive, we reposition the file at the start * of the header we just read before we return the stream. * In a more general situation, it might be better to leave * the file at the actual member, rather than its header, but - * not here... + * not here. */ - if (fseek(arch, -(long)sizeof(struct ar_hdr), SEEK_CUR) != 0) { + if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) != 0) { fclose(arch); return NULL; } return arch; } #ifdef AR_EFMT1 /* * BSD 4.4 extended AR format: #1/, with name as the * first bytes of the file */ - if (strncmp(arhPtr->AR_NAME, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && - ch_isdigit(arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1])) + if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 && + ch_isdigit(out_arh->AR_NAME[sizeof AR_EFMT1 - 1])) { - int elen = atoi(&arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1]); + int elen = atoi(&out_arh->AR_NAME[sizeof AR_EFMT1 - 1]); char ename[MAXPATHLEN + 1]; if ((unsigned int)elen > MAXPATHLEN) { fclose(arch); return NULL; } if (fread(ename, (size_t)elen, 1, arch) != 1) { fclose(arch); return NULL; } ename[elen] = '\0'; - if (DEBUG(ARCH) || DEBUG(MAKE)) { - debug_printf("ArchFind: Extended format entry for %s\n", ename); - } + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("ArchFindMember: Extended format entry for %s\n", + ename); if (strncmp(ename, member, len) == 0) { /* Found as extended name */ if (fseek(arch, -(long)sizeof(struct ar_hdr) - elen, SEEK_CUR) != 0) { fclose(arch); return NULL; } return arch; } if (fseek(arch, -elen, SEEK_CUR) != 0) { fclose(arch); return NULL; } } #endif -skip: /* * This isn't the member we're after, so we need to advance the * stream's pointer to the start of the next header. Files are * padded with newlines to an even-byte boundary, so we need to * extract the size of the file from the 'size' field of the * header and round it up during the seek. */ - arhPtr->ar_size[sizeof(arhPtr->ar_size) - 1] = '\0'; - size = (int)strtol(arhPtr->ar_size, NULL, 10); + out_arh->AR_SIZE[sizeof out_arh->AR_SIZE - 1] = '\0'; + size = (int)strtol(out_arh->AR_SIZE, NULL, 10); if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) { fclose(arch); return NULL; } } - /* - * We've looked everywhere, but the member is not to be found. Close the - * archive and return NULL -- an error. - */ fclose(arch); return NULL; } -/*- - *----------------------------------------------------------------------- - * Arch_Touch -- - * Touch a member of an archive. - * The modification time of the entire archive is also changed. - * For a library, this could necessitate the re-ranlib'ing of the - * whole thing. +/* Touch a member of an archive, on disk. + * The GNode's modification time is left as-is. * + * The st_mtime of the entire archive is also changed. + * For a library, it may be required to run ranlib after this. + * * Input: * gn Node of member to touch * * Results: * The 'time' field of the member's header is updated. - *----------------------------------------------------------------------- */ void Arch_Touch(GNode *gn) { - FILE *arch; /* Stream open to archive, positioned properly */ - struct ar_hdr arh; /* Current header describing member */ + FILE *f; + struct ar_hdr arh; - arch = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), - &arh, "r+"); + f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+"); + if (f == NULL) + return; - snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long)now); - - if (arch != NULL) { - (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); - fclose(arch); - } + snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now); + (void)fwrite(&arh, sizeof arh, 1, f); + fclose(f); /* TODO: handle errors */ } /* Given a node which represents a library, touch the thing, making sure that - * the table of contents also is touched. + * the table of contents is also touched. * * Both the modification time of the library and of the RANLIBMAG member are - * set to 'now'. - * - * Input: - * gn The node of the library to touch - */ + * set to 'now'. */ void -Arch_TouchLib(GNode *gn) +Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED) { #ifdef RANLIBMAG - FILE * arch; /* Stream open to archive */ - struct ar_hdr arh; /* Header describing table of contents */ - struct utimbuf times; /* Times for utime() call */ + FILE *f; + struct ar_hdr arh; /* Header describing table of contents */ + struct utimbuf times; - arch = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); - snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long) now); + f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); + if (f == NULL) + return; - if (arch != NULL) { - (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); - fclose(arch); + snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now); + (void)fwrite(&arh, sizeof arh, 1, f); + fclose(f); /* TODO: handle errors */ - times.actime = times.modtime = now; - utime(gn->path, ×); - } -#else - (void)gn; + times.actime = times.modtime = now; + utime(gn->path, ×); /* TODO: handle errors */ #endif } -/* Return the modification time of a member of an archive. The mtime field - * of the given node is filled in with the value returned by the function. - * - * Input: - * gn Node describing archive member - */ -time_t -Arch_MTime(GNode *gn) +/* Update the mtime of the GNode with the mtime from the archive member on + * disk (or in the cache). */ +void +Arch_UpdateMTime(GNode *gn) { - struct ar_hdr *arhPtr; /* Header of desired member */ - time_t modTime; /* Modification time as an integer */ + struct ar_hdr *arh; - arhPtr = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE); - if (arhPtr != NULL) { - modTime = (time_t)strtol(arhPtr->AR_DATE, NULL, 10); - } else { - modTime = 0; - } - - gn->mtime = modTime; - return modTime; + arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE); + if (arh != NULL) + gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10); + else + gn->mtime = 0; } -/* Given a non-existent archive member's node, get its modification time from - * its archived form, if it exists. gn->mtime is filled in as well. */ -time_t -Arch_MemMTime(GNode *gn) +/* Given a non-existent archive member's node, update gn->mtime from its + * archived form, if it exists. */ +void +Arch_UpdateMemberMTime(GNode *gn) { GNodeListNode *ln; for (ln = gn->parents->first; ln != NULL; ln = ln->next) { GNode *pgn = ln->datum; if (pgn->type & OP_ARCHV) { /* * If the parent is an archive specification and is being made * and its member's name matches the name of the node we were * given, record the modification time of the parent in the * child. We keep searching its parents in case some other * parent requires this child to exist... */ const char *nameStart = strchr(pgn->name, '(') + 1; const char *nameEnd = strchr(nameStart, ')'); size_t nameLen = (size_t)(nameEnd - nameStart); if ((pgn->flags & REMAKE) && strncmp(nameStart, gn->name, nameLen) == 0) { - gn->mtime = Arch_MTime(pgn); + Arch_UpdateMTime(pgn); + gn->mtime = pgn->mtime; } } else if (pgn->flags & REMAKE) { /* * Something which isn't a library depends on the existence of * this target, so it needs to exist. */ gn->mtime = 0; break; } } - - return gn->mtime; } /* Search for a library along the given search path. * * The node's 'path' field is set to the found path (including the * actual file name, not -l...). If the system can handle the -L * flag when linking (or we cannot find the library), we assume that * the user has placed the .LIBS variable in the final linking * command (or the linker will know where to find it) and set the * TARGET variable for this node to be the node's name. Otherwise, * we set the TARGET variable to be the full path of the library, * as returned by Dir_FindFile. * * Input: * gn Node of library to find */ void Arch_FindLib(GNode *gn, SearchPath *path) { char *libName = str_concat3("lib", gn->name + 2, ".a"); gn->path = Dir_FindFile(libName, path); free(libName); #ifdef LIBRARIES Var_Set(TARGET, gn->name, gn); #else Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn); #endif } /* Decide if a node with the OP_LIB attribute is out-of-date. Called from - * Make_OODate to make its life easier. - * The library will be hashed if it hasn't been already. + * GNode_IsOODate to make its life easier. + * The library is cached if it hasn't been already. * * There are several ways for a library to be out-of-date that are * not available to ordinary files. In addition, there are ways * that are open to regular files that are not available to - * libraries. A library that is only used as a source is never + * libraries. + * + * A library that is only used as a source is never * considered out-of-date by itself. This does not preclude the * library's modification time from making its parent be out-of-date. * A library will be considered out-of-date for any of these reasons, * given that it is a target on a dependency line somewhere: * * Its modification time is less than that of one of its sources * (gn->mtime < gn->youngestChild->mtime). * * Its modification time is greater than the time at which the make * began (i.e. it's been modified in the course of the make, probably * by archiving). * * The modification time of one of its sources is greater than the one * of its RANLIBMAG member (i.e. its table of contents is out-of-date). - * We don't compare of the archive time vs. TOC time because they can be + * We don't compare the archive time vs. TOC time because they can be * too close. In my opinion we should not bother with the TOC at all * since this is used by 'ar' rules that affect the data contents of the * archive, not by ranlib rules, which affect the TOC. - * - * Input: - * gn The library's graph node - * - * Results: - * TRUE if the library is out-of-date. FALSE otherwise. */ Boolean Arch_LibOODate(GNode *gn) { Boolean oodate; if (gn->type & OP_PHONY) { oodate = TRUE; } else if (!GNode_IsTarget(gn) && Lst_IsEmpty(gn->children)) { oodate = FALSE; } else if ((!Lst_IsEmpty(gn->children) && gn->youngestChild == NULL) || (gn->mtime > now) || (gn->youngestChild != NULL && gn->mtime < gn->youngestChild->mtime)) { oodate = TRUE; } else { #ifdef RANLIBMAG - struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ + struct ar_hdr *arh; /* Header for __.SYMDEF */ int modTimeTOC; /* The table-of-contents's mod time */ - arhPtr = ArchStatMember(gn->path, RANLIBMAG, FALSE); + arh = ArchStatMember(gn->path, RANLIBMAG, FALSE); - if (arhPtr != NULL) { - modTimeTOC = (int)strtol(arhPtr->AR_DATE, NULL, 10); + if (arh != NULL) { + modTimeTOC = (int)strtol(arh->ar_date, NULL, 10); - if (DEBUG(ARCH) || DEBUG(MAKE)) { - debug_printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); - } - oodate = (gn->youngestChild == NULL || gn->youngestChild->mtime > modTimeTOC); + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("%s modified %s...", + RANLIBMAG, Targ_FmtTime(modTimeTOC)); + oodate = gn->youngestChild == NULL || + gn->youngestChild->mtime > modTimeTOC; } else { - /* - * A library w/o a table of contents is out-of-date - */ - if (DEBUG(ARCH) || DEBUG(MAKE)) { - debug_printf("No t.o.c...."); - } + /* A library without a table of contents is out-of-date. */ + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("no toc..."); oodate = TRUE; } #else oodate = FALSE; #endif } return oodate; } /* Initialize the archives module. */ void Arch_Init(void) { archives = Lst_New(); } /* Clean up the archives module. */ void Arch_End(void) { #ifdef CLEANUP Lst_Destroy(archives, ArchFree); #endif } Boolean Arch_IsLib(GNode *gn) { static const char armag[] = "!\n"; char buf[sizeof armag - 1]; int fd; if ((fd = open(gn->path, O_RDONLY)) == -1) return FALSE; if (read(fd, buf, sizeof buf) != sizeof buf) { (void)close(fd); return FALSE; } (void)close(fd); return memcmp(buf, armag, sizeof buf) == 0; } Index: head/contrib/bmake/bmake.1 =================================================================== --- head/contrib/bmake/bmake.1 (revision 367862) +++ head/contrib/bmake/bmake.1 (revision 367863) @@ -1,2455 +1,2468 @@ -.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $ +.\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig 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 November 1, 2020 +.Dd November 14, 2020 .Dt BMAKE 1 .Os .Sh NAME .Nm bmake .Nd maintain program dependencies .Sh SYNOPSIS .Nm -.Op Fl BeikNnqrstWwX +.Op Fl BeikNnqrSstWwX .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 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 h Print debugging information about hash table operations. .It Ar j Print debugging information about running multiple shells. .It Ar L Turn on lint checks. This will throw errors for variable assignments that do not parse correctly, at the time of assignment so the file and line number are available. .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, overriding the default behavior set via .Va .MAKE.EXPAND_VARIABLES . .It Ar v Print debugging information about variable assignment. .It Ar x Run shell commands with .Fl x so the actual commands are printed as they are executed. .El .It Fl e Specify that environment variables override macro assignments within makefiles. .It Fl f Ar makefile Specify a makefile to read instead of the default .Ql Pa makefile . If .Ar makefile is .Ql Fl , standard input is read. Multiple makefiles may be specified, and are read in the order specified. .It Fl I Ar directory Specify a directory in which to search for makefiles and included makefiles. The system makefile directory (or directories, see the .Fl m option) is automatically included as part of this list. .It Fl i Ignore non-zero exit of shell commands in the makefile. Equivalent to specifying .Ql Fl before each command line in the makefile. .It Fl J Ar private This option should .Em not be specified by the user. .Pp When the .Ar j option is in use in a recursive build, this option is passed by a make to child makes to allow all the make processes in the build to cooperate to avoid overloading the system. .It Fl j Ar max_jobs Specify the maximum number of jobs that .Nm may have running at any one time. The value is saved in .Va .MAKE.JOBS . Turns compatibility mode off, unless the .Ar B flag is also specified. When compatibility mode is off, all commands associated with a target are executed in a single shell invocation as opposed to the traditional one shell invocation per line. This can break traditional scripts which change directories on each command invocation and then expect to start with a fresh environment on the next line. It is more efficient to correct the scripts rather than turn backwards compatibility on. .It Fl k Continue processing after errors are encountered, but only on those targets that do not depend on the target whose creation caused the error. .It Fl m Ar directory Specify a directory in which to search for sys.mk and makefiles included via the .Li \&< Ns Ar file Ns Li \&> 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 .Li \*q Ns Ar file Ns Li \*q 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 file system. 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) or the command is prefixed with .Ql Ic + . .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 +Stop processing if an error is encountered. +This is the default behavior and the opposite of +.Fl k . .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 the value of .Ar variable . 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. The value printed is extracted from the global context after all makefiles have been read. By default, the raw variable contents (which may include additional unexpanded variable references) are shown. If .Ar variable contains a .Ql \&$ then the value will be recursively expanded to its complete resultant text before printing. The expanded value will also be printed if .Va .MAKE.EXPAND_VARIABLES is set to true and the .Fl dV option has not been used to override it. Note that loop-local and target-local variables, as well as values taken temporarily by global variables during makefile processing, are not accessible via this option. The .Fl dv debug mode can be used to see these at the cost of generating substantial extraneous output. .It Fl v Ar variable Like .Fl V but the variable is always expanded to its complete value. .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 customarily created from them. A target is considered out-of-date if it does not exist, or if its modification time is less than that of any of its sources. An out-of-date target will be re-created, but not until all sources have been examined and themselves re-created as needed. Three operators may be used: .Bl -tag -width flag .It Ic \&: Many dependency lines may name this target but only one may have attached shell commands. All sources named in all dependency lines are considered together, and if needed the attached shell commands are run to create or re-create the target. If .Nm is interrupted, the target is removed. .It Ic \&! The same, but the target is always re-created whether or not it is out of date. .It Ic \&:: Any dependency line may have attached shell commands, but each one is handled independently: its sources are considered and the attached shell commands are run if the target is out of date with respect to (only) those sources. Thus, different groups of the attached shell commands may be run depending on the circumstances. Furthermore, unlike .Ic \&:, for dependency lines with no sources, the attached shell commands are always run. Also unlike .Ic \&:, the target will not be removed if .Nm is interrupted. .El All dependency lines mentioning a particular target must use the same operator. .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 one or more lines of shell commands, normally used to create the target. Each of the lines in this script .Em must 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 .Ql Ic \&:: operator is used, however, all rules may include scripts and the scripts are executed in the order found. .Pp 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. In compatibility (non-jobs) mode, each command is run in a separate process. If the command contains any shell meta characters .Pq Ql #=|^(){};&<>*?[]:$`\e\en it will be passed to the shell; otherwise .Nm will attempt direct execution. 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 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 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} $@) @echo Back in `pwd` ensure-one-shell-regardless-of-mode: @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, parentheses, 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" -offset indent .It Va .ALLSRC The list of all sources for this target; also known as .Ql Va \&> . .It Va .ARCHIVE 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 \&< . It is not defined in explicit rules. .It Va .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 @ . For compatibility with other makes this is an alias for .Ic .ARCHIVE in archive member rules. .El .Pp The shorter forms .Ql ( Va > , .Ql Va \&! , .Ql Va < , .Ql Va % , .Ql Va \&? , .Ql Va * , and .Ql Va @ ) are permitted for backward 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 :T modifiers. These forms are accepted for compatibility with .At V 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 . .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. If true, variable values printed with .Fl V are fully expanded; if false, the raw variable contents (which may include additional unexpanded variable references) are shown. .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 missing-filemon=bf. .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 missing-meta= Ar bf If .Va bf is True, then a missing .meta file makes the target out-of-date. .It Pa missing-filemon= Ar bf If .Va bf is True, then missing filemon data makes the target out-of-date. .It Pa nofilemon Do not use .Xr filemon 4 . .It Pa env For debugging, it can be useful to include 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.IGNORE_PATTERNS Provides a list of patterns to match against pathnames. Ignore any that match. .It Va .MAKE.META.IGNORE_FILTER Provides a list of variable modifiers to apply to each pathname. Ignore if the expansion is an empty string. .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 behavior 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.SAVE_DOLLARS value should be a boolean that controls whether .Ql $$ are preserved when doing .Ql := assignments. The default is false, for backwards compatibility. Set to true for compatability with other makes. If set to false, .Ql $$ becomes .Ql $ per normal evaluation rules. .It Va MAKE_PRINT_VAR_ON_ERROR When .Nm stops due to an error, it sets .Ql Va .ERROR_TARGET to the name of the target that failed, .Ql Va .ERROR_CMD to the commands of the failed target, and in "meta" mode, it also sets .Ql Va .ERROR_CWD to the .Xr getcwd 3 , and .Ql Va .ERROR_META_FILE to the path of the meta file (if any) describing the failed target. It then 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 via the special target .Ql Ic .OBJDIR . In all cases, .Nm will .Xr chdir 2 to the specified directory if it exists, and set .Ql Va .OBJDIR and .Ql Ev PWD to that directory before executing any targets. +.Pp +Except in the case of an explicit +.Ql Ic .OBJDIR +target, +.Nm +will check that the specified directory is writable and ignore it if not. +This check can be skipped by setting the environment variable +.Ql Ev MAKE_OBJDIR_CHECK_WRITABLE +to "no". . .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 behavior 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 .SHELL The pathname of the shell used to run target scripts. It is read-only. .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 Selects 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 normalize 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 Orders every word in variable alphabetically. .It Cm \&:Or Orders every word in variable in reverse alphabetical order. .It Cm \&:Ox Shuffles the 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 behavior. 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 to the shell. .It Cm \&:q Quotes every shell meta-character in the variable, and also doubles .Sq $ characters so that it can be passed safely through recursive invocations of .Nm . This is equivalent to: .Sq \&:S/\e\&$/&&/g:Q . .It Cm \&:R Replaces each word in the variable with everything but its suffix. .It Cm \&:range[=count] The value is an integer sequence representing the words of the original value, or the supplied .Va count . .It Cm \&:gmtime[=utc] The value is a format string for .Xr strftime 3 , using .Xr gmtime 3 . If a .Va utc value is not provided or is 0, the current time is used. .It Cm \&:hash Computes a 32-bit hash of the value and encode it as hex digits. .It Cm \&:localtime[=utc] The value is a format string for .Xr strftime 3 , using .Xr localtime 3 . If a .Va utc value is not provided or is 0, the current time is used. .It Cm \&:tA Attempts 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 Modifies the first occurrence of .Ar old_string in each word of the variable's value, replacing it with .Ar new_string . If a .Ql g is appended to the last delimiter of the pattern, all occurrences in each word are replaced. If a .Ql 1 is appended to the last delimiter of the pattern, only the first occurrence is affected. If a .Ql W is appended to the last delimiter 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 & 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). .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 path component. .It Cm \&:u Removes 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 if 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 . If only .Ar old_string contains the pattern matching character .Ar % , and .Ar old_string matches, then the result is the .Ar new_string . If only the .Ar new_string contains the pattern matching character .Ar % , then it is not treated specially and it is printed as a literal .Ar % on match. If there is more than one pattern matching character .Ar ( % ) in either the .Ar new_string or .Ar old_string , only the first instance is treated specially (as the pattern character); all subsequent instances are treated as regular characters. .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. Assigns .Ar temp to each word in the variable and evaluates .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 \&:_[=var] Saves the current variable value in .Ql $_ or the named .Va var for later reference. Example usage: .Bd -literal -offset indent M_cmpv.units = 1 1000 1000000 M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \&\\ \\* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh .Dv .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}} .Ed Here .Ql $_ is used to save the result of the .Ql :S modifier which is later referenced using the index values from .Ql :range . .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 behavior, 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. If the list is already ordered, then this effectively reverses the list, but it is more efficient to use .Ql Cm \&:Or instead of .Ql Cm \&:O:[-1..1] . .\" :[*] .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 \&< Ns Ar file Ns Cm \&> or .Cm \&.include \&\*q Ns Ar file Ns Cm \&\*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. .Pp If the include statement is written as .Cm .-include or as .Cm .sinclude then errors locating and/or opening include files are ignored. .Pp If the include statement is written as .Cm .dinclude not only are errors locating and/or opening include files ignored, but stale dependencies within the included file will be ignored just like .Va .MAKE.DEPENDFILE . .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 immediately. .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 .export-literal Ar variable ... The same as .Ql .export-env , except that variables in the value are not expanded. .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 \&&& 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 \&&& . .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 numerical 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 a numerical 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-lines .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-lines 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 .DELETE_ON_ERROR If this target is present in the makefile, it globally causes make to delete targets whose commands fail. (By default, only targets whose commands are interrupted during execution are deleted. This is the historical behavior.) This setting can be used to help prevent half-finished or malformed targets from being left around and corrupting future rebuilds. .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 .OBJDIR The source is a new value for .Ql Va .OBJDIR . If it exists, .Nm will .Xr chdir 2 to it and update the value of .Ql Va .OBJDIR . .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 built-in shell specs; .Ar sh , .Ar ksh , and .Ar csh . .It Ar path Specifies the path to the shell. .It Ar hasErrCtl Indicates whether the shell supports exit on error. .It Ar check The command to turn on error checking. .It Ar ignore The command to disable error checking. .It Ar echo The command to turn on echoing of commands executed. .It Ar quiet The command to turn off echoing of commands executed. .It Ar filter The output to filter after issuing the .Ar quiet command. It is typically identical to .Ar quiet . .It Ar errFlag The flag to pass the shell to enable error checking. .It Ar echoFlag The flag to pass the shell to enable command echoing. .It Ar newline The string literal to pass the shell that results in a single newline character when used outside of any quoting characters. .El Example: .Bd -literal \&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \e check="set \-e" ignore="set +e" \e echo="set \-v" quiet="set +v" filter="set +v" \e echoFlag=v errFlag=e newline="'\en'" .Ed .It Ic .SILENT Apply the .Ic .SILENT attribute to any specified sources. If no sources are specified, the .Ic .SILENT attribute is applied to every command in the file. .It Ic .STALE This target gets run when a dependency file contains stale entries, having .Va .ALLSRC set to the name of that dependency file. .It Ic .SUFFIXES Each source specifies a suffix to .Nm . If no sources are specified, any previously specified suffixes are deleted. It allows the creation of suffix-transformation rules. .Pp Example: .Bd -literal \&.SUFFIXES: .o \&.c.o: cc \-o ${.TARGET} \-c ${.IMPSRC} .Ed .El .Sh ENVIRONMENT .Nm uses the following environment variables, if they exist: .Ev MACHINE , .Ev MACHINE_ARCH , .Ev MAKE , .Ev MAKEFLAGS , .Ev MAKEOBJDIR , .Ev MAKEOBJDIRPREFIX , .Ev MAKESYSPATH , .Ev PWD , and .Ev TMPDIR . .Pp .Ev MAKEOBJDIRPREFIX and .Ev MAKEOBJDIR may only be set in the environment or on the command line to .Nm and not as makefile variables; see the description of .Ql Va .OBJDIR for more details. .Sh FILES .Bl -tag -width /usr/share/mk -compact .It .depend list of dependencies .It Makefile list of dependencies .It makefile list of dependencies .It sys.mk system makefile .It /usr/share/mk system makefile directory .El .Sh COMPATIBILITY The basic make syntax is compatible between different versions of make; however the special variables, variable modifiers and conditionals are not. .Ss Older versions An incomplete list of changes in older versions of .Nm : .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 on the data. For instance, finding the end of a variable's use should involve scanning each of 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 367862) +++ head/contrib/bmake/bmake.cat1 (revision 367863) @@ -1,1571 +1,1580 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) NAME bmake -- maintain program dependencies SYNOPSIS - bmake [-BeikNnqrstWwX] [-C directory] [-D variable] [-d flags] + bmake [-BeikNnqrSstWwX] [-C directory] [-D variable] [-d flags] [-f makefile] [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file] [-V variable] [-v variable] [variable=value] [target ...] DESCRIPTION bmake 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 -f makefile makefile option is given, bmake will try to open `makefile' then `Makefile' in order to find the specifications. If the file `.depend' exists, it is read (see mkdep(1)). This manual page is intended as a reference document only. For a more thorough description of bmake and makefiles, please refer to PMake - A Tutorial. bmake will prepend the contents of the MAKEFLAGS environment variable to the command line arguments before parsing them. The options are as follows: -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. -C directory Change to directory before reading the makefiles or doing any- thing else. If multiple -C options are specified, each is inter- preted relative to the previous one: -C / -C etc is equivalent to -C /etc. -D variable Define variable to be 1, in the global context. -d [-]flags Turn on debugging, and specify which portions of bmake are to print debugging information. Unless the flags are preceded by `-' they are added to the 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 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. Flags 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[+]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 `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. g1 Print the input graph before making anything. g2 Print the input graph after making everything, or before exiting on error. g3 Print the input graph before exiting on error. h Print debugging information about hash table operations. j Print debugging information about running multiple shells. L Turn on lint checks. This will throw errors for variable assignments that do not parse correctly, at the time of assignment so the file and line number are available. 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 /tmp if TMPDIR is unset or set to the empty string. The temporary scripts are created by mkstemp(3), and have names of the form makeXXXXXX. NOTE: This can create many files in TMPDIR or /tmp, 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 -V option to print raw values of variables, overriding the default behavior set via .MAKE.EXPAND_VARIABLES. v Print debugging information about variable assignment. x Run shell commands with -x so the actual commands are printed as they are executed. -e Specify that environment variables override macro assignments within makefiles. -f makefile Specify a makefile to read instead of the default `makefile'. If makefile is `-', standard input is read. Multiple makefiles may be specified, and are read in the order specified. -I directory Specify a directory in which to search for makefiles and included makefiles. The system makefile directory (or directories, see the -m option) is automatically included as part of this list. -i Ignore non-zero exit of shell commands in the makefile. Equiva- lent to specifying `-' before each command line in the makefile. -J private This option should not 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. -j max_jobs Specify the maximum number of jobs that bmake may have running at any one time. The value is saved in .MAKE.JOBS. 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. -k Continue processing after errors are encountered, but only on those targets that do not depend on the target whose creation caused the error. -m directory Specify a directory in which to search for sys.mk and makefiles included via the <file>-style include statement. The -m 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 "file"-style include statements (see the -I option). If a file or directory name in the -m argument (or the MAKESYSPATH environment variable) starts with the string ".../" then bmake 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 file system. If the search is success- ful, then the resulting directory replaces the ".../" specifica- tion in the -m argument. If used, this feature allows bmake to easily search in the current source tree for customized sys.mk files (e.g., by using ".../mk/sys.mk" as an argument). -n 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) or the command is prefixed with `+'. -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. -q Do not execute any commands, but exit 0 if the specified targets are up-to-date and 1, otherwise. -r Do not use the built-in rules specified in the system makefile. + -S Stop processing if an error is encountered. This is the default + behavior and the opposite of -k. + -s Do not echo any commands as they are executed. Equivalent to specifying `@' before each command line in the makefile. -T tracefile When used with the -j flag, append a trace record to tracefile for each job started and completed. -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. -V variable Print the value of variable. 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 unde- fined variable. The value printed is extracted from the global context after all makefiles have been read. By default, the raw variable contents (which may include additional unexpanded vari- able references) are shown. If variable contains a `$' then the value will be recursively expanded to its complete resultant text before printing. The expanded value will also be printed if .MAKE.EXPAND_VARIABLES is set to true and the -dV option has not been used to override it. Note that loop-local and target-local variables, as well as values taken temporarily by global vari- ables during makefile processing, are not accessible via this option. The -dv debug mode can be used to see these at the cost of generating substantial extraneous output. -v variable Like -V but the variable is always expanded to its complete value. -W Treat any warnings during makefile parsing as errors. -w Print entering and leaving directory messages, pre and post pro- cessing. -X 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 MAKEFLAGS environment variable. This option may be useful on systems which have a small limit on the size of command arguments. variable=value Set the value of the variable variable to value. Normally, all values passed on the command line are also exported to sub-makes in the environment. The -X 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. 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 ``depend'' on the sources and are customarily created from them. A target is con- sidered out-of-date if it does not exist, or if its modification time is less than that of any of its sources. An out-of-date target will be re- created, but not until all sources have been examined and themselves re- created as needed. Three operators may be used: : Many dependency lines may name this target but only one may have attached shell commands. All sources named in all dependency lines are considered together, and if needed the attached shell commands are run to create or re-create the target. If bmake is inter- rupted, the target is removed. ! The same, but the target is always re-created whether or not it is out of date. :: Any dependency line may have attached shell commands, but each one is handled independently: its sources are considered and the attached shell commands are run if the target is out of date with respect to (only) those sources. Thus, different groups of the attached shell commands may be run depending on the circumstances. Furthermore, unlike :, for dependency lines with no sources, the attached shell commands are always run. Also unlike :, the target will not be removed if bmake is interrupted. All dependency lines mentioning a particular target must use the same operator. 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. SHELL COMMANDS 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 must 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. 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 -n 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 bmake is run in jobs mode with -j max_jobs, the entire script for 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 bmake 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. Makefiles should be written so that the mode of bmake operation does not change their behavior. For example, any command which needs to use ``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} $@) @echo Back in `pwd` ensure-one-shell-regardless-of-mode: @echo Building $@ in `pwd`; \ (cd ${.CURDIR} && ${MAKE} $@); \ echo Back in `pwd` Since bmake will chdir(2) to `.OBJDIR' before executing any targets, each child process starts with that as its current working directory. VARIABLE ASSIGNMENTS Variables in make are much like variables in the shell, and, by tradi- tion, consist of all upper-case letters. Variable assignment modifiers 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. NOTE: References to undefined variables are not 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 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. 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, parentheses, 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''. Variable classes The four different classes of variables (in order of increasing prece- dence) are: Environment variables Variables defined as part of bmake'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. 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: .ALLSRC The list of all sources for this target; also known as `>'. .ARCHIVE The name of the archive file; also known as `!'. .IMPSRC 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. .MEMBER The name of the archive member; also known as `%'. .OODATE The list of sources for this target that were deemed out- of-date; also known as `?'. .PREFIX 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 .SUFFIXES or it will not be recog- nized. .TARGET The name of the target; also known as `@'. For compati- bility with other makes this is an alias for .ARCHIVE in archive member rules. 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 `.TARGET', `.PREFIX', `.ARCHIVE', and `.MEMBER'. Additional built-in variables In addition, bmake sets or knows about the following variables: $ A single dollar sign `$', i.e. `$$' expands to a single dollar sign. .ALLTARGETS The list of all targets encountered in the Makefile. If evaluated during Makefile parsing, lists only those tar- gets encountered thus far. .CURDIR A path to the directory where bmake was executed. Refer to the description of `PWD' for more details. .INCLUDEDFROMDIR The directory of the file this Makefile was included from. .INCLUDEDFROMFILE The filename of the file this Makefile was included from. MAKE The name that bmake was executed with (argv[0]). For compatibility bmake also sets .MAKE with the same value. The preferred variable to use is the environment variable MAKE because it is more compatible with other versions of bmake and cannot be confused with the special target with the same name. .MAKE.DEPENDFILE Names the makefile (default `.depend') from which gener- ated dependencies are read. .MAKE.EXPAND_VARIABLES A boolean that controls the default behavior of the -V option. If true, variable values printed with -V are fully expanded; if false, the raw variable contents (which may include additional unexpanded variable refer- ences) are shown. .MAKE.EXPORTED The list of variables exported by bmake. .MAKE.JOBS The argument to the -j option. .MAKE.JOB.PREFIX If bmake is run with j then output for each target is prefixed with a token `--- target ---' the first part of which can be controlled via .MAKE.JOB.PREFIX. If .MAKE.JOB.PREFIX 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 bmake's command line. Anything specified on bmake's command line is appended to the `MAKEFLAGS' variable which is then entered into the envi- ronment for all programs which bmake executes. .MAKE.LEVEL The recursion depth of bmake. The initial instance of bmake 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 bmake. .MAKE.MAKEFILE_PREFERENCE The ordered list of makefile names (default `makefile', `Makefile') that bmake will look for. .MAKE.MAKEFILES The list of makefiles read by bmake, which is useful for tracking dependencies. Each makefile is recorded only once, regardless of the number of times read. .MAKE.MODE Processed after reading all makefiles. Can affect the mode that bmake runs in. It can contain a number of key- words: compat Like -B, puts bmake into "compat" mode. meta Puts bmake into "meta" mode, where meta files are created for each tar- get to capture the command run, the output generated and if filemon(4) is available, the system calls which are of interest to bmake. The cap- tured output can be very useful when diagnosing errors. curdirOk= bf Normally bmake will not create .meta files in `.CURDIR'. This can be overridden by setting bf to a value which represents True. missing-meta= bf If bf is True, then a missing .meta file makes the target out-of-date. missing-filemon= bf If bf is True, then missing filemon data makes the target out-of-date. nofilemon Do not use filemon(4). env For debugging, it can be useful to include the environment in the .meta file. 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: .MAKE.META.PREFIX. ignore-cmd Some makefiles have commands which are simply not stable. This keyword causes them to be ignored for deter- mining whether a target is out of date in "meta" mode. See also .NOMETA_CMP. silent= bf If bf is True, when a .meta file is created, mark the target .SILENT. .MAKE.META.BAILIWICK In "meta" mode, provides a list of prefixes which match the directories controlled by bmake. If a file that was generated outside of .OBJDIR but within said bailiwick is missing, the current target is considered out-of-date. .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 .MAKE.META.FILES. .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 informa- tion. .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: `/dev /etc /proc /tmp /var/run /var/tmp' .MAKE.META.IGNORE_PATTERNS Provides a list of patterns to match against pathnames. Ignore any that match. .MAKE.META.IGNORE_FILTER Provides a list of variable modifiers to apply to each pathname. Ignore if the expansion is an empty string. .MAKE.META.PREFIX Defines the message printed for each meta file updated in "meta verbose" mode. The default value is: Building ${.TARGET:H:tA}/${.TARGET:T} .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 `MAKEFLAGS'. This behavior can be disabled by assigning an empty value to `.MAKEOVERRIDES' within a makefile. Extra variables can be exported from a makefile by appending their names to `.MAKEOVERRIDES'. `MAKEFLAGS' is re-exported whenever `.MAKEOVERRIDES' is modified. .MAKE.PATH_FILEMON If bmake was built with filemon(4) support, this is set to the path of the device node. This allows makefiles to test for this support. .MAKE.PID The process-id of bmake. .MAKE.PPID The parent process-id of bmake. .MAKE.SAVE_DOLLARS value should be a boolean that controls whether `$$' are preserved when doing `:=' assignments. The default is false, for backwards compatibility. Set to true for com- patability with other makes. If set to false, `$$' becomes `$' per normal evaluation rules. MAKE_PRINT_VAR_ON_ERROR When bmake stops due to an error, it sets `.ERROR_TARGET' to the name of the target that failed, `.ERROR_CMD' to the commands of the failed target, and in "meta" mode, it also sets `.ERROR_CWD' to the getcwd(3), and `.ERROR_META_FILE' to the path of the meta file (if any) describing the failed target. It then prints its name and the value of `.CURDIR' as well as the value of any variables named in `MAKE_PRINT_VAR_ON_ERROR'. .newline 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 `MAKE_PRINT_VAR_ON_ERROR' could be done as ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@}. .OBJDIR 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}/obj.${MACHINE} 4. ${.CURDIR}/obj 5. /usr/obj/${.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'. `.OBJDIR' may be modified in the makefile via the special target `.OBJDIR'. In all cases, bmake will chdir(2) to the specified directory if it exists, and set `.OBJDIR' and `PWD' to that directory before executing any targets. + Except in the case of an explicit `.OBJDIR' target, bmake + will check that the specified directory is writable and + ignore it if not. This check can be skipped by setting + the environment variable `MAKE_OBJDIR_CHECK_WRITABLE' to + "no". + .PARSEDIR A path to the directory of the current `Makefile' being parsed. .PARSEFILE The basename of the current `Makefile' being parsed. This variable and `.PARSEDIR' are both set only while the `Makefiles' are being parsed. If you want to retain their current values, assign them to a variable using assignment with expansion: (`:='). .PATH A variable that represents the list of directories that bmake will search for files. The search list should be updated using the target `.PATH' rather than the vari- able. PWD Alternate path to the current directory. bmake normally sets `.CURDIR' 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 bmake sets `.CURDIR' to the value of `PWD' instead. This behavior is disabled if `MAKEOBJDIRPREFIX' is set or `MAKEOBJDIR' contains a variable transform. `PWD' is set to the value of `.OBJDIR' for all programs which bmake executes. .SHELL The pathname of the shell used to run target scripts. It is read-only. .TARGETS The list of targets explicitly specified on the command line, if any. VPATH Colon-separated (``:'') lists of directories that bmake will search for files. The variable is supported for compatibility with old make programs only, use `.PATH' instead. Variable modifiers 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: :E Replaces each word in the variable with its suffix. :H Replaces each word in the variable with everything but the last com- ponent. :Mpattern Selects only those words that match pattern. 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 normalize the inter-word spacing, removing all leading and trailing space, and converting multiple consecutive spaces to single spaces. :Npattern This is identical to `:M', but selects all words which do not match pattern. :O Orders every word in variable alphabetically. :Or Orders every word in variable in reverse alphabetical order. :Ox Shuffles the 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 behavior. 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 :Q Quotes every shell meta-character in the variable, so that it can be passed safely to the shell. :q Quotes every shell meta-character in the variable, and also doubles `$' characters so that it can be passed safely through recursive invocations of bmake. This is equivalent to: `:S/\$/&&/g:Q'. :R Replaces each word in the variable with everything but its suffix. :range[=count] The value is an integer sequence representing the words of the orig- inal value, or the supplied count. :gmtime[=utc] The value is a format string for strftime(3), using gmtime(3). If a utc value is not provided or is 0, the current time is used. :hash Computes a 32-bit hash of the value and encode it as hex digits. :localtime[=utc] The value is a format string for strftime(3), using localtime(3). If a utc value is not provided or is 0, the current time is used. :tA Attempts to convert variable to an absolute path using realpath(3), if that fails, the value is unchanged. :tl Converts variable to lower-case letters. :tsc 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. :tu Converts variable to upper-case letters. :tW Causes the value to be treated as a single word (possibly containing embedded white space). See also `:[*]'. :tw Causes the value to be treated as a sequence of words delimited by white space. See also `:[@]'. :S/old_string/new_string/[1gW] Modifies the first occurrence of old_string in each word of the variable's value, replacing it with new_string. If a `g' is appended to the last delimiter of the pattern, all occurrences in each word are replaced. If a `1' is appended to the last delimiter of the pattern, only the first occurrence is affected. If a `W' is appended to the last delimiter of the pattern, then the value is treated as a single word (possibly containing embedded white space). If old_string begins with a caret (`^'), old_string is anchored at the beginning of each word. If old_string ends with a dollar sign (`$'), it is anchored at the end of each word. Inside new_string, an ampersand (`&') is replaced by old_string (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 old_string and new_string 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. :C/pattern/replacement/[1gW] The :C modifier is just like the :S modifier except that the old and new strings, instead of being simple strings, are an extended regu- lar expression (see regex(3)) string pattern and an ed(1)-style string replacement. Normally, the first occurrence of the pattern pattern in each word of the value is substituted with replacement. 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 pattern 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). As for the :S modifier, the pattern and replacement are subjected to variable expansion before being parsed as regular expressions. :T Replaces each word in the variable with its last path component. :u Removes adjacent duplicate words (like uniq(1)). :?true_string:false_string If the variable name (not its value), when parsed as a .if condi- tional expression, evaluates to true, return as its value the true_string, otherwise return the 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 ${NUMBERS:M42:?match:no} which actually tests defined(NUMBERS), to determine if any words match "42" you need to use something like: ${"${NUMBERS:M42}" != "":?match:no}. :old_string=new_string This is the AT&T System V UNIX style variable substitution. It must be the last modifier specified. If old_string or new_string 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 old_string to be replaced in new_string. If only old_string con- tains the pattern matching character %, and old_string matches, then the result is the new_string. If only the new_string contains the pattern matching character %, then it is not treated specially and it is printed as a literal % on match. If there is more than one pattern matching character (%) in either the new_string or old_string, only the first instance is treated specially (as the pattern character); all subsequent instances are treated as regular characters. Variable expansion occurs in the normal fashion inside both old_string and new_string 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. :@temp@string@ This is the loop expansion mechanism from the OSF Development Envi- ronment (ODE) make. Unlike .for loops, expansion occurs at the time of reference. Assigns temp to each word in the variable and evalu- ates string. The ODE convention is that temp 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}@} :_[=var] Saves the current variable value in `$_' or the named var for later reference. Example usage: M_cmpv.units = 1 1000 1000000 M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \ \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}} Here `$_' is used to save the result of the `:S' modifier which is later referenced using the index values from `:range'. :Unewval If the variable is undefined, 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: ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}} If a value is only required if the variable is undefined, use: ${VAR:D:Unewval} :Dnewval If the variable is defined, newval is the value. :L The name of the variable is the value. :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. :!cmd! The output of running cmd is the value. :sh If the variable is non-empty it is run as a command and the output becomes the new value. ::=str The variable is assigned the value 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 bmake 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. ::?=str As for ::= but only if the variable does not already have a value. ::+=str Append str to the variable. ::!=cmd Assign the output of cmd to the variable. :[range] 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 behavior, 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 inte- gers (where index 1 represents the first word), and backwards using negative integers (where index -1 represents the last word). The range is subjected to variable expansion, and the expanded result is then interpreted as follows: index Selects a single word from the value. start..end Selects all words from start to end, inclusive. For example, `:[2..-1]' selects all words from the second word to the last word. If start is greater than end, then the words are out- put in reverse order. For example, `:[-1..1]' selects all the words from last to first. If the list is already ordered, then this effectively reverses the list, but it is more efficient to use `:Or' instead of `:O:[-1..1]'. * 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. INCLUDE STATEMENTS, CONDITIONALS AND FOR LOOPS Makefile inclusion, conditional structures and for loops reminiscent of the C programming language are provided in bmake. All such structures are identified by a line beginning with a single dot (`.') character. Files are included with either .include <file> or .include "file". 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 -I option are searched before the system makefile directory. For compatibility with other versions of bmake `include file ...' is also accepted. If the include statement is written as .-include or as .sinclude then errors locating and/or opening include files are ignored. If the include statement is written as .dinclude not only are errors locating and/or opening include files ignored, but stale dependencies within the included file will be ignored just like .MAKE.DEPENDFILE. Conditional expressions are also preceded by a single dot as the first character of a line. The possible conditionals are as follows: .error message The message is printed along with the name of the makefile and line number, then bmake will exit immediately. .export variable ... 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 -X flag, so should be used with caution. For compatibility with other bmake programs `export variable=value' is also accepted. Appending a variable name to .MAKE.EXPORTED is equivalent to exporting a variable. .export-env variable ... The same as `.export', except that the variable is not appended to .MAKE.EXPORTED. This allows exporting a value to the environ- ment which is different from that used by bmake internally. .export-literal variable ... The same as `.export-env', except that variables in the value are not expanded. .info message The message is printed along with the name of the makefile and line number. .undef variable Un-define the specified global variable. Only global variables may be un-defined. .unexport variable ... The opposite of `.export'. The specified global variable will be removed from .MAKE.EXPORTED. If no variable list is provided, all globals are unexported, and .MAKE.EXPORTED deleted. .unexport-env 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 .MAKE.LEVEL 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. .warning message The message prefixed by `warning:' is printed along with the name of the makefile and line number. .if [!]expression [operator expression ...] Test the value of an expression. .ifdef [!]variable [operator variable ...] Test the value of a variable. .ifndef [!]variable [operator variable ...] Test the value of a variable. .ifmake [!]target [operator target ...] Test the target being built. .ifnmake [!] target [operator target ...] Test the target being built. .else Reverse the sense of the last conditional. .elif [!] expression [operator expression ...] A combination of `.else' followed by `.if'. .elifdef [!]variable [operator variable ...] A combination of `.else' followed by `.ifdef'. .elifndef [!]variable [operator variable ...] A combination of `.else' followed by `.ifndef'. .elifmake [!]target [operator target ...] A combination of `.else' followed by `.ifmake'. .elifnmake [!]target [operator target ...] A combination of `.else' followed by `.ifnmake'. .endif End the body of the conditional. The operator may be any one of the following: || Logical OR. && Logical AND; of higher precedence than ``||''. As in C, bmake 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 expression may be any of the following: defined Takes a variable name as an argument and evaluates to true if the variable has been defined. make Takes a target name as an argument and evaluates to true if the target was specified as part of bmake's command line or was declared the default target (either implicitly or explicitly, see .MAIN) before the line containing the conditional. empty Takes a variable, with possible modifiers, and evaluates to true if the expansion of the variable would result in an empty string. 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 .PATH). target Takes a target name as an argument and evaluates to true if the target has been defined. commands Takes a target name as an argument and evaluates to true if the target has been defined and has commands associated with it. Expression may also be an arithmetic or string comparison. Variable expansion is performed on both sides of the comparison, after which the numerical 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 a numerical 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 bmake 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 `.ifdef', `.ifndef', or `.if' the ``defined'' expression is applied. Similarly, if the form is `.ifmake' or `.ifnmake', the ``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 `.else' or `.endif' 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: .for variable [variable ...] in expression .endfor After the for expression is evaluated, it is split into words. On each iteration of the loop, one word is taken and assigned to each variable, in order, and these variables are substituted into the make-lines 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. COMMENTS Comments begin with a hash (`#') character, anywhere but in a shell com- mand line, and continue to the end of an unescaped new line. SPECIAL SOURCES (ATTRIBUTES) .EXEC Target is never out of date, but always execute commands any- way. .IGNORE Ignore any errors from the commands associated with this tar- get, exactly as if they all were preceded by a dash (`-'). .MADE Mark all sources of this target as being up-to-date. .MAKE Execute the commands associated with this target even if the -n or -t options were specified. Normally used to mark recursive bmakes. .META Create a meta file for the target, even if it is flagged as .PHONY, .MAKE, or .SPECIAL. Usage in conjunction with .MAKE is the most likely case. In "meta" mode, the target is out-of- date if the meta file is missing. .NOMETA Do not create a meta file for the target. Meta files are also not created for .PHONY, .MAKE, or .SPECIAL targets. .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 .OODATE, 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 :M pattern suppresses any expansion of the unwanted vari- able. .NOPATH Do not search for the target in the directories specified by .PATH. .NOTMAIN Normally bmake 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. .OPTIONAL If a target is marked with this attribute and bmake can't fig- ure out how to create it, it will ignore this fact and assume the file isn't needed or already exists. .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 -t option. Suffix-transformation rules are not applied to .PHONY targets. .PRECIOUS When bmake is interrupted, it normally removes any partially made targets. This source prevents the target from being removed. .RECURSIVE Synonym for .MAKE. .SILENT Do not echo any of the commands associated with this target, exactly as if they all were preceded by an at sign (`@'). .USE Turn the target into bmake'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 .USE) of the source. If the target already has commands, the .USE target's commands are appended to them. .USEBEFORE Exactly like .USE, but prepend the .USEBEFORE target commands to the target. .WAIT If .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 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 .WAIT is only relevant for parallel makes. SPECIAL TARGETS Special targets may not be included with other targets, i.e. they must be the only target specified. .BEGIN Any command lines attached to this target are executed before anything else is done. .DEFAULT This is sort of a .USE rule for any target (that was used only as a source) that bmake can't figure out any other way to cre- ate. Only the shell script is used. The .IMPSRC variable of a target that inherits .DEFAULT's commands is set to the target's own name. .DELETE_ON_ERROR If this target is present in the makefile, it globally causes make to delete targets whose commands fail. (By default, only targets whose commands are interrupted during execution are deleted. This is the historical behavior.) This setting can be used to help prevent half-finished or malformed targets from being left around and corrupting future rebuilds. .END Any command lines attached to this target are executed after everything else is done. .ERROR Any command lines attached to this target are executed when another target fails. The .ERROR_TARGET variable is set to the target that failed. See also MAKE_PRINT_VAR_ON_ERROR. .IGNORE Mark each of the sources with the .IGNORE attribute. If no sources are specified, this is the equivalent of specifying the -i option. .INTERRUPT If bmake is interrupted, the commands for this target will be executed. .MAIN If no target is specified when bmake is invoked, this target will be built. .MAKEFLAGS This target provides a way to specify flags for bmake when the makefile is used. The flags are as if typed to the shell, though the -f option will have no effect. .NOPATH Apply the .NOPATH attribute to any specified sources. .NOTPARALLEL Disable parallel mode. .NO_PARALLEL Synonym for .NOTPARALLEL, for compatibility with other pmake variants. .OBJDIR The source is a new value for `.OBJDIR'. If it exists, bmake will chdir(2) to it and update the value of `.OBJDIR'. .ORDER 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 .ORDER is only relevant for parallel makes. .PATH 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 .DOTLAST target, then the current working directory is searched last. .PATH.suffix Like .PATH but applies only to files with a particular suffix. The suffix must have been previously declared with .SUFFIXES. .PHONY Apply the .PHONY attribute to any specified sources. .PRECIOUS Apply the .PRECIOUS attribute to any specified sources. If no sources are specified, the .PRECIOUS attribute is applied to every target in the file. .SHELL Sets the shell that bmake will use to execute commands. The sources are a set of field=value pairs. name This is the minimal specification, used to select one of the built-in shell specs; sh, ksh, and csh. path Specifies the path to the shell. hasErrCtl Indicates whether the shell supports exit on error. check The command to turn on error checking. ignore The command to disable error checking. echo The command to turn on echoing of commands executed. quiet The command to turn off echoing of commands exe- cuted. filter The output to filter after issuing the quiet com- mand. It is typically identical to quiet. errFlag The flag to pass the shell to enable error checking. echoFlag The flag to pass the shell to enable command echo- ing. newline 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'" .SILENT Apply the .SILENT attribute to any specified sources. If no sources are specified, the .SILENT attribute is applied to every command in the file. .STALE This target gets run when a dependency file contains stale entries, having .ALLSRC set to the name of that dependency file. .SUFFIXES Each source specifies a suffix to bmake. 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} ENVIRONMENT bmake 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 bmake and not as makefile variables; see the descrip- tion of `.OBJDIR' for more details. FILES .depend list of dependencies Makefile list of dependencies makefile list of dependencies sys.mk system makefile /usr/share/mk system makefile directory COMPATIBILITY The basic make syntax is compatible between different versions of make; however the special variables, variable modifiers and conditionals are not. Older versions An incomplete list of changes in older versions of bmake: 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. Other make dialects Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not sup- port most of the features of bmake as described in this manual. Most notably: +o The .WAIT and .ORDER declarations and most functionality per- taining to parallelization. (GNU make supports parallelization but lacks these features needed to control it effectively.) +o 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.) +o All built-in variables that begin with a dot. +o Most of the special sources and targets that begin with a dot, with the notable exception of .PHONY, .PRECIOUS, and .SUFFIXES. +o Variable modifiers, except for the :old=new string substitution, which does not portably support globbing with `%' and historically only works on declared suffixes. +o 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 .PATH functionality is based on an older feature 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. The $@ and $< variables are more or less universally portable, as is the $(MAKE) 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. SEE ALSO mkdep(1) HISTORY bmake 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). BUGS The make syntax is difficult to parse without actually acting on the data. For instance, finding the end of a variable's use should involve scanning each of 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. -FreeBSD 11.3 November 1, 2020 FreeBSD 11.3 +FreeBSD 11.3 November 14, 2020 FreeBSD 11.3 Index: head/contrib/bmake/boot-strap =================================================================== --- head/contrib/bmake/boot-strap (revision 367862) +++ head/contrib/bmake/boot-strap (revision 367863) @@ -1,483 +1,487 @@ : # NAME: # boot-strap # # SYNOPSIS: # boot-strap ["options"] # boot-strap --prefix=/opt --install # boot-strap --prefix=$HOME --install-host-target -DWITH_PROG_VERSION # boot-strap ["options"] op=build # boot-strap ["options"] op=install # # DESCRIPTION: # This script is used to configure/build bmake it builds for # each host-target in a different subdir to keep the src clean. # There is no requirement for an existing make(1). # # On successful completion if no '--install' flag is given, # it echos a command to do installation. # # The variable "op" defaults to 'all', and is affected by # '--install' flag as above. # Other values include: # # configure # Just run 'configure' # # build # If 'configure' has not been done, do it, then # run the build script, and finally 'test'. # # install # If 'build' has not been done, do it, 'test' then # install. # # clean # attempt to clean up # # test # run the unit-tests. Done automatically after 'build' # and before 'install'. # # The above are leveraged by a trivial makefile for the benefit # of those that have './configure; make; make install' baked # into them. # # Options: # # -c "rc" # Pick up settings from "rc". # We look for '.bmake-boot-strap.rc' before processing # options (unless SKIP_RC is set in environment). # # --share "share_dir" # Where to put man pages and mk files. # If $prefix ends in $HOST_TARGET, and $prefix/../share # exits, the default will be that rather than $prefix/share. # # --mksrc "mksrc" # Indicate where the mk files can be found. # Default is $Mydir/mk # # --install # If build and test work, run bmake install. # BINDIR=$prefix/bin # SHAREDIR=$prefix/share # # --install-host-target # As for '--install' but BINDIR=$prefix/$HOST_TARGET/bin # This is useful when $prefix/ is shared by multiple # machines. # # Flags relevant when installing: # # -DWITHOUT_INSTALL_MK # Skip installing mk files. # By default they will be installed to $prefix/share/mk # # -DWITH_PROG_VERSION # Install 'bmake' as 'bmake-$MAKE_VERSION' # A symlink will be made as 'bmake' unless # -DWITHOUT_PROG_LINK is set. # # Possibly useful configure_args: # # --without-meta # disable use of meta mode. # # --without-filemon # disable use of filemon(9) which is currently only # available for NetBSD and FreeBSD. # # --with-filemon=ktrace # on NetBSD or others with fktrace(2), use ktrace # version of filemon. # # --with-filemon="path/to/filemon.h" # enables use of filemon(9) by meta mode. # # --with-machine="machine" # set "machine" to override that determined by # machine.sh # # --with-force-machine="machine" # force "machine" even if uname(3) provides a value. # # --with-machine_arch="machine_arch" # set "machine_arch" to override that determined by # machine.sh # +# --with-force_machine_arch="machine_arch" +# force "machine_arch" to override that determined by +# machine.sh +# # --with-default-sys-path="syspath" # set an explicit default "syspath" which is where bmake # will look for sys.mk and friends. # # AUTHOR: # Simon J. Gerraty # RCSid: -# $Id: boot-strap,v 1.53 2020/09/16 02:12:01 sjg Exp $ +# $Id: boot-strap,v 1.54 2020/11/13 21:47:25 sjg Exp $ # # @(#) Copyright (c) 2001 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 # Mydir=`dirname $0` . "$Mydir/os.sh" case "$Mydir" in /*) ;; *) Mydir=`cd "$Mydir" && 'pwd'`;; esac Usage() { [ "$1" ] && echo "ERROR: $@" >&2 echo "Usage:" >&2 echo "$0 [-- ...][][--install]" >&2 exit 1 } Error() { echo "ERROR: $@" >&2 exit 1 } source_rc() { rc="$1"; shift for d in ${*:-""} do r="${d:+$d/}$rc" [ -f "$r" -a -s "$r" ] || continue echo "NOTE: reading $r" . "$r" break done } cmd_args="$@" # clear some things from the environment that we care about unset MAKEOBJDIR MAKEOBJDIRPREFIX # or that might be incompatible unset MAKE MAKEFLAGS # --install[-host-target] will set this INSTALL_PREFIX= # other things we pass to install step INSTALL_ARGS= CONFIGURE_ARGS= MAKESYSPATH= # pick a useful default prefix (for me at least ;-) for prefix in /opt/$HOST_TARGET "$HOME/$HOST_TARGET" /usr/pkg /usr/local "" do [ -d "${prefix:-.}" ] || continue case "$prefix" in */$HOST_TARGET) p=`dirname $prefix` if [ -d $p/share ]; then INSTALL_BIN=$HOST_TARGET/bin prefix=$p fi ;; esac echo "NOTE: default prefix=$prefix ${INSTALL_BIN:+INSTALL_BIN=$INSTALL_BIN}" break done srcdir=$Mydir mksrc=$Mydir/mk objdir= quiet=: ${SKIP_RC:+:} source_rc .bmake-boot-strap.rc . "$Mydir/.." "$HOME" get_optarg() { expr "x$1" : "x[^=]*=\\(.*\\)" } here=`'pwd'` if [ $here = $Mydir ]; then # avoid pollution OBJROOT=../ fi op=all BMAKE= while : do case "$1" in --) shift; break;; --help) sed -n -e "1d;/RCSid/,\$d" -e '/^#\.[a-z]/d' -e '/^#/s,^# *,,p' $0; exit 0;; --prefix) prefix="$2"; shift;; --prefix=*) prefix=`get_optarg "$1"`;; --src=*) srcdir=`get_optarg "$1"`;; --with-mksrc=*|--mksrc=*) mksrc=`get_optarg "$1"`;; --share=*) share_dir=`get_optarg "$1"`;; --share) share_dir="$2"; shift;; --with-default-sys-path=*) CONFIGURE_ARGS="$1";; --with-default-sys-path) CONFIGURE_ARGS="$1 $2";; --install) INSTALL_PREFIX=${INSTALL_PREFIX:-$prefix};; --install-host-target) INSTALL_PREFIX=${INSTALL_PREFIX:-$prefix} INSTALL_BIN=$HOST_TARGET/bin;; --install-destdir=*) INSTALL_DESTDIR=`get_optarg "$1"`;; --install-prefix=*) INSTALL_PREFIX=`get_optarg "$1"`;; -DWITH*) INSTALL_ARGS="$INSTALL_ARGS $1";; -s|--src) srcdir="$2"; shift;; -m|--mksrc) mksrc="$2"; shift;; -o|--objdir) objdir="$2"; shift;; -q) quiet=;; -c) source_rc "$2"; shift;; --*) CONFIGURE_ARGS="$CONFIGURE_ARGS $1";; *=*) eval "$1"; export `expr "x$1" : "x\\(.[^=]*\\)=.*"`;; *) break;; esac shift done AddConfigure() { case " $CONFIGURE_ARGS " in *" $1"*) ;; *) CONFIGURE_ARGS="$CONFIGURE_ARGS $1$2";; esac } GetDir() { match="$1" shift fmatch="$1" shift for dir in $* do [ -d "$dir" ] || continue case "/$dir/" in *$match*) ;; *) continue;; esac case "$fmatch" in .) ;; *) [ -s $dir/$fmatch ] || continue;; esac case "$dir/" in *./*) cd "$dir" && 'pwd';; /*) echo $dir;; *) cd "$dir" && 'pwd';; esac break done } FindHereOrAbove() { ( _t=-s while : do case "$1" in -C) cd "$2"; shift; shift;; -?) _t=$1; shift;; *) break;; esac done case "$1" in /*) # we shouldn't be here [ $_t "$1" ] && echo "$1" return ;; .../*) want=`echo "$1" | sed 's,^.../*,,'`;; *) want="$1";; esac here=`'pwd'` while : do if [ $_t "./$want" ]; then echo "$here/$want" return fi cd .. here=`'pwd'` case "$here" in /) return;; esac done ) } # is $1 missing from $2 (or PATH) ? no_path() { eval "__p=\$${2:-PATH}" case ":$__p:" in *:"$1":*) return 1;; *) return 0;; esac } # if $1 exists and is not in path, append it add_path () { case "$1" in -?) t=$1; shift;; *) t=-d;; esac case "$2,$1" in MAKESYSPATH,.../*) ;; *) [ $t ${1:-.} ] || return;; esac no_path $* && eval ${2:-PATH}="$__p${__p:+:}$1" } srcdir=`GetDir /bmake make-bootstrap.sh.in "$srcdir" "$2" "$Mydir" ./bmake* "$Mydir"/../bmake*` [ -d "${srcdir:-/dev/null}" ] || Usage case "$mksrc" in none|-) # we ignore this now mksrc=$Mydir/mk ;; .../*) # find here or above mksrc=`FindHereOrAbove -C "$Mydir" -s "$mksrc/sys.mk"` # that found a file mksrc=`dirname $mksrc` ;; *) # guess we want mksrc... mksrc=`GetDir /mk sys.mk "$mksrc" "$3" ./mk* "$srcdir"/mk* "$srcdir"/../mk*` [ -d "${mksrc:-/dev/null}" ] || Usage "Use '-m none' to build without mksrc" ;; esac # Ok, get to work... objdir="${objdir:-$OBJROOT$HOST_TARGET}" [ -d "$objdir" ] || mkdir -p "$objdir" [ -d "$objdir" ] || mkdir "$objdir" cd "$objdir" || exit 1 # make it absolute objdir=`'pwd'` ShareDir() { case "/$1" in /) [ -d /share ] || return;; */$HOST_TARGET) if [ -d "$1/../share" ]; then echo `dirname "$1"`/share return fi ;; esac echo $1/share } # make it easy to force prefix to use $HOST_TARGET : looking at "$prefix" case "$prefix" in */host?target) prefix=`echo "$prefix" | sed "s,host.target,${HOST_TARGET},"`;; esac share_dir="${share_dir:-`ShareDir $prefix`}" AddConfigure --prefix= "$prefix" case "$CONFIGURE_ARGS" in *--with-*-sys-path*) ;; # skip *) [ "$share_dir" ] && AddConfigure --with-default-sys-path= "$share_dir/mk";; esac if [ "$mksrc" ]; then AddConfigure --with-mksrc= "$mksrc" # not all cc's support this CFLAGS_MF= CFLAGS_MD= export CFLAGS_MF CFLAGS_MD fi # this makes it easy to run the bmake we just built # the :tA dance is needed because 'pwd' and even /bin/pwd # may not give the same result as realpath(). Bmake() { ( cd $Mydir && MAKESYSPATH=$mksrc SRCTOP=$Mydir OBJTOP=$objdir \ MAKEOBJDIR='${.CURDIR:S,${SRCTOP:tA},${OBJTOP:tA},}' \ ${BMAKE:-$objdir/bmake} -f $Mydir/Makefile "$@" ) } # there is actually a shell where type is not a builtin # if type is missing, which(1) had better exists! if (type cat) > /dev/null 2>&1; then which() { type "$@" | sed 's,[()],,g;s,^[^/][^/]*,,;q' } fi # make sure test below uses the same diff that configure did TOOL_DIFF=`which diff` export TOOL_DIFF op_configure() { $srcdir/configure $CONFIGURE_ARGS || exit 1 } op_build() { [ -s make-bootstrap.sh ] || op_configure chmod 755 make-bootstrap.sh || exit 1 ./make-bootstrap.sh || exit 1 case "$op" in build) op_test;; esac } op_test() { [ -x bmake ] || op_build Bmake test || exit 1 } op_clean() { if [ -x bmake ]; then ln bmake bmake$$ BMAKE=$objdir/bmake$$ Bmake clean rm -f bmake$$ elif [ $objdir != $srcdir ]; then rm -rf * fi } op_install() { op_test case "$INSTALL_PREFIX,$INSTALL_BIN,$prefix" in ,$HOST_TARGET/bin,*/$HOST_TARGET) INSTALL_PREFIX=`dirname $prefix` ;; esac INSTALL_PREFIX=${INSTALL_PREFIX:-$prefix} Bmake install prefix=$INSTALL_PREFIX BINDIR=$INSTALL_PREFIX/${INSTALL_BIN:-bin} ${INSTALL_DESTDIR:+DESTDIR=$INSTALL_DESTDIR} $INSTALL_ARGS || exit 1 } op_all() { rm -f make-bootstrap.sh bmake *.o if [ -n "$INSTALL_PREFIX" ]; then op_install else op_test MAKE_VERSION=`sed -n '/^_MAKE_VERSION/ { s,.*= *,,;p; }' $srcdir/Makefile` cat << EOM You can install by running: $0 $cmd_args op=install Use --install-prefix=/something to install somewhere other than $prefix Use --install-destdir=/somewhere to set DESTDIR during install Use --install-host-target to use INSTALL_BIN=$HOST_TARGET/bin Use -DWITH_PROG_VERSION to install as bmake-$MAKE_VERSION Use -DWITHOUT_PROG_LINK to suppress bmake -> bmake-$MAKE_VERSION symlink Use -DWITHOUT_INSTALL_MK to skip installing files to $prefix/share/mk EOM fi cat << EOM Note: bmake.cat1 contains ANSI escape sequences. You may need the -r or -R option to more/less to view it correctly. EOM } op_$op exit 0 Index: head/contrib/bmake/buf.c =================================================================== --- head/contrib/bmake/buf.c (revision 367862) +++ head/contrib/bmake/buf.c (revision 367863) @@ -1,211 +1,214 @@ -/* $NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $ */ +/* $NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig 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. */ /* Automatically-expanding null-terminated buffers. */ #include #include "make.h" /* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $"); +MAKE_RCSID("$NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig Exp $"); /* Make space in the buffer for adding a single byte. */ void Buf_Expand_1(Buffer *buf) { buf->cap += buf->cap > 16 ? buf->cap : 16; buf->data = bmake_realloc(buf->data, buf->cap); } /* Add the bytes to the buffer. */ void Buf_AddBytes(Buffer *buf, const char *bytes, size_t bytes_len) { size_t old_len = buf->len; char *end; if (__predict_false(old_len + bytes_len >= buf->cap)) { buf->cap += buf->cap > bytes_len + 16 ? buf->cap : bytes_len + 16; buf->data = bmake_realloc(buf->data, buf->cap); } end = buf->data + old_len; buf->len = old_len + bytes_len; memcpy(end, bytes, bytes_len); end[bytes_len] = '\0'; } /* Add the bytes between start and end to the buffer. */ void Buf_AddBytesBetween(Buffer *buf, const char *start, const char *end) { Buf_AddBytes(buf, start, (size_t)(end - start)); } /* Add the string to the buffer. */ void Buf_AddStr(Buffer *buf, const char *str) { Buf_AddBytes(buf, str, strlen(str)); } /* Add the number to the buffer. */ void Buf_AddInt(Buffer *buf, int n) { enum { bits = sizeof(int) * CHAR_BIT, max_octal_digits = (bits + 2) / 3, max_decimal_digits = /* at most */ max_octal_digits, max_sign_chars = 1, str_size = max_sign_chars + max_decimal_digits + 1 }; char str[str_size]; size_t len = (size_t)snprintf(str, sizeof str, "%d", n); Buf_AddBytes(buf, str, len); } /* Get the data (usually a string) from the buffer. * The returned data is valid until the next modifying operation * on the buffer. * * Returns the data and optionally the length of the data. */ char * Buf_GetAll(Buffer *buf, size_t *out_len) { if (out_len != NULL) *out_len = buf->len; return buf->data; } /* Mark the buffer as empty, so it can be filled with data again. */ void Buf_Empty(Buffer *buf) { buf->len = 0; buf->data[0] = '\0'; } -/* Initialize a buffer. - * If the given initial capacity is 0, a reasonable default is used. */ +/* Initialize a buffer. */ void -Buf_Init(Buffer *buf, size_t cap) +Buf_InitSize(Buffer *buf, size_t cap) { - if (cap <= 0) - cap = 256; buf->cap = cap; buf->len = 0; buf->data = bmake_malloc(cap); buf->data[0] = '\0'; +} + +void +Buf_Init(Buffer *buf) +{ + Buf_InitSize(buf, 256); } /* Reset the buffer. * If freeData is TRUE, the data from the buffer is freed as well. * Otherwise it is kept and returned. */ char * Buf_Destroy(Buffer *buf, Boolean freeData) { char *data = buf->data; if (freeData) { free(data); data = NULL; } buf->cap = 0; buf->len = 0; buf->data = NULL; return data; } #ifndef BUF_COMPACT_LIMIT # define BUF_COMPACT_LIMIT 128 /* worthwhile saving */ #endif /* Reset the buffer and return its data. * * If the buffer size is much greater than its content, * a new buffer will be allocated and the old one freed. */ char * Buf_DestroyCompact(Buffer *buf) { #if BUF_COMPACT_LIMIT > 0 if (buf->cap - buf->len >= BUF_COMPACT_LIMIT) { /* We trust realloc to be smart */ char *data = bmake_realloc(buf->data, buf->len + 1); data[buf->len] = '\0'; /* XXX: unnecessary */ Buf_Destroy(buf, FALSE); return data; } #endif return Buf_Destroy(buf, FALSE); } Index: head/contrib/bmake/buf.h =================================================================== --- head/contrib/bmake/buf.h (revision 367862) +++ head/contrib/bmake/buf.h (revision 367863) @@ -1,131 +1,132 @@ -/* $NetBSD: buf.h,v 1.34 2020/09/27 16:59:02 rillig Exp $ */ +/* $NetBSD: buf.h,v 1.36 2020/11/10 00:32:12 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * * 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: @(#)buf.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: @(#)buf.h 8.1 (Berkeley) 6/6/93 */ /* Automatically growing null-terminated buffers of characters. */ #ifndef MAKE_BUF_H #define MAKE_BUF_H #include /* An automatically growing null-terminated buffer of characters. */ typedef struct Buffer { size_t cap; /* Allocated size of the buffer, including the null */ size_t len; /* Number of bytes in buffer, excluding the null */ char *data; /* The buffer itself (always null-terminated) */ } Buffer; /* If we aren't on NetBSD, __predict_false() might not be defined. */ #ifndef __predict_false #define __predict_false(x) (x) #endif void Buf_Expand_1(Buffer *); /* Buf_AddByte adds a single byte to a buffer. */ -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void Buf_AddByte(Buffer *buf, char byte) { size_t old_len = buf->len++; char *end; if (__predict_false(old_len + 1 >= buf->cap)) Buf_Expand_1(buf); end = buf->data + old_len; end[0] = byte; end[1] = '\0'; } -static inline MAKE_ATTR_UNUSED size_t +MAKE_INLINE size_t Buf_Len(const Buffer *buf) { return buf->len; } -static inline MAKE_ATTR_UNUSED Boolean +MAKE_INLINE Boolean Buf_EndsWith(const Buffer *buf, char ch) { return buf->len > 0 && buf->data[buf->len - 1] == ch; } void Buf_AddBytes(Buffer *, const char *, size_t); void Buf_AddBytesBetween(Buffer *, const char *, const char *); void Buf_AddStr(Buffer *, const char *); void Buf_AddInt(Buffer *, int); char *Buf_GetAll(Buffer *, size_t *); void Buf_Empty(Buffer *); -void Buf_Init(Buffer *, size_t); +void Buf_Init(Buffer *); +void Buf_InitSize(Buffer *, size_t); char *Buf_Destroy(Buffer *, Boolean); char *Buf_DestroyCompact(Buffer *); #endif /* MAKE_BUF_H */ Index: head/contrib/bmake/compat.c =================================================================== --- head/contrib/bmake/compat.c (revision 367862) +++ head/contrib/bmake/compat.c (revision 367863) @@ -1,709 +1,698 @@ -/* $NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $ */ +/* $NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig 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. */ /*- * 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 "make.h" #include "dir.h" #include "job.h" #include "metachar.h" #include "pathnames.h" /* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $"); +MAKE_RCSID("$NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $"); static GNode *curTarg = NULL; static pid_t compatChild; static int compatSigno; /* - * CompatDeleteTarget -- delete a failed, interrupted, or otherwise - * duffed target if not inhibited by .PRECIOUS. + * CompatDeleteTarget -- delete the file of a failed, interrupted, or + * otherwise duffed target if not inhibited by .PRECIOUS. */ static void CompatDeleteTarget(GNode *gn) { if (gn != NULL && !Targ_Precious(gn)) { const char *file = GNode_VarTarget(gn); if (!opts.noExecute && eunlink(file) != -1) { Error("*** %s removed", file); } } } /* Interrupt the creation of the current target and remove it if it ain't * precious. Then exit. * * If .INTERRUPT exists, its commands are run first WITH INTERRUPTS IGNORED. * * XXX: is .PRECIOUS supposed to inhibit .INTERRUPT? I doubt it, but I've * left the logic alone for now. - dholland 20160826 */ static void CompatInterrupt(int signo) { - GNode *gn; - CompatDeleteTarget(curTarg); if (curTarg != NULL && !Targ_Precious(curTarg)) { /* * Run .INTERRUPT only if hit with interrupt signal */ if (signo == SIGINT) { - gn = Targ_FindNode(".INTERRUPT"); + GNode *gn = Targ_FindNode(".INTERRUPT"); if (gn != NULL) { Compat_Make(gn, gn); } } } if (signo == SIGQUIT) _exit(signo); /* * If there is a child running, pass the signal on. * We will exist after it has exited. */ compatSigno = signo; if (compatChild > 0) { KILLPG(compatChild, signo); } else { bmake_signal(signo, SIG_DFL); kill(myPid, signo); } } /* 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. */ int Compat_RunCommand(const char *cmdp, GNode *gn) { char *cmdStart; /* Start of expanded command */ char *bp; Boolean silent; /* Don't print command */ Boolean 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 */ StringListNode *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 */ Boolean useShell; /* TRUE if command should be executed * using a shell */ const char *volatile cmd = cmdp; silent = (gn->type & OP_SILENT) != 0; errCheck = !(gn->type & OP_IGNORE); doIt = FALSE; /* Luckily the commands don't end up in a string pool, otherwise * this comparison could match too early, in a dependency using "..." * for delayed commands, run in parallel mode, using the same shell * command line more than once; see JobPrintCommand. * TODO: write a unit-test to protect against this potential bug. */ cmdNode = Lst_FindDatum(gn->commands, cmd); (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart); /* TODO: handle errors */ - if (*cmdStart == '\0') { + if (cmdStart[0] == '\0') { free(cmdStart); return 0; } cmd = cmdStart; LstNode_Set(cmdNode, cmdStart); if (gn->type & OP_SAVE_CMDS) { GNode *endNode = Targ_GetEndNode(); if (gn != endNode) { Lst_Append(endNode->commands, cmdStart); return 0; } } if (strcmp(cmdStart, "...") == 0) { gn->type |= OP_SAVE_CMDS; return 0; } - while (*cmd == '@' || *cmd == '-' || *cmd == '+') { - switch (*cmd) { - case '@': + for (;;) { + if (*cmd == '@') silent = !DEBUG(LOUD); - break; - case '-': + else if (*cmd == '-') errCheck = FALSE; - break; - case '+': + else if (*cmd == '+') { doIt = TRUE; - if (!shellName) /* we came here from jobs */ + if (!shellName) /* we came here from jobs */ Shell_Init(); + } else break; - } cmd++; } while (ch_isspace(*cmd)) cmd++; /* * If we did not end up with a command, just skip it. */ - if (!*cmd) + if (cmd[0] == '\0') return 0; #if !defined(MAKE_NATIVE) /* * In a non-native build, the host environment might be weird enough * that it's necessary to go through a shell to get the correct * behaviour. Or perhaps the shell has been replaced with something * that does extra logging, and that should not be bypassed. */ useShell = TRUE; #else /* * Search for meta characters in the command. If there are no meta * characters, there's no need to execute a shell to execute the * command. * * Additionally variable assignments and empty commands * go to the shell. Therefore treat '=' and ':' like shell * meta characters as documented in make(1). */ useShell = needshell(cmd); #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 || !GNode_ShouldExecute(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 && !GNode_ShouldExecute(gn)) { + if (!doIt && !GNode_ShouldExecute(gn)) return 0; - } + DEBUG1(JOB, "Execute: '%s'\n", cmd); 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; + /* The following work for any of the builtin shell specs. */ + int shargc = 0; shargv[shargc++] = shellPath; - /* - * The following work for any of the builtin shell specs. - */ - if (errCheck && shellErrFlag) { + if (errCheck && shellErrFlag) shargv[shargc++] = shellErrFlag; - } - if (DEBUG(SHELL)) - shargv[shargc++] = "-xc"; - else - shargv[shargc++] = "-c"; + shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c"; shargv[shargc++] = cmd; shargv[shargc] = NULL; av = shargv; 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. */ Words words = Str_Words(cmd, FALSE); mav = words.words; bp = words.freeIt; av = (void *)mav; } #ifdef USE_META if (useMeta) { meta_compat_start(); } #endif /* * Fork and execute the single command. If the fork fails, we abort. */ compatChild = cpid = vFork(); if (cpid < 0) { Fatal("Could not fork"); } if (cpid == 0) { Var_ExportVars(); #ifdef USE_META if (useMeta) { meta_compat_child(); } #endif (void)execvp(av[0], (char *const *)UNCONST(av)); execDie("exec", av[0]); } free(mav); free(bp); /* XXX: Memory management looks suspicious here. */ /* XXX: Setting a list item to NULL is unexpected. */ LstNode_SetNull(cmdNode); #ifdef USE_META if (useMeta) { meta_compat_parent(cpid); } #endif /* * The child is off and running. Now all we can do is wait... */ while ((retstat = wait(&reason)) != cpid) { if (retstat > 0) JobReapChild(retstat, reason, FALSE); /* not ours? */ if (retstat == -1 && errno != EINTR) { break; } } if (retstat < 0) Fatal("error in wait: %d: %s", retstat, strerror(errno)); 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)) { - const char *cp; + const char *p = cmd; debug_printf("\n*** Failed target: %s\n*** Failed command: ", gn->name); - for (cp = cmd; *cp; ) { - if (ch_isspace(*cp)) { + + /* Replace runs of whitespace with a single space, to reduce + * the amount of whitespace for multi-line command lines. */ + while (*p != '\0') { + if (ch_isspace(*p)) { debug_printf(" "); - while (ch_isspace(*cp)) - cp++; + cpp_skip_whitespace(&p); } else { - debug_printf("%c", *cp); - cp++; + debug_printf("%c", *p); + p++; } } debug_printf("\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 (opts.keepgoing) { /* Abort the current target, but let others continue. */ printf(" (continuing)\n"); } else { printf("\n"); } if (deleteOnError) CompatDeleteTarget(gn); } else { /* * Continue executing commands for this target. * If we return 0, this will happen... */ printf(" (ignored)\n"); status = 0; } } free(cmdStart); compatChild = 0; if (compatSigno) { bmake_signal(compatSigno, SIG_DFL); kill(myPid, compatSigno); } return status; } static void RunCommands(GNode *gn) { StringListNode *ln; for (ln = gn->commands->first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; if (Compat_RunCommand(cmd, gn) != 0) break; } } static void MakeNodes(GNodeList *gnodes, GNode *pgn) { GNodeListNode *ln; for (ln = gnodes->first; ln != NULL; ln = ln->next) { GNode *cohort = ln->datum; Compat_Make(cohort, pgn); } } /* Make a target. * * If an error is detected and not being ignored, the process exits. * * Input: * gn The node to make * pgn Parent to abort if necessary */ void Compat_Make(GNode *gn, GNode *pgn) { - if (!shellName) /* we came here from jobs */ + if (shellName == NULL) /* we came here from jobs */ Shell_Init(); + if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) { /* * 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. + * but the -k flag was given, our 'make' field will be set to 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)) Suff_FindDeps(gn); MakeNodes(gn->children, gn); if (!(gn->flags & REMAKE)) { gn->made = ABORTED; pgn->flags &= ~(unsigned)REMAKE; goto cohorts; } if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) Var_Set(IMPSRC, GNode_VarTarget(gn), pgn); /* * All the children were made ok. Now youngestChild->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. + * are defined by GNode_IsOODate. */ DEBUG1(MAKE, "Examining %s...", gn->name); - if (!Make_OODate(gn)) { + if (!GNode_IsOODate(gn)) { gn->made = UPTODATE; DEBUG0(MAKE, "up-to-date.\n"); goto cohorts; } else DEBUG0(MAKE, "out-of-date.\n"); /* * If the user is just seeing if something is out-of-date, exit now * to tell him/her "yes". */ - if (opts.queryFlag) { + if (opts.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 (!opts.touchFlag || (gn->type & OP_MAKE)) { curTarg = gn; #ifdef USE_META if (useMeta && GNode_ShouldExecute(gn)) { meta_job_start(NULL, gn); } #endif RunCommands(gn); curTarg = NULL; } else { Job_Touch(gn, (gn->type & OP_SILENT) != 0); } } else { gn->made = ERROR; } #ifdef USE_META if (useMeta && GNode_ShouldExecute(gn)) { if (meta_job_finish(NULL) != 0) gn->made = ERROR; } #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. + * its modification time and timestamp all its parents. * This is to keep its state from affecting that of its parent. */ gn->made = MADE; - pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0; + if (Make_Recheck(gn) == 0) + pgn->flags |= FORCE; if (!(gn->type & OP_EXEC)) { pgn->flags |= CHILDMADE; - Make_TimeStamp(pgn, gn); + GNode_UpdateYoungestChild(pgn, gn); } } else if (opts.keepgoing) { pgn->flags &= ~(unsigned)REMAKE; } else { PrintOnError(gn, "\nStop."); exit(1); } } else if (gn->made == ERROR) { /* Already had an error when making this. Tell the parent to abort. */ pgn->flags &= ~(unsigned)REMAKE; } else { if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) { const char *target = GNode_VarTarget(gn); Var_Set(IMPSRC, target != NULL ? target : "", pgn); } switch(gn->made) { case BEINGMADE: Error("Graph cycles through %s", gn->name); gn->made = ERROR; pgn->flags &= ~(unsigned)REMAKE; break; case MADE: - if ((gn->type & OP_EXEC) == 0) { + if (!(gn->type & OP_EXEC)) { pgn->flags |= CHILDMADE; - Make_TimeStamp(pgn, gn); + GNode_UpdateYoungestChild(pgn, gn); } break; case UPTODATE: - if ((gn->type & OP_EXEC) == 0) { - Make_TimeStamp(pgn, gn); - } + if (!(gn->type & OP_EXEC)) + GNode_UpdateYoungestChild(pgn, gn); break; default: break; } } cohorts: MakeNodes(gn->cohorts, pgn); } /* Initialize this module and start making. * * Input: * targs The target nodes to re-create */ void Compat_Run(GNodeList *targs) { GNode *gn = NULL; /* Current root target */ int errors; /* Number of targets not remade due to errors */ if (!shellName) Shell_Init(); if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) bmake_signal(SIGINT, CompatInterrupt); if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) bmake_signal(SIGTERM, CompatInterrupt); if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) bmake_signal(SIGHUP, CompatInterrupt); if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) bmake_signal(SIGQUIT, CompatInterrupt); /* Create the .END node now, to keep the (debug) output of the * counter.mk test the same as before 2020-09-23. This implementation * detail probably doesn't matter though. */ (void)Targ_GetEndNode(); /* * If the user has defined a .BEGIN target, execute the commands attached * to it. */ if (!opts.queryFlag) { gn = Targ_FindNode(".BEGIN"); if (gn != NULL) { Compat_Make(gn, gn); if (gn->made == ERROR) { PrintOnError(gn, "\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 = 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++; } } /* * If the user has defined a .END target, run its commands. */ if (errors == 0) { GNode *endNode = Targ_GetEndNode(); Compat_Make(endNode, endNode); /* XXX: Did you mean endNode->made instead of gn->made? */ if (gn->made == ERROR) { PrintOnError(gn, "\nStop."); exit(1); } } } Index: head/contrib/bmake/cond.c =================================================================== --- head/contrib/bmake/cond.c (revision 367862) +++ head/contrib/bmake/cond.c (revision 367863) @@ -1,1234 +1,1281 @@ -/* $NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig 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. */ /* Handling of conditionals in a makefile. * * Interface: - * Cond_EvalLine Evaluate the conditional. + * Cond_EvalLine Evaluate the conditional directive, such as + * '.if ', '.elifnmake ', '.else', '.endif'. * * Cond_EvalCondition * Evaluate the conditional, which is either the argument * of one of the .if directives or the condition in a * ':?then:else' variable modifier. * * Cond_save_depth * Cond_restore_depth * Save and restore the nesting of the conditions, at * the start and end of including another makefile, to * ensure that in each makefile the conditional * directives are well-balanced. */ #include #include "make.h" #include "dir.h" /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $"); /* * The parsing of conditional expressions is based on this grammar: * E -> F || E * E -> F * F -> T && F * F -> T * T -> defined(variable) * T -> make(target) * T -> exists(file) * T -> empty(varspec) * T -> target(name) * T -> commands(name) * T -> symbol * T -> $(varspec) op value * T -> $(varspec) == "string" * T -> $(varspec) != "string" * T -> "string" * T -> ( E ) * T -> ! T * op -> == | != | > | < | >= | <= * * 'symbol' is some other symbol to which the default function is applied. * * The tokens are scanned by CondToken, which returns: * TOK_AND for '&' or '&&' * TOK_OR for '|' or '||' * TOK_NOT for '!' * TOK_LPAREN for '(' * TOK_RPAREN for ')' * Other terminal symbols are evaluated using either the default function or * the function given in the terminal, they return either TOK_TRUE or * TOK_FALSE. * * TOK_FALSE is 0 and TOK_TRUE 1 so we can directly assign C comparisons. * * All non-terminal functions (CondParser_Expr, CondParser_Factor and * CondParser_Term) return either TOK_FALSE, TOK_TRUE, or TOK_ERROR on error. */ typedef enum Token { TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT, TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR } Token; typedef struct CondParser { const struct If *if_info; /* Info for current statement */ const char *p; /* The remaining condition to parse */ Token curr; /* Single push-back token used in parsing */ /* Whether an error message has already been printed for this condition. * The first available error message is usually the most specific one, * therefore it makes sense to suppress the standard "Malformed * conditional" message. */ Boolean printedError; } CondParser; static Token CondParser_Expr(CondParser *par, Boolean); static unsigned int cond_depth = 0; /* current .if nesting level */ static unsigned int cond_min_depth = 0; /* depth at makefile open */ /* * Indicate when we should be strict about lhs of comparisons. * In strict mode, the lhs must be a variable expression or a string literal * in quotes. In non-strict mode it may also be an unquoted string literal. * * TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) * FALSE when CondEvalExpression is called from ApplyModifier_IfElse - * since lhs is already expanded and we cannot tell if + * since lhs is already expanded, and at that point we cannot tell if * it was a variable reference or not. */ static Boolean lhsStrict; static int is_token(const char *str, const char *tok, size_t len) { return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); } +static Token +ToToken(Boolean cond) +{ + return cond ? TOK_TRUE : TOK_FALSE; +} + /* Push back the most recent token read. We only need one level of this. */ static void CondParser_PushBack(CondParser *par, Token t) { assert(par->curr == TOK_NONE); assert(t != TOK_NONE); par->curr = t; } static void CondParser_SkipWhitespace(CondParser *par) { cpp_skip_whitespace(&par->p); } /* Parse the argument of a built-in function. * * Arguments: * *pp initially points at the '(', * upon successful return it points right after the ')'. * * *out_arg receives the argument as string. * * func says whether the argument belongs to an actual function, or * whether the parsed argument is passed to the default function. * - * Return the length of the argument. */ + * Return the length of the argument, or 0 on error. */ static size_t ParseFuncArg(const char **pp, Boolean doEval, const char *func, char **out_arg) { const char *p = *pp; Buffer argBuf; int paren_depth; size_t argLen; if (func != NULL) p++; /* Skip opening '(' - verified by caller */ if (*p == '\0') { - /* - * No arguments whatsoever. Because 'make' and 'defined' aren't really - * "reserved words", we don't print a message. I think this is better - * than hitting the user with a warning message every time s/he uses - * the word 'make' or 'defined' at the beginning of a symbol... - */ - *out_arg = NULL; - return 0; + *out_arg = NULL; /* Missing closing parenthesis: */ + return 0; /* .if defined( */ } - while (*p == ' ' || *p == '\t') { - p++; - } + cpp_skip_hspace(&p); - Buf_Init(&argBuf, 16); + Buf_InitSize(&argBuf, 16); paren_depth = 0; for (;;) { char ch = *p; - if (ch == 0 || ch == ' ' || ch == '\t') + if (ch == '\0' || ch == ' ' || ch == '\t') break; if ((ch == '&' || ch == '|') && paren_depth == 0) break; if (*p == '$') { /* * Parse the variable spec and install it as part of the argument * if it's valid. We tell Var_Parse to complain on an undefined * variable, so we don't need to do it. Nor do we return an error, * though perhaps we should... */ void *nestedVal_freeIt; - VarEvalFlags eflags = VARE_UNDEFERR | (doEval ? VARE_WANTRES : 0); + VarEvalFlags eflags = doEval ? VARE_WANTRES | VARE_UNDEFERR + : VARE_NONE; const char *nestedVal; (void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal, &nestedVal_freeIt); /* TODO: handle errors */ Buf_AddStr(&argBuf, nestedVal); free(nestedVal_freeIt); continue; } if (ch == '(') paren_depth++; else if (ch == ')' && --paren_depth < 0) break; Buf_AddByte(&argBuf, *p); p++; } *out_arg = Buf_GetAll(&argBuf, &argLen); Buf_Destroy(&argBuf, FALSE); - while (*p == ' ' || *p == '\t') { - p++; - } + cpp_skip_hspace(&p); if (func != NULL && *p++ != ')') { Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", func); /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ return 0; } *pp = p; return argLen; } /* Test whether the given variable is defined. */ static Boolean FuncDefined(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { void *freeIt; Boolean result = Var_Value(arg, VAR_CMDLINE, &freeIt) != NULL; bmake_free(freeIt); return result; } /* See if the given target is being made. */ static Boolean FuncMake(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { StringListNode *ln; for (ln = opts.create->first; ln != NULL; ln = ln->next) if (Str_Match(ln->datum, arg)) return TRUE; return FALSE; } /* See if the given file exists. */ static Boolean FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { Boolean result; char *path; path = Dir_FindFile(arg, dirSearchPath); - DEBUG2(COND, "exists(%s) result is \"%s\"\n", arg, path ? path : ""); - if (path != NULL) { - result = TRUE; - free(path); - } else { - result = FALSE; - } + DEBUG2(COND, "exists(%s) result is \"%s\"\n", + arg, path != NULL ? path : ""); + result = path != NULL; + free(path); return result; } /* See if the given node exists and is an actual target. */ static Boolean FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { GNode *gn = Targ_FindNode(arg); return gn != NULL && GNode_IsTarget(gn); } /* See if the given node exists and is an actual target with commands * associated with it. */ static Boolean FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { GNode *gn = Targ_FindNode(arg); return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands); } -/*- +/* * Convert the given number into a double. * We try a base 10 or 16 integer conversion first, if that fails * then we try a floating point conversion instead. * * Results: - * Sets 'value' to double value of string. * Returns TRUE if the conversion succeeded. + * Sets 'out_value' to the converted number. */ static Boolean -TryParseNumber(const char *str, double *value) +TryParseNumber(const char *str, double *out_value) { - char *eptr, ech; - unsigned long l_val; - double d_val; + char *end; + unsigned long ul_val; + double dbl_val; errno = 0; - if (!*str) { - *value = 0.0; + if (str[0] == '\0') { /* XXX: why is an empty string a number? */ + *out_value = 0.0; return TRUE; } - l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10); - ech = *eptr; - if (ech == '\0' && errno != ERANGE) { - d_val = str[0] == '-' ? -(double)-l_val : (double)l_val; - } else { - if (ech != '\0' && ech != '.' && ech != 'e' && ech != 'E') - return FALSE; - d_val = strtod(str, &eptr); - if (*eptr) - return FALSE; + + ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); + if (*end == '\0' && errno != ERANGE) { + *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; + return TRUE; } - *value = d_val; + if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') + return FALSE; /* skip the expensive strtod call */ + dbl_val = strtod(str, &end); + if (*end != '\0') + return FALSE; + + *out_value = dbl_val; return TRUE; } static Boolean is_separator(char ch) { return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL; } /*- * Parse a string from a variable reference or an optionally quoted * string. This is called for the lhs and rhs of string comparisons. * * Results: * Returns the string, absent any quotes, or NULL on error. - * Sets quoted if the string was quoted. - * Sets freeIt if needed. + * Sets out_quoted if the string was quoted. + * Sets out_freeIt. */ /* coverity:[+alloc : arg-*4] */ static const char * CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, - Boolean *quoted, void **freeIt) + Boolean *out_quoted, void **out_freeIt) { Buffer buf; const char *str; Boolean atStart; const char *nested_p; - Boolean qt; + Boolean quoted; const char *start; VarEvalFlags eflags; VarParseResult parseResult; - Buf_Init(&buf, 0); + Buf_Init(&buf); str = NULL; - *freeIt = NULL; - *quoted = qt = par->p[0] == '"' ? 1 : 0; + *out_freeIt = NULL; + *out_quoted = quoted = par->p[0] == '"'; start = par->p; - if (qt) + if (quoted) par->p++; - while (par->p[0] && str == NULL) { + while (par->p[0] != '\0' && str == NULL) { switch (par->p[0]) { case '\\': par->p++; if (par->p[0] != '\0') { Buf_AddByte(&buf, par->p[0]); par->p++; } continue; case '"': - if (qt) { - par->p++; /* we don't want the quotes */ + if (quoted) { + par->p++; /* skip the closing quote */ goto got_str; } Buf_AddByte(&buf, par->p[0]); /* likely? */ par->p++; continue; - case ')': + case ')': /* see is_separator */ case '!': case '=': case '>': case '<': case ' ': case '\t': - if (!qt) + if (!quoted) goto got_str; Buf_AddByte(&buf, par->p[0]); par->p++; continue; case '$': /* if we are in quotes, an undefined variable is ok */ - eflags = ((!qt && doEval) ? VARE_UNDEFERR : 0) | - (doEval ? VARE_WANTRES : 0); + eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR : + doEval ? VARE_WANTRES : + VARE_NONE; + nested_p = par->p; atStart = nested_p == start; parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str, - freeIt); + out_freeIt); /* TODO: handle errors */ if (str == var_Error) { if (parseResult & VPR_ANY_MSG) par->printedError = TRUE; - if (*freeIt) { - free(*freeIt); - *freeIt = NULL; + if (*out_freeIt != NULL) { + /* XXX: Can there be any situation in which a returned + * var_Error requires freeIt? */ + free(*out_freeIt); + *out_freeIt = NULL; } /* * Even if !doEval, we still report syntax errors, which * is what getting var_Error back with !doEval means. */ str = NULL; goto cleanup; } par->p = nested_p; /* * If the '$' started the string literal (which means no quotes), * and the variable expression is followed by a space, looks like * a comparison operator or is the end of the expression, we are * done. */ if (atStart && is_separator(par->p[0])) goto cleanup; Buf_AddStr(&buf, str); - if (*freeIt) { - free(*freeIt); - *freeIt = NULL; + if (*out_freeIt) { + free(*out_freeIt); + *out_freeIt = NULL; } str = NULL; /* not finished yet */ continue; default: - if (strictLHS && !qt && *start != '$' && !ch_isdigit(*start)) { + if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) { /* lhs must be quoted, a variable reference or number */ - if (*freeIt) { - free(*freeIt); - *freeIt = NULL; - } str = NULL; goto cleanup; } Buf_AddByte(&buf, par->p[0]); par->p++; continue; } } got_str: - *freeIt = Buf_GetAll(&buf, NULL); - str = *freeIt; + *out_freeIt = Buf_GetAll(&buf, NULL); + str = *out_freeIt; cleanup: Buf_Destroy(&buf, FALSE); return str; } -/* The different forms of .if directives. */ -static const struct If { +struct If { const char *form; /* Form of if */ size_t formlen; /* Length of form */ Boolean doNot; /* TRUE if default function should be negated */ Boolean (*defProc)(size_t, const char *); /* Default function to apply */ -} ifs[] = { +}; + +/* The different forms of .if directives. */ +static const struct If ifs[] = { { "def", 3, FALSE, FuncDefined }, { "ndef", 4, TRUE, FuncDefined }, { "make", 4, FALSE, FuncMake }, { "nmake", 5, TRUE, FuncMake }, { "", 0, FALSE, FuncDefined }, { NULL, 0, FALSE, NULL } }; +enum { PLAIN_IF_INDEX = 4 }; +static Boolean +If_Eval(const struct If *if_info, const char *arg, size_t arglen) +{ + Boolean res = if_info->defProc(arglen, arg); + return if_info->doNot ? !res : res; +} + /* Evaluate a "comparison without operator", such as in ".if ${VAR}" or * ".if 0". */ -static Token -EvalNotEmpty(CondParser *par, const char *lhs, Boolean lhsQuoted) +static Boolean +EvalNotEmpty(CondParser *par, const char *value, Boolean quoted) { - double left; + double num; - /* For .ifxxx "..." check for non-empty string. */ - if (lhsQuoted) - return lhs[0] != '\0'; + /* For .ifxxx "...", check for non-empty string. */ + if (quoted) + return value[0] != '\0'; - /* For .ifxxx compare against zero */ - if (TryParseNumber(lhs, &left)) - return left != 0.0; + /* For .ifxxx , compare against zero */ + if (TryParseNumber(value, &num)) + return num != 0.0; - /* For .if ${...} check for non-empty string (defProc is ifdef). */ + /* For .if ${...}, check for non-empty string. This is different from + * the evaluation function from that .if variant, which would test + * whether a variable of the given name were defined. */ + /* XXX: Whitespace should count as empty, just as in ParseEmptyArg. */ if (par->if_info->form[0] == '\0') - return lhs[0] != 0; + return value[0] != '\0'; - /* Otherwise action default test ... */ - return par->if_info->defProc(strlen(lhs), lhs) == !par->if_info->doNot; + /* For the other variants of .ifxxx ${...}, use its default function. */ + return If_Eval(par->if_info, value, strlen(value)); } /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ static Token EvalCompareNum(double lhs, const char *op, double rhs) { DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, op); switch (op[0]) { case '!': if (op[1] != '=') { Parse_Error(PARSE_WARNING, "Unknown operator"); /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ return TOK_ERROR; } - return lhs != rhs; + return ToToken(lhs != rhs); case '=': if (op[1] != '=') { Parse_Error(PARSE_WARNING, "Unknown operator"); /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ return TOK_ERROR; } - return lhs == rhs; + return ToToken(lhs == rhs); case '<': - return op[1] == '=' ? lhs <= rhs : lhs < rhs; + return ToToken(op[1] == '=' ? lhs <= rhs : lhs < rhs); case '>': - return op[1] == '=' ? lhs >= rhs : lhs > rhs; + return ToToken(op[1] == '=' ? lhs >= rhs : lhs > rhs); } return TOK_ERROR; } static Token EvalCompareStr(const char *lhs, const char *op, const char *rhs) { if (!((op[0] == '!' || op[0] == '=') && op[1] == '=')) { Parse_Error(PARSE_WARNING, "String comparison operator must be either == or !="); /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ return TOK_ERROR; } DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op); - return (*op == '=') == (strcmp(lhs, rhs) == 0); + return ToToken((*op == '=') == (strcmp(lhs, rhs) == 0)); } /* Evaluate a comparison, such as "${VAR} == 12345". */ static Token EvalCompare(const char *lhs, Boolean lhsQuoted, const char *op, const char *rhs, Boolean rhsQuoted) { double left, right; if (!rhsQuoted && !lhsQuoted) if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) return EvalCompareNum(left, op, right); return EvalCompareStr(lhs, op, rhs); } /* Parse a comparison condition such as: * * 0 * ${VAR:Mpattern} * ${VAR} == value * ${VAR:U0} < 12345 */ static Token CondParser_Comparison(CondParser *par, Boolean doEval) { Token t = TOK_ERROR; const char *lhs, *op, *rhs; - void *lhsFree, *rhsFree; + void *lhs_freeIt, *rhs_freeIt; Boolean lhsQuoted, rhsQuoted; - rhs = NULL; - lhsFree = rhsFree = NULL; - lhsQuoted = rhsQuoted = FALSE; - /* * Parse the variable spec and skip over it, saving its * value in lhs. */ - lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhsFree); - if (!lhs) - goto done; + lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhs_freeIt); + if (lhs == NULL) + goto done_lhs; CondParser_SkipWhitespace(par); - /* - * Make sure the operator is a valid one. If it isn't a - * known relational operator, pretend we got a - * != 0 comparison. - */ op = par->p; switch (par->p[0]) { case '!': case '=': case '<': case '>': - if (par->p[1] == '=') { + if (par->p[1] == '=') par->p += 2; - } else { + else par->p++; - } break; default: - t = doEval ? EvalNotEmpty(par, lhs, lhsQuoted) : TOK_FALSE; - goto done; + /* Unknown operator, compare against an empty string or 0. */ + t = ToToken(doEval && EvalNotEmpty(par, lhs, lhsQuoted)); + goto done_lhs; } CondParser_SkipWhitespace(par); if (par->p[0] == '\0') { Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator"); /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ - goto done; + goto done_lhs; } - rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhsFree); + rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhs_freeIt); if (rhs == NULL) - goto done; + goto done_rhs; if (!doEval) { t = TOK_FALSE; - goto done; + goto done_rhs; } t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted); -done: - free(lhsFree); - free(rhsFree); +done_rhs: + free(rhs_freeIt); +done_lhs: + free(lhs_freeIt); return t; } +/* The argument to empty() is a variable name, optionally followed by + * variable modifiers. */ static size_t -ParseEmptyArg(const char **linePtr, Boolean doEval, - const char *func MAKE_ATTR_UNUSED, char **argPtr) +ParseEmptyArg(const char **pp, Boolean doEval, + const char *func MAKE_ATTR_UNUSED, char **out_arg) { void *val_freeIt; const char *val; size_t magic_res; /* We do all the work here and return the result as the length */ - *argPtr = NULL; + *out_arg = NULL; - (*linePtr)--; /* Make (*linePtr)[1] point to the '('. */ - (void)Var_Parse(linePtr, VAR_CMDLINE, doEval ? VARE_WANTRES : 0, + (*pp)--; /* Make (*pp)[1] point to the '('. */ + (void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE, &val, &val_freeIt); /* TODO: handle errors */ - /* If successful, *linePtr points beyond the closing ')' now. */ + /* If successful, *pp points beyond the closing ')' now. */ if (val == var_Error) { free(val_freeIt); return (size_t)-1; } /* A variable is empty when it just contains spaces... 4/15/92, christos */ cpp_skip_whitespace(&val); /* * For consistency with the other functions we can't generate the * true/false here. */ magic_res = *val != '\0' ? 2 : 1; free(val_freeIt); return magic_res; } static Boolean FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) { /* Magic values ahead, see ParseEmptyArg. */ return arglen == 1; } -static Token -CondParser_Func(CondParser *par, Boolean doEval) +static Boolean +CondParser_Func(CondParser *par, Boolean doEval, Token *out_token) { static const struct fn_def { const char *fn_name; size_t fn_name_len; size_t (*fn_parse)(const char **, Boolean, const char *, char **); Boolean (*fn_eval)(size_t, const char *); - } fn_defs[] = { + } fns[] = { { "defined", 7, ParseFuncArg, FuncDefined }, { "make", 4, ParseFuncArg, FuncMake }, { "exists", 6, ParseFuncArg, FuncExists }, { "empty", 5, ParseEmptyArg, FuncEmpty }, { "target", 6, ParseFuncArg, FuncTarget }, - { "commands", 8, ParseFuncArg, FuncCommands }, - { NULL, 0, NULL, NULL }, + { "commands", 8, ParseFuncArg, FuncCommands } }; - const struct fn_def *fn_def; - Token t; + const struct fn_def *fn; char *arg = NULL; size_t arglen; const char *cp = par->p; - const char *cp1; + const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0]; - for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) { - if (!is_token(cp, fn_def->fn_name, fn_def->fn_name_len)) + for (fn = fns; fn != fns_end; fn++) { + if (!is_token(cp, fn->fn_name, fn->fn_name_len)) continue; - cp += fn_def->fn_name_len; - /* There can only be whitespace before the '(' */ + + cp += fn->fn_name_len; cpp_skip_whitespace(&cp); if (*cp != '(') break; - arglen = fn_def->fn_parse(&cp, doEval, fn_def->fn_name, &arg); + arglen = fn->fn_parse(&cp, doEval, fn->fn_name, &arg); if (arglen == 0 || arglen == (size_t)-1) { par->p = cp; - return arglen == 0 ? TOK_FALSE : TOK_ERROR; + *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR; + return TRUE; } + /* Evaluate the argument using the required function. */ - t = !doEval || fn_def->fn_eval(arglen, arg); + *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg)); free(arg); par->p = cp; - return t; + return TRUE; } + return FALSE; +} + +/* Parse a function call, a number, a variable expression or a string + * literal. */ +static Token +CondParser_LeafToken(CondParser *par, Boolean doEval) +{ + Token t; + char *arg = NULL; + size_t arglen; + const char *cp = par->p; + const char *cp1; + + if (CondParser_Func(par, doEval, &t)) + return t; + /* Push anything numeric through the compare expression */ cp = par->p; - if (ch_isdigit(cp[0]) || strchr("+-", cp[0])) + if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') return CondParser_Comparison(par, doEval); /* * Most likely we have a naked token to apply the default function to. * However ".if a == b" gets here when the "a" is unquoted and doesn't * start with a '$'. This surprises people. * If what follows the function argument is a '=' or '!' then the syntax * would be invalid if we did "defined(a)" - so instead treat as an * expression. */ arglen = ParseFuncArg(&cp, doEval, NULL, &arg); cp1 = cp; cpp_skip_whitespace(&cp1); if (*cp1 == '=' || *cp1 == '!') return CondParser_Comparison(par, doEval); par->p = cp; /* * Evaluate the argument using the default function. * This path always treats .if as .ifdef. To get here, the character * after .if must have been taken literally, so the argument cannot * be empty - even if it contained a variable expansion. */ - t = !doEval || par->if_info->defProc(arglen, arg) == !par->if_info->doNot; + t = ToToken(!doEval || If_Eval(par->if_info, arg, arglen)); free(arg); return t; } /* Return the next token or comparison result from the parser. */ static Token CondParser_Token(CondParser *par, Boolean doEval) { Token t; t = par->curr; if (t != TOK_NONE) { par->curr = TOK_NONE; return t; } - while (par->p[0] == ' ' || par->p[0] == '\t') { - par->p++; - } + cpp_skip_hspace(&par->p); switch (par->p[0]) { case '(': par->p++; return TOK_LPAREN; case ')': par->p++; return TOK_RPAREN; case '|': par->p++; - if (par->p[0] == '|') { + if (par->p[0] == '|') par->p++; + else if (opts.lint) { + Parse_Error(PARSE_FATAL, "Unknown operator '|'"); + par->printedError = TRUE; + return TOK_ERROR; } return TOK_OR; case '&': par->p++; - if (par->p[0] == '&') { + if (par->p[0] == '&') par->p++; + else if (opts.lint) { + Parse_Error(PARSE_FATAL, "Unknown operator '&'"); + par->printedError = TRUE; + return TOK_ERROR; } return TOK_AND; case '!': par->p++; return TOK_NOT; - case '#': - case '\n': + case '#': /* XXX: see unit-tests/cond-token-plain.mk */ + case '\n': /* XXX: why should this end the condition? */ + /* Probably obsolete now, from 1993-03-21. */ case '\0': return TOK_EOF; case '"': case '$': return CondParser_Comparison(par, doEval); default: - return CondParser_Func(par, doEval); + return CondParser_LeafToken(par, doEval); } } /* Parse a single term in the expression. This consists of a terminal symbol * or TOK_NOT and a term (not including the binary operators): * * T -> defined(variable) | make(target) | exists(file) | symbol * T -> ! T | ( E ) * * Results: * TOK_TRUE, TOK_FALSE or TOK_ERROR. */ static Token CondParser_Term(CondParser *par, Boolean doEval) { Token t; t = CondParser_Token(par, doEval); if (t == TOK_EOF) { /* * If we reached the end of the expression, the expression * is malformed... */ t = TOK_ERROR; } else if (t == TOK_LPAREN) { /* * T -> ( E ) */ t = CondParser_Expr(par, doEval); if (t != TOK_ERROR) { if (CondParser_Token(par, doEval) != TOK_RPAREN) { t = TOK_ERROR; } } } else if (t == TOK_NOT) { t = CondParser_Term(par, doEval); if (t == TOK_TRUE) { t = TOK_FALSE; } else if (t == TOK_FALSE) { t = TOK_TRUE; } } return t; } /* Parse a conjunctive factor (nice name, wot?) * * F -> T && F | T * * Results: * TOK_TRUE, TOK_FALSE or TOK_ERROR */ static Token CondParser_Factor(CondParser *par, Boolean doEval) { Token l, o; l = CondParser_Term(par, doEval); if (l != TOK_ERROR) { o = CondParser_Token(par, doEval); if (o == TOK_AND) { /* * F -> T && F * * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we * have to parse the r.h.s. anyway (to throw it away). * If T is TOK_TRUE, the result is the r.h.s., be it a TOK_ERROR * or not. */ if (l == TOK_TRUE) { l = CondParser_Factor(par, doEval); } else { (void)CondParser_Factor(par, FALSE); } } else { /* * F -> T */ CondParser_PushBack(par, o); } } return l; } /* Main expression production. * * E -> F || E | F * * Results: * TOK_TRUE, TOK_FALSE or TOK_ERROR. */ static Token CondParser_Expr(CondParser *par, Boolean doEval) { Token l, o; l = CondParser_Factor(par, doEval); if (l != TOK_ERROR) { o = CondParser_Token(par, doEval); if (o == TOK_OR) { /* * E -> F || E * * A similar thing occurs for ||, except that here we make sure * the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s. * Once again, if l is TOK_FALSE, the result is the r.h.s. and once * again if l is TOK_TRUE, we parse the r.h.s. to throw it away. */ if (l == TOK_FALSE) { l = CondParser_Expr(par, doEval); } else { (void)CondParser_Expr(par, FALSE); } } else { /* * E -> F */ CondParser_PushBack(par, o); } } return l; } static CondEvalResult CondParser_Eval(CondParser *par, Boolean *value) { Token res; DEBUG1(COND, "CondParser_Eval: %s\n", par->p); res = CondParser_Expr(par, TRUE); if (res != TOK_FALSE && res != TOK_TRUE) return COND_INVALID; if (CondParser_Token(par, TRUE /* XXX: Why TRUE? */) != TOK_EOF) return COND_INVALID; *value = res == TOK_TRUE; return COND_PARSE; } /* Evaluate the condition, including any side effects from the variable * expressions in the condition. The condition consists of &&, ||, !, * function(arg), comparisons and parenthetical groupings thereof. * * Results: * COND_PARSE if the condition was valid grammatically * COND_INVALID if not a valid conditional. * * (*value) is set to the boolean value of the condition */ static CondEvalResult CondEvalExpression(const struct If *info, const char *cond, Boolean *value, Boolean eprint, Boolean strictLHS) { - static const struct If *dflt_info; CondParser par; - int rval; + CondEvalResult rval; lhsStrict = strictLHS; - while (*cond == ' ' || *cond == '\t') - cond++; + cpp_skip_hspace(&cond); - if (info == NULL && (info = dflt_info) == NULL) { - /* Scan for the entry for .if - it can't be first */ - for (info = ifs;; info++) - if (info->form[0] == 0) - break; - dflt_info = info; - } - assert(info != NULL); - - par.if_info = info; + par.if_info = info != NULL ? info : ifs + PLAIN_IF_INDEX; par.p = cond; par.curr = TOK_NONE; par.printedError = FALSE; rval = CondParser_Eval(&par, value); if (rval == COND_INVALID && eprint && !par.printedError) Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); return rval; } +/* Evaluate a condition in a :? modifier, such as + * ${"${VAR}" == value:?yes:no}. */ CondEvalResult Cond_EvalCondition(const char *cond, Boolean *out_value) { return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE); } -/* Evaluate the conditional in the passed line. The line looks like this: - * . - * In this line, is any of if, ifmake, ifnmake, ifdef, ifndef, - * elif, elifmake, elifnmake, elifdef, elifndef. - * In this line, consists of &&, ||, !, function(arg), comparisons - * and parenthetical groupings thereof. +/* Evaluate the conditional directive in the line, which is one of: * - * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order - * to detect spurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF), - * otherwise .else could be treated as '.elif 1'. + * .if + * .ifmake + * .ifnmake + * .ifdef + * .ifndef + * .elif + * .elifmake + * .elifnmake + * .elifdef + * .elifndef + * .else + * .endif * + * In these directives, consists of &&, ||, !, function(arg), + * comparisons, expressions, bare words, numbers and strings, and + * parenthetical groupings thereof. + * * Results: - * COND_PARSE to continue parsing the lines after the conditional - * (when .if or .else returns TRUE) + * COND_PARSE to continue parsing the lines that follow the + * conditional (when evaluates to TRUE) * COND_SKIP to skip the lines after the conditional - * (when .if or .elif returns FALSE, or when a previous + * (when evaluates to FALSE, or when a previous * branch has already been taken) * COND_INVALID if the conditional was not valid, either because of * a syntax error or because some variable was undefined * or because the condition could not be evaluated */ CondEvalResult -Cond_EvalLine(const char *line) +Cond_EvalLine(const char *const line) { - enum { MAXIF = 128 }; /* maximum depth of .if'ing */ - enum { MAXIF_BUMP = 32 }; /* how much to grow by */ - enum if_states { - IF_ACTIVE, /* .if or .elif part active */ - ELSE_ACTIVE, /* .else part active */ - SEARCH_FOR_ELIF, /* searching for .elif/else to execute */ - SKIP_TO_ELSE, /* has been true, but not seen '.else' */ - SKIP_TO_ENDIF /* nothing else to execute */ - }; - static enum if_states *cond_state = NULL; - static unsigned int max_if_depth = MAXIF; + typedef enum IfState { + /* None of the previous evaluated to TRUE. */ + IFS_INITIAL = 0, + + /* The previous evaluated to TRUE. + * The lines following this condition are interpreted. */ + IFS_ACTIVE = 1 << 0, + + /* The previous directive was an '.else'. */ + IFS_SEEN_ELSE = 1 << 1, + + /* One of the previous evaluated to TRUE. */ + IFS_WAS_ACTIVE = 1 << 2 + + } IfState; + + static enum IfState *cond_states = NULL; + static unsigned int cond_states_cap = 128; + const struct If *ifp; Boolean isElif; Boolean value; - enum if_states state; + IfState state; + const char *p = line; - if (!cond_state) { - cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state)); - cond_state[0] = IF_ACTIVE; + if (cond_states == NULL) { + cond_states = bmake_malloc(cond_states_cap * sizeof *cond_states); + cond_states[0] = IFS_ACTIVE; } - /* skip leading character (the '.') and any whitespace */ - for (line++; *line == ' ' || *line == '\t'; line++) - continue; - /* Find what type of if we're dealing with. */ - if (line[0] == 'e') { - if (line[1] != 'l') { - if (!is_token(line + 1, "ndif", 4)) + p++; /* skip the leading '.' */ + cpp_skip_hspace(&p); + + /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ + if (p[0] == 'e') { + if (p[1] != 'l') { + if (!is_token(p + 1, "ndif", 4)) { + /* Unknown directive. It might still be a transformation + * rule like '.elisp.scm', therefore no error message here. */ return COND_INVALID; - /* End of conditional section */ + } + + /* It is an '.endif'. */ + /* TODO: check for extraneous */ + if (cond_depth == cond_min_depth) { Parse_Error(PARSE_FATAL, "if-less endif"); return COND_PARSE; } + /* Return state for previous conditional */ cond_depth--; - return cond_state[cond_depth] <= ELSE_ACTIVE + return cond_states[cond_depth] & IFS_ACTIVE ? COND_PARSE : COND_SKIP; } /* Quite likely this is 'else' or 'elif' */ - line += 2; - if (is_token(line, "se", 2)) { - /* It is else... */ + p += 2; + if (is_token(p, "se", 2)) { /* It is an 'else'. */ + + if (opts.lint && p[2] != '\0') + Parse_Error(PARSE_FATAL, + "The .else directive does not take arguments."); + if (cond_depth == cond_min_depth) { Parse_Error(PARSE_FATAL, "if-less else"); return COND_PARSE; } - state = cond_state[cond_depth]; - switch (state) { - case SEARCH_FOR_ELIF: - state = ELSE_ACTIVE; - break; - case ELSE_ACTIVE: - case SKIP_TO_ENDIF: - Parse_Error(PARSE_WARNING, "extra else"); - /* FALLTHROUGH */ - default: - case IF_ACTIVE: - case SKIP_TO_ELSE: - state = SKIP_TO_ENDIF; - break; + state = cond_states[cond_depth]; + if (state == IFS_INITIAL) { + state = IFS_ACTIVE | IFS_SEEN_ELSE; + } else { + if (state & IFS_SEEN_ELSE) + Parse_Error(PARSE_WARNING, "extra else"); + state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; } - cond_state[cond_depth] = state; - return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP; + cond_states[cond_depth] = state; + + return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; } /* Assume for now it is an elif */ isElif = TRUE; } else isElif = FALSE; - if (line[0] != 'i' || line[1] != 'f') - /* Not an ifxxx or elifxxx line */ - return COND_INVALID; + if (p[0] != 'i' || p[1] != 'f') { + /* Unknown directive. It might still be a transformation rule like + * '.elisp.scm', therefore no error message here. */ + return COND_INVALID; /* Not an ifxxx or elifxxx line */ + } /* * Figure out what sort of conditional it is -- what its default * function is, etc. -- by looking in the table of valid "ifs" */ - line += 2; + p += 2; for (ifp = ifs;; ifp++) { - if (ifp->form == NULL) + if (ifp->form == NULL) { + /* TODO: Add error message about unknown directive, + * since there is no other known directive that starts with 'el' + * or 'if'. + * Example: .elifx 123 */ return COND_INVALID; - if (is_token(ifp->form, line, ifp->formlen)) { - line += ifp->formlen; + } + if (is_token(p, ifp->form, ifp->formlen)) { + p += ifp->formlen; break; } } /* Now we know what sort of 'if' it is... */ if (isElif) { if (cond_depth == cond_min_depth) { Parse_Error(PARSE_FATAL, "if-less elif"); return COND_PARSE; } - state = cond_state[cond_depth]; - if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) { + state = cond_states[cond_depth]; + if (state & IFS_SEEN_ELSE) { Parse_Error(PARSE_WARNING, "extra elif"); - cond_state[cond_depth] = SKIP_TO_ENDIF; + cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; return COND_SKIP; } - if (state != SEARCH_FOR_ELIF) { - /* Either just finished the 'true' block, or already SKIP_TO_ELSE */ - cond_state[cond_depth] = SKIP_TO_ELSE; + if (state != IFS_INITIAL) { + cond_states[cond_depth] = IFS_WAS_ACTIVE; return COND_SKIP; } } else { /* Normal .if */ - if (cond_depth + 1 >= max_if_depth) { + if (cond_depth + 1 >= cond_states_cap) { /* * This is rare, but not impossible. * In meta mode, dirdeps.mk (only runs at level 0) * can need more than the default. */ - max_if_depth += MAXIF_BUMP; - cond_state = bmake_realloc(cond_state, - max_if_depth * sizeof(*cond_state)); + cond_states_cap += 32; + cond_states = bmake_realloc(cond_states, + cond_states_cap * sizeof *cond_states); } - state = cond_state[cond_depth]; + state = cond_states[cond_depth]; cond_depth++; - if (state > ELSE_ACTIVE) { + if (!(state & IFS_ACTIVE)) { /* If we aren't parsing the data, treat as always false */ - cond_state[cond_depth] = SKIP_TO_ELSE; + cond_states[cond_depth] = IFS_WAS_ACTIVE; return COND_SKIP; } } /* And evaluate the conditional expression */ - if (CondEvalExpression(ifp, line, &value, TRUE, TRUE) == COND_INVALID) { + if (CondEvalExpression(ifp, p, &value, TRUE, TRUE) == COND_INVALID) { /* Syntax error in conditional, error message already output. */ /* Skip everything to matching .endif */ - cond_state[cond_depth] = SKIP_TO_ELSE; + /* XXX: An extra '.else' is not detected in this case. */ + cond_states[cond_depth] = IFS_WAS_ACTIVE; return COND_SKIP; } if (!value) { - cond_state[cond_depth] = SEARCH_FOR_ELIF; + cond_states[cond_depth] = IFS_INITIAL; return COND_SKIP; } - cond_state[cond_depth] = IF_ACTIVE; + cond_states[cond_depth] = IFS_ACTIVE; return COND_PARSE; } void Cond_restore_depth(unsigned int saved_depth) { unsigned int open_conds = cond_depth - cond_min_depth; if (open_conds != 0 || saved_depth > cond_depth) { Parse_Error(PARSE_FATAL, "%u open conditional%s", open_conds, open_conds == 1 ? "" : "s"); cond_depth = cond_min_depth; } cond_min_depth = saved_depth; } unsigned int Cond_save_depth(void) { unsigned int depth = cond_min_depth; cond_min_depth = cond_depth; return depth; } 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 367862) +++ head/contrib/bmake/configure.in (revision 367863) @@ -1,432 +1,433 @@ dnl dnl RCSid: -dnl $Id: configure.in,v 1.67 2020/10/19 19:47:50 sjg Exp $ +dnl $Id: configure.in,v 1.69 2020/11/14 07:40:43 sjg Exp $ dnl dnl Process this file with autoconf to produce a configure script dnl AC_PREREQ(2.50) -AC_INIT([bmake], [20201018], [sjg@NetBSD.org]) +AC_INIT([bmake], [20201112], [sjg@NetBSD.org]) AC_CONFIG_HEADERS(config.h) dnl make srcdir absolute case "$srcdir" in /*) ;; *) srcdir=`cd $srcdir && pwd`;; esac dnl get _MAKE_VERSION . $srcdir/VERSION OS=`uname -s` 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 case "$OS" in CYGWIN*|MINGW*) use_makefile=no;; *) use_makefile=yes;; esac AC_ARG_WITH(makefile, -[ --without-makefile disable use of generated makefile], +[ --without-makefile disable use of generated makefile], [case "${withval}" in yes|no) use_makefile=${withval};; *) AC_MSG_ERROR(bad value ${withval} given for makefile) ;; esac]) dnl use_meta=yes AC_ARG_WITH(meta, -[ --without-meta disable use of meta-mode], +[ --without-meta disable 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={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev], +[ --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev], [ case "/${withval}" in /no) use_filemon=no;; /*trace) filemon_h=no use_filemon="${withval}";; */filemon.h) filemon_h="${withval}";; */filemon*) filemon_h="${withval}/filemon.h";; *) AC_MSG_ERROR(bad value ${withval} given for filemon) ;; esac case "$use_filemon,$filemon_h" in ,*.h) use_filemon=dev;; esac ], [ case "$OS" in NetBSD) filemon_h=no use_filemon=ktrace;; *) for d in "/usr/include/dev/filemon" "$prefix/include/dev/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" && { use_filemon=dev; break; } done ;; esac use_filemon=${use_filemon:-no} case "$use_filemon" in dev) ;; *) filemon_h=no;; esac ]) dnl echo "Note: use_meta=$use_meta use_filemon=$use_filemon filemon_h=$filemon_h" >&6 case "$use_meta" in yes) case "$use_filemon" in no) ;; *) echo "Using: filemon_${use_filemon}.c" >&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_CC_C99 dnl 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_SYS_WAIT AC_HEADER_DIRENT dnl Keep this list sorted AC_CHECK_HEADERS(sys/param.h) dnl On BSDi at least we really need sys/param.h for sys/sysctl.h AC_CHECK_HEADERS([sys/sysctl.h], [], [], [#ifdef HAVE_SYS_PARAM_H # include # endif ]) AC_CHECK_HEADERS( \ ar.h \ err.h \ fcntl.h \ libgen.h \ limits.h \ paths.h \ poll.h \ ranlib.h \ sys/mman.h \ sys/select.h \ sys/socket.h \ sys/time.h \ sys/uio.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_MODE_T AC_TYPE_OFF_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_UINT32_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;; +*) force_machine_arch=FORCE_; machine_arch=$with_force_machine_arch;; 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 +echo "Using: ${force_machine}MACHINE=$machine, ${force_machine_arch}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(force_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(use_filemon) AC_SUBST(filemon_h) AC_SUBST(_MAKE_VERSION) bm_outfiles="Makefile.config unit-tests/Makefile.config make-bootstrap.sh" if test $use_makefile = yes; then bm_outfiles="makefile $bm_outfiles" fi AC_OUTPUT($bm_outfiles) cat < #include #include #include #include "make.h" #include "dir.h" #include "job.h" /* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $"); +MAKE_RCSID("$NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $"); #define DIR_DEBUG0(text) DEBUG0(DIR, text) #define DIR_DEBUG1(fmt, arg1) DEBUG1(DIR, fmt, arg1) #define DIR_DEBUG2(fmt, arg1, arg2) DEBUG2(DIR, fmt, arg1, arg2) /* A search path is a list of CachedDir structures. A CachedDir has in it the * name of the directory and the names of all the files in the directory. * This is used to cut down on the number of system calls necessary to find * implicit dependents and their like. Since these searches are made before * any actions are taken, we need not worry about the directory changing due * to creation commands. If this hampers the style of some makefiles, they * must be changed. * * All previously-read directories are kept in openDirs, which is checked * first before a directory is opened. * * The need for the caching of whole directories is brought about by the * multi-level transformation code in suff.c, which tends to search for far * more files than regular make does. In the initial implementation, the * amount of time spent performing "stat" calls was truly astronomical. * The problem with caching at the start is, of course, that pmake doesn't * then detect changes to these directories during the course of the make. * Three possibilities suggest themselves: * * 1) just use stat to test for a file's existence. As mentioned above, * this is very inefficient due to the number of checks engendered by * the multi-level transformation code. * * 2) use readdir() and company to search the directories, keeping them * open between checks. I have tried this and while it didn't slow down * the process too much, it could severely affect the amount of * parallelism available as each directory open would take another file * descriptor out of play for handling I/O for another job. Given that - * it is only recently that UNIX OS's have taken to allowing more than - * 20 or 32 file descriptors for a process, this doesn't seem acceptable - * to me. + * it is only recently (as of 1993 or earlier) that UNIX OS's have taken + * to allowing more than 20 or 32 file descriptors for a process, this + * doesn't seem acceptable to me. * * 3) record the mtime of the directory in the CachedDir structure and * verify the directory hasn't changed since the contents were cached. * This will catch the creation or deletion of files, but not the * updating of files. However, since it is the creation and deletion * that is the problem, this could be a good thing to do. Unfortunately, * if the directory (say ".") were fairly large and changed fairly * frequently, the constant reloading could seriously degrade * performance. It might be good in such cases to keep track of the * number of reloadings and if the number goes over a (small) limit, * resort to using stat in its place. * * An additional thing to consider is that pmake is used primarily to create - * C programs and until recently pcc-based compilers refused to allow you to - * specify where the resulting object file should be placed. This forced all - * objects to be created in the current directory. This isn't meant as a full - * excuse, just an explanation of some of the reasons for the caching used - * here. + * C programs and until recently (as of 1993 or earlier) pcc-based compilers + * refused to allow you to specify where the resulting object file should be + * placed. This forced all objects to be created in the current directory. + * This isn't meant as a full excuse, just an explanation of some of the + * reasons for the caching used here. * * One more note: the location of a target's file is only performed on the * downward traversal of the graph and then only for terminal nodes in the * graph. This could be construed as wrong in some cases, but prevents * inadvertent modification of files when the "installed" directory for a * file is provided in the search path. * * Another data structure maintained by this module is an mtime cache used * when the searching of cached directories fails to find a file. In the past, * Dir_FindFile would simply perform an access() call in such a case to * determine if the file could be found using just the name given. When this * hit, however, all that was gained was the knowledge that the file existed. * Given that an access() is essentially a stat() without the copyout() call, * and that the same filesystem overhead would have to be incurred in * Dir_MTime, it made sense to replace the access() with a stat() and record - * the mtime in a cache for when Dir_MTime was actually called. + * the mtime in a cache for when Dir_UpdateMTime was actually called. */ typedef List CachedDirList; typedef ListNode CachedDirListNode; typedef ListNode SearchPathNode; SearchPath *dirSearchPath; /* main search path */ /* A list of cached directories, with fast lookup by directory name. */ typedef struct OpenDirs { CachedDirList *list; HashTable /* of CachedDirListNode */ table; } OpenDirs; static void OpenDirs_Init(OpenDirs *odirs) { odirs->list = Lst_New(); HashTable_Init(&odirs->table); } #ifdef CLEANUP static void OpenDirs_Done(OpenDirs *odirs) { CachedDirListNode *ln = odirs->list->first; while (ln != NULL) { CachedDirListNode *next = ln->next; CachedDir *dir = ln->datum; Dir_Destroy(dir); /* removes the dir from odirs->list */ ln = next; } Lst_Free(odirs->list); HashTable_Done(&odirs->table); } #endif static CachedDir * OpenDirs_Find(OpenDirs *odirs, const char *name) { CachedDirListNode *ln = HashTable_FindValue(&odirs->table, name); return ln != NULL ? ln->datum : NULL; } static void OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir) { - HashEntry *he = HashTable_FindEntry(&odirs->table, cdir->name); - if (he != NULL) + if (HashTable_FindEntry(&odirs->table, cdir->name) != NULL) return; - he = HashTable_CreateEntry(&odirs->table, cdir->name, NULL); Lst_Append(odirs->list, cdir); - HashEntry_Set(he, odirs->list->last); + HashTable_Set(&odirs->table, cdir->name, odirs->list->last); } static void OpenDirs_Remove(OpenDirs *odirs, const char *name) { HashEntry *he = HashTable_FindEntry(&odirs->table, name); CachedDirListNode *ln; if (he == NULL) return; ln = HashEntry_Get(he); HashTable_DeleteEntry(&odirs->table, he); Lst_Remove(odirs->list, ln); } -static OpenDirs openDirs; /* the list of all open directories */ +static OpenDirs openDirs; /* all cached directories */ /* - * Variables for gathering statistics on the efficiency of the cashing + * Variables for gathering statistics on the efficiency of the caching * mechanism. */ static int hits; /* Found in directory cache */ static int misses; /* Sad, but not evil misses */ static int nearmisses; /* Found under search path */ static int bigmisses; /* Sought by itself */ static CachedDir *dot; /* contents of current directory */ static CachedDir *cur; /* contents of current directory, if not dot */ static CachedDir *dotLast; /* a fake path entry indicating we need to * look for . last */ /* Results of doing a last-resort stat in Dir_FindFile -- if we have to go to * the system to find the file, we might as well have its mtime on record. * * XXX: If this is done way early, there's a chance other rules will have * already updated the file, in which case we'll update it again. Generally, * there won't be two rules to update a single file, so this should be ok, * but... */ static HashTable mtimes; static HashTable lmtimes; /* same as mtimes but for lstat */ -/* - * We use stat(2) a lot, cache the results. - * mtime and mode are all we care about. - */ -struct cache_st { - time_t lmtime; /* lstat */ - time_t mtime; /* stat */ - mode_t mode; -}; - -/* minimize changes below */ typedef enum CachedStatsFlags { - CST_LSTAT = 0x01, /* call lstat(2) instead of stat(2) */ - CST_UPDATE = 0x02 /* ignore existing cached entry */ + CST_NONE = 0, + CST_LSTAT = 1 << 0, /* call lstat(2) instead of stat(2) */ + CST_UPDATE = 1 << 1 /* ignore existing cached entry */ } CachedStatsFlags; -/* Returns 0 and the result of stat(2) or lstat(2) in *mst, or -1 on error. */ +/* Returns 0 and the result of stat(2) or lstat(2) in *out_cst, + * or -1 on error. */ static int -cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst, +cached_stats(const char *pathname, struct cached_stat *out_cst, CachedStatsFlags flags) { - HashEntry *entry; + HashTable *tbl = flags & CST_LSTAT ? &lmtimes : &mtimes; struct stat sys_st; - struct cache_st *cst; + struct cached_stat *cst; int rc; - if (!pathname || !pathname[0]) - return -1; + if (pathname == NULL || pathname[0] == '\0') + return -1; /* This can happen in meta mode. */ - entry = HashTable_FindEntry(htp, pathname); - - if (entry && !(flags & CST_UPDATE)) { - cst = HashEntry_Get(entry); - - mst->mst_mode = cst->mode; - mst->mst_mtime = (flags & CST_LSTAT) ? cst->lmtime : cst->mtime; - if (mst->mst_mtime) { - DIR_DEBUG2("Using cached time %s for %s\n", - Targ_FmtTime(mst->mst_mtime), pathname); - return 0; - } + cst = HashTable_FindValue(tbl, pathname); + if (cst != NULL && !(flags & CST_UPDATE)) { + *out_cst = *cst; + DIR_DEBUG2("Using cached time %s for %s\n", + Targ_FmtTime(cst->cst_mtime), pathname); + return 0; } - rc = (flags & CST_LSTAT) - ? lstat(pathname, &sys_st) - : stat(pathname, &sys_st); + rc = (flags & CST_LSTAT ? lstat : stat)(pathname, &sys_st); if (rc == -1) - return -1; + return -1; /* don't cache negative lookups */ if (sys_st.st_mtime == 0) sys_st.st_mtime = 1; /* avoid confusion with missing file */ - mst->mst_mode = sys_st.st_mode; - mst->mst_mtime = sys_st.st_mtime; - - if (entry == NULL) - entry = HashTable_CreateEntry(htp, pathname, NULL); - if (HashEntry_Get(entry) == NULL) { - HashEntry_Set(entry, bmake_malloc(sizeof(*cst))); - memset(HashEntry_Get(entry), 0, sizeof(*cst)); + if (cst == NULL) { + cst = bmake_malloc(sizeof *cst); + HashTable_Set(tbl, pathname, cst); } - cst = HashEntry_Get(entry); - if (flags & CST_LSTAT) { - cst->lmtime = sys_st.st_mtime; - } else { - cst->mtime = sys_st.st_mtime; - } - cst->mode = sys_st.st_mode; + + cst->cst_mtime = sys_st.st_mtime; + cst->cst_mode = sys_st.st_mode; + + *out_cst = *cst; DIR_DEBUG2(" Caching %s for %s\n", Targ_FmtTime(sys_st.st_mtime), pathname); return 0; } int -cached_stat(const char *pathname, struct make_stat *st) +cached_stat(const char *pathname, struct cached_stat *cst) { - return cached_stats(&mtimes, pathname, st, 0); + return cached_stats(pathname, cst, CST_NONE); } int -cached_lstat(const char *pathname, struct make_stat *st) +cached_lstat(const char *pathname, struct cached_stat *cst) { - return cached_stats(&lmtimes, pathname, st, CST_LSTAT); + return cached_stats(pathname, cst, CST_LSTAT); } /* Initialize the directories module. */ void Dir_Init(void) { dirSearchPath = Lst_New(); OpenDirs_Init(&openDirs); HashTable_Init(&mtimes); HashTable_Init(&lmtimes); } void Dir_InitDir(const char *cdname) { Dir_InitCur(cdname); - dotLast = bmake_malloc(sizeof(CachedDir)); + dotLast = bmake_malloc(sizeof *dotLast); dotLast->refCount = 1; dotLast->hits = 0; dotLast->name = bmake_strdup(".DOTLAST"); HashTable_Init(&dotLast->files); } /* * Called by Dir_InitDir and whenever .CURDIR is assigned to. */ void Dir_InitCur(const char *cdname) { CachedDir *dir; - if (cdname != NULL) { + if (cdname == NULL) + return; + + /* + * Our build directory is not the same as our source directory. + * Keep this one around too. + */ + dir = Dir_AddDir(NULL, cdname); + if (dir == NULL) + return; + + /* XXX: Reference counting is wrong here. + * If this function is called repeatedly with the same directory name, + * its reference count increases each time even though the number of + * actual references stays the same. */ + + dir->refCount++; + if (cur != NULL && cur != dir) { /* - * Our build directory is not the same as our source directory. - * Keep this one around too. + * We've been here before, clean up. */ - if ((dir = Dir_AddDir(NULL, cdname))) { - dir->refCount++; - if (cur && cur != dir) { - /* - * We've been here before, clean up. - */ - cur->refCount--; - Dir_Destroy(cur); - } - cur = dir; - } + cur->refCount--; + Dir_Destroy(cur); } + cur = dir; } /* (Re)initialize "dot" (current/object directory) path hash. * Some directories may be opened. */ void Dir_InitDot(void) { if (dot != NULL) { /* Remove old entry from openDirs, but do not destroy. */ OpenDirs_Remove(&openDirs, dot->name); } dot = Dir_AddDir(NULL, "."); if (dot == NULL) { Error("Cannot open `.' (%s)", strerror(errno)); exit(1); } /* * We always need to have dot around, so we increment its reference count * to make sure it's not destroyed. */ dot->refCount++; Dir_SetPATH(); /* initialize */ } /* Clean up the directories module. */ void Dir_End(void) { #ifdef CLEANUP if (cur) { cur->refCount--; Dir_Destroy(cur); } dot->refCount--; dotLast->refCount--; Dir_Destroy(dotLast); Dir_Destroy(dot); Dir_ClearPath(dirSearchPath); Lst_Free(dirSearchPath); OpenDirs_Done(&openDirs); HashTable_Done(&mtimes); #endif } /* * We want ${.PATH} to indicate the order in which we will actually * search, so we rebuild it after any .PATH: target. * This is the simplest way to deal with the effect of .DOTLAST. */ void Dir_SetPATH(void) { CachedDirListNode *ln; Boolean hasLastDot = FALSE; /* true if we should search dot last */ Var_Delete(".PATH", VAR_GLOBAL); if ((ln = dirSearchPath->first) != NULL) { CachedDir *dir = ln->datum; if (dir == dotLast) { hasLastDot = TRUE; Var_Append(".PATH", dotLast->name, VAR_GLOBAL); } } if (!hasLastDot) { if (dot) Var_Append(".PATH", dot->name, VAR_GLOBAL); if (cur) Var_Append(".PATH", cur->name, VAR_GLOBAL); } for (ln = dirSearchPath->first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (dir == dotLast) continue; if (dir == dot && hasLastDot) continue; Var_Append(".PATH", dir->name, VAR_GLOBAL); } if (hasLastDot) { if (dot) Var_Append(".PATH", dot->name, VAR_GLOBAL); if (cur) Var_Append(".PATH", cur->name, VAR_GLOBAL); } } /* See if the given name has any wildcard characters in it and all braces and * brackets are properly balanced. * * XXX: This code is not 100% correct ([^]] fails etc.). I really don't think * that make(1) should be expanding patterns, because then you have to set a * mechanism for escaping the expansion! * * Return TRUE if the word should be expanded, FALSE otherwise. */ Boolean Dir_HasWildcards(const char *name) { const char *p; Boolean wild = FALSE; int braces = 0, brackets = 0; for (p = name; *p != '\0'; p++) { switch (*p) { case '{': braces++; wild = TRUE; break; case '}': braces--; break; case '[': brackets++; wild = TRUE; break; case ']': brackets--; break; case '?': case '*': wild = TRUE; break; default: break; } } return wild && brackets == 0 && braces == 0; } /* See if any files match the pattern and add their names to the 'expansions' * list if they do. * * This is incomplete -- wildcards are only expanded in the final path * component, but not in directories like src/lib*c/file*.c, but it * will do for now (now being 1993 until at least 2020). To expand these, * use the ':sh' variable modifier such as in ${:!echo src/lib*c/file*.c!}. * * Input: * pattern Pattern to look for * dir Directory to search * expansion Place to store the results */ static void DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions) { const char *dirName = dir->name; Boolean isDot = dirName[0] == '.' && dirName[1] == '\0'; HashIter hi; + /* XXX: Iterating over all hash entries is inefficient. If the pattern + * is a plain string without any wildcards, a direct lookup is faster. */ + HashIter_Init(&hi, &dir->files); while (HashIter_Next(&hi) != NULL) { const char *base = hi.entry->key; if (!Str_Match(base, pattern)) continue; /* * Follow the UNIX convention that dot files are only found if the * pattern begins with a dot. The pattern '.*' does not match '.' or * '..' since these are not included in the directory cache. * * This means that the pattern '[a-z.]*' does not find '.file', which * is consistent with bash, NetBSD sh and csh. */ if (base[0] == '.' && pattern[0] != '.') continue; { char *fullName = isDot ? bmake_strdup(base) : str_concat3(dirName, "/", base); Lst_Append(expansions, fullName); } } } /* Find the next closing brace in the string, taking nested braces into * account. */ static const char * closing_brace(const char *p) { int nest = 0; while (*p != '\0') { if (*p == '}' && nest == 0) break; if (*p == '{') nest++; if (*p == '}') nest--; p++; } return p; } /* Find the next closing brace or comma in the string, taking nested braces * into account. */ static const char * separator_comma(const char *p) { int nest = 0; while (*p != '\0') { if ((*p == '}' || *p == ',') && nest == 0) break; if (*p == '{') nest++; if (*p == '}') nest--; p++; } return p; } static Boolean contains_wildcard(const char *p) { for (; *p != '\0'; p++) { switch (*p) { case '*': case '?': case '{': case '[': return TRUE; } } return FALSE; } static char * concat3(const char *a, size_t a_len, const char *b, size_t b_len, const char *c, size_t c_len) { size_t s_len = a_len + b_len + c_len; char *s = bmake_malloc(s_len + 1); memcpy(s, a, a_len); memcpy(s + a_len, b, b_len); memcpy(s + a_len + b_len, c, c_len); s[s_len] = '\0'; return s; } /* Expand curly braces like the C shell. Brace expansion by itself is purely * textual, the expansions are not looked up in the file system. But if an * expanded word contains wildcard characters, it is expanded further, * matching only the actually existing files. * * Example: "{a{b,c}}" expands to "ab" and "ac". * Example: "{a}" expands to "a". * Example: "{a,*.c}" expands to "a" and all "*.c" files that exist. * * Input: * word Entire word to expand * brace First curly brace in it * path Search path to use * expansions Place to store the expansions */ static void DirExpandCurly(const char *word, const char *brace, SearchPath *path, StringList *expansions) { const char *prefix, *middle, *piece, *middle_end, *suffix; size_t prefix_len, suffix_len; /* Split the word into prefix '{' middle '}' suffix. */ middle = brace + 1; middle_end = closing_brace(middle); if (*middle_end == '\0') { Error("Unterminated {} clause \"%s\"", middle); return; } prefix = word; prefix_len = (size_t)(brace - prefix); suffix = middle_end + 1; suffix_len = strlen(suffix); /* Split the middle into pieces, separated by commas. */ piece = middle; while (piece < middle_end + 1) { const char *piece_end = separator_comma(piece); size_t piece_len = (size_t)(piece_end - piece); char *file = concat3(prefix, prefix_len, piece, piece_len, suffix, suffix_len); if (contains_wildcard(file)) { Dir_Expand(file, path, expansions); free(file); } else { Lst_Append(expansions, file); } piece = piece_end + 1; /* skip over the comma or closing brace */ } } /* Expand the word in each of the directories from the path. */ static void DirExpandPath(const char *word, SearchPath *path, StringList *expansions) { SearchPathNode *ln; for (ln = path->first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; DirMatchFiles(word, dir, expansions); } } static void PrintExpansions(StringList *expansions) { const char *sep = ""; StringListNode *ln; for (ln = expansions->first; ln != NULL; ln = ln->next) { const char *word = ln->datum; debug_printf("%s%s", sep, word); sep = " "; } debug_printf("\n"); } /* Expand the given word into a list of words by globbing it, looking in the * directories on the given search path. * * Input: * word the word to expand * path the directories in which to find the files * expansions the list on which to place the results */ void Dir_Expand(const char *word, SearchPath *path, StringList *expansions) { const char *cp; assert(path != NULL); assert(expansions != NULL); DIR_DEBUG1("Expanding \"%s\"... ", word); cp = strchr(word, '{'); if (cp) { DirExpandCurly(word, cp, path, expansions); } else { cp = strchr(word, '/'); if (cp) { /* * The thing has a directory component -- find the first wildcard * in the string. */ for (cp = word; *cp; cp++) { if (*cp == '?' || *cp == '[' || *cp == '*') { break; } } if (*cp != '\0') { /* * Back up to the start of the component */ while (cp > word && *cp != '/') { cp--; } if (cp != word) { char *prefix = bmake_strsedup(word, cp + 1); /* * If the glob isn't in the first component, try and find * all the components up to the one with a wildcard. */ char *dirpath = Dir_FindFile(prefix, path); free(prefix); /* * dirpath is null if can't find the leading component * XXX: Dir_FindFile won't find internal components. * i.e. if the path contains ../Etc/Object and we're * looking for Etc, it won't be found. Ah well. * Probably not important. */ if (dirpath != NULL) { char *dp = &dirpath[strlen(dirpath) - 1]; if (*dp == '/') *dp = '\0'; path = Lst_New(); (void)Dir_AddDir(path, dirpath); DirExpandPath(cp + 1, path, expansions); Lst_Free(path); } } else { /* * Start the search from the local directory */ DirExpandPath(word, path, expansions); } } else { /* * Return the file -- this should never happen. */ DirExpandPath(word, path, expansions); } } else { /* * First the files in dot */ DirMatchFiles(word, dot, expansions); /* * Then the files in every other directory on the path. */ DirExpandPath(word, path, expansions); } } if (DEBUG(DIR)) PrintExpansions(expansions); } /* Find if the file with the given name exists in the given path. * Return the freshly allocated path to the file, or NULL. */ static char * DirLookup(CachedDir *dir, const char *base) { char *file; /* the current filename to check */ DIR_DEBUG1(" %s ...\n", dir->name); if (HashTable_FindEntry(&dir->files, base) == NULL) return NULL; file = str_concat3(dir->name, "/", base); DIR_DEBUG1(" returning %s\n", file); dir->hits++; hits++; return file; } /* Find if the file with the given name exists in the given directory. * Return the freshly allocated path to the file, or NULL. */ static char * DirLookupSubdir(CachedDir *dir, const char *name) { - struct make_stat mst; + struct cached_stat cst; char *file = dir == dot ? bmake_strdup(name) : str_concat3(dir->name, "/", name); DIR_DEBUG1("checking %s ...\n", file); - if (cached_stat(file, &mst) == 0) { + if (cached_stat(file, &cst) == 0) { nearmisses++; return file; } free(file); return NULL; } /* Find if the file with the given name exists in the given path. * Return the freshly allocated path to the file, the empty string, or NULL. * Returning the empty string means that the search should be terminated. */ static char * DirLookupAbs(CachedDir *dir, const char *name, const char *cp) { const char *dnp; /* pointer into dir->name */ const char *np; /* pointer into name */ DIR_DEBUG1(" %s ...\n", dir->name); /* * If the file has a leading path component and that component * exactly matches the entire name of the current search * directory, we can attempt another cache lookup. And if we don't * have a hit, we can safely assume the file does not exist at all. */ for (dnp = dir->name, np = name; *dnp != '\0' && *dnp == *np; dnp++, np++) continue; if (*dnp != '\0' || np != cp - 1) return NULL; if (HashTable_FindEntry(&dir->files, cp) == NULL) { DIR_DEBUG0(" must be here but isn't -- returning\n"); return bmake_strdup(""); /* to terminate the search */ } dir->hits++; hits++; DIR_DEBUG1(" returning %s\n", name); return bmake_strdup(name); } /* Find the file given on "." or curdir. * Return the freshly allocated path to the file, or NULL. */ static char * DirFindDot(const char *name, const char *base) { if (HashTable_FindEntry(&dot->files, base) != NULL) { DIR_DEBUG0(" in '.'\n"); hits++; dot->hits++; return bmake_strdup(name); } if (cur != NULL && HashTable_FindEntry(&cur->files, base) != NULL) { DIR_DEBUG1(" in ${.CURDIR} = %s\n", cur->name); hits++; cur->hits++; return str_concat3(cur->name, "/", base); } return NULL; } /* Find the file with the given name along the given search path. * * If the file is found in a directory that is not on the path * already (either 'name' is absolute or it is a relative path * [ dir1/.../dirn/file ] which exists below one of the directories * already on the search path), its directory is added to the end * of the path, on the assumption that there will be more files in * that directory later on. Sometimes this is true. Sometimes not. * * Input: * name the file to find * path the directories to search, or NULL * * Results: * The freshly allocated path to the file, or NULL. */ char * Dir_FindFile(const char *name, SearchPath *path) { SearchPathNode *ln; char *file; /* the current filename to check */ const char *base; /* Terminal name of file */ Boolean hasLastDot = FALSE; /* true if we should search dot last */ Boolean hasSlash; /* true if 'name' contains a / */ - struct make_stat mst; /* Buffer for stat, if necessary */ + struct cached_stat cst; /* Buffer for stat, if necessary */ const char *trailing_dot = "."; /* * Find the final component of the name and note whether it has a * slash in it (the name, I mean) */ base = strrchr(name, '/'); if (base) { hasSlash = TRUE; base++; } else { hasSlash = FALSE; base = name; } DIR_DEBUG1("Searching for %s ...", name); if (path == NULL) { DIR_DEBUG0("couldn't open path, file not found\n"); misses++; return NULL; } if ((ln = path->first) != NULL) { CachedDir *dir = ln->datum; if (dir == dotLast) { hasLastDot = TRUE; DIR_DEBUG0("[dot last]..."); } } DIR_DEBUG0("\n"); /* * If there's no leading directory components or if the leading * directory component is exactly `./', consult the cached contents * of each of the directories on the search path. */ if (!hasSlash || (base - name == 2 && *name == '.')) { /* * We look through all the directories on the path seeking one which * contains the final component of the given name. If such a beast * is found, we concatenate the directory name and the final * component and return the resulting string. If we don't find any * such thing, we go on to phase two... * * No matter what, we always look for the file in the current * directory before anywhere else (unless we found the magic * DOTLAST path, in which case we search it last) and we *do not* * add the ./ to it if it exists. * This is so there are no conflicts between what the user * specifies (fish.c) and what pmake finds (./fish.c). */ if (!hasLastDot && (file = DirFindDot(name, base)) != NULL) return file; for (; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (dir == dotLast) continue; if ((file = DirLookup(dir, base)) != NULL) return file; } if (hasLastDot && (file = DirFindDot(name, base)) != NULL) return file; } /* * We didn't find the file on any directory in the search path. * If the name doesn't contain a slash, that means it doesn't exist. * If it *does* contain a slash, however, there is still hope: it * could be in a subdirectory of one of the members of the search * path. (eg. /usr/include and sys/types.h. The above search would * fail to turn up types.h in /usr/include, but it *is* in * /usr/include/sys/types.h). * [ This no longer applies: If we find such a beast, we assume there * will be more (what else can we assume?) and add all but the last * component of the resulting name onto the search path (at the * end).] * This phase is only performed if the file is *not* absolute. */ if (!hasSlash) { DIR_DEBUG0(" failed.\n"); misses++; return NULL; } if (*base == '\0') { /* we were given a trailing "/" */ base = trailing_dot; } if (name[0] != '/') { Boolean checkedDot = FALSE; DIR_DEBUG0(" Trying subdirectories...\n"); if (!hasLastDot) { if (dot) { checkedDot = TRUE; if ((file = DirLookupSubdir(dot, name)) != NULL) return file; } if (cur && (file = DirLookupSubdir(cur, name)) != NULL) return file; } for (ln = path->first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (dir == dotLast) continue; if (dir == dot) { if (checkedDot) continue; checkedDot = TRUE; } if ((file = DirLookupSubdir(dir, name)) != NULL) return file; } if (hasLastDot) { if (dot && !checkedDot) { checkedDot = TRUE; if ((file = DirLookupSubdir(dot, name)) != NULL) return file; } if (cur && (file = DirLookupSubdir(cur, name)) != NULL) return file; } if (checkedDot) { /* * Already checked by the given name, since . was in the path, * so no point in proceeding... */ DIR_DEBUG0(" Checked . already, returning NULL\n"); return NULL; } } else { /* name[0] == '/' */ /* * For absolute names, compare directory path prefix against the * the directory path of each member on the search path for an exact * match. If we have an exact match on any member of the search path, * use the cached contents of that member to lookup the final file * component. If that lookup fails we can safely assume that the * file does not exist at all. This is signified by DirLookupAbs() * returning an empty string. */ DIR_DEBUG0(" Trying exact path matches...\n"); if (!hasLastDot && cur && ((file = DirLookupAbs(cur, name, base)) != NULL)) { if (file[0] == '\0') { free(file); return NULL; } return file; } for (ln = path->first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (dir == dotLast) continue; if ((file = DirLookupAbs(dir, name, base)) != NULL) { if (file[0] == '\0') { free(file); return NULL; } return file; } } if (hasLastDot && cur && ((file = DirLookupAbs(cur, name, base)) != NULL)) { if (file[0] == '\0') { free(file); return NULL; } return file; } } /* * Didn't find it that way, either. Sigh. Phase 3. Add its directory * onto the search path in any case, just in case, then look for the * thing in the hash table. If we find it, grand. We return a new * copy of the name. Otherwise we sadly return a NULL pointer. Sigh. * Note that if the directory holding the file doesn't exist, this will * do an extra search of the final directory on the path. Unless something * weird happens, this search won't succeed and life will be groovy. * * Sigh. We cannot add the directory onto the search path because * of this amusing case: * $(INSTALLDIR)/$(FILE): $(FILE) * * $(FILE) exists in $(INSTALLDIR) but not in the current one. * When searching for $(FILE), we will find it in $(INSTALLDIR) * b/c we added it here. This is not good... */ -#ifdef notdef +#if 0 if (base == trailing_dot) { base = strrchr(name, '/'); base++; } base[-1] = '\0'; (void)Dir_AddDir(path, name); base[-1] = '/'; bigmisses++; ln = Lst_Last(path); if (ln == NULL) { return NULL; } else { dir = LstNode_Datum(ln); } if (Hash_FindEntry(&dir->files, base) != NULL) { return bmake_strdup(name); } else { return NULL; } -#else /* !notdef */ +#else DIR_DEBUG1(" Looking for \"%s\" ...\n", name); bigmisses++; - if (cached_stat(name, &mst) == 0) { + if (cached_stat(name, &cst) == 0) { return bmake_strdup(name); } DIR_DEBUG0(" failed. Returning NULL\n"); return NULL; -#endif /* notdef */ +#endif } /* Search for a path starting at a given directory and then working our way * up towards the root. * * Input: * here starting directory * search_path the relative path we are looking for * * Results: * The found path, or NULL. */ char * Dir_FindHereOrAbove(const char *here, const char *search_path) { - struct make_stat mst; + struct cached_stat cst; char *dirbase, *dirbase_end; char *try, *try_end; /* copy out our starting point */ dirbase = bmake_strdup(here); dirbase_end = dirbase + strlen(dirbase); /* loop until we determine a result */ for (;;) { /* try and stat(2) it ... */ try = str_concat3(dirbase, "/", search_path); - if (cached_stat(try, &mst) != -1) { + if (cached_stat(try, &cst) != -1) { /* * success! if we found a file, chop off * the filename so we return a directory. */ - if ((mst.mst_mode & S_IFMT) != S_IFDIR) { + if ((cst.cst_mode & S_IFMT) != S_IFDIR) { try_end = try + strlen(try); while (try_end > try && *try_end != '/') try_end--; if (try_end > try) *try_end = '\0'; /* chop! */ } free(dirbase); return try; } free(try); /* * nope, we didn't find it. if we used up dirbase we've * reached the root and failed. */ if (dirbase_end == dirbase) break; /* failed! */ /* * truncate dirbase from the end to move up a dir */ while (dirbase_end > dirbase && *dirbase_end != '/') dirbase_end--; *dirbase_end = '\0'; /* chop! */ } free(dirbase); return NULL; } -/*- - *----------------------------------------------------------------------- - * Dir_MTime -- - * Find the modification time of the file described by gn along the - * search path dirSearchPath. +/* Search gn along dirSearchPath and store its modification time in gn->mtime. + * If no file is found, store 0 instead. * - * Input: - * gn the file whose modification time is desired - * - * Results: - * The modification time or 0 if it doesn't exist - * - * Side Effects: - * The modification time is placed in the node's mtime slot. - * If the node didn't have a path entry before, and Dir_FindFile - * found one for it, the full name is placed in the path slot. - *----------------------------------------------------------------------- - */ -time_t -Dir_MTime(GNode *gn, Boolean recheck) + * The found file is stored in gn->path, unless the node already had a path. */ +void +Dir_UpdateMTime(GNode *gn, Boolean recheck) { - char *fullName; /* the full pathname of name */ - struct make_stat mst; /* buffer for finding the mod time */ + char *fullName; + struct cached_stat cst; if (gn->type & OP_ARCHV) { - return Arch_MTime(gn); - } else if (gn->type & OP_PHONY) { + Arch_UpdateMTime(gn); + return; + } + + if (gn->type & OP_PHONY) { gn->mtime = 0; - return 0; - } else if (gn->path == NULL) { + return; + } + + if (gn->path == NULL) { if (gn->type & OP_NOPATH) fullName = NULL; else { fullName = Dir_FindFile(gn->name, Suff_FindPath(gn)); if (fullName == NULL && gn->flags & FROM_DEPEND && !Lst_IsEmpty(gn->implicitParents)) { char *cp; cp = strrchr(gn->name, '/'); if (cp) { /* * This is an implied source, and it may have moved, * see if we can find it via the current .PATH */ cp++; fullName = Dir_FindFile(cp, Suff_FindPath(gn)); if (fullName) { /* * Put the found file in gn->path * so that we give that to the compiler. */ gn->path = bmake_strdup(fullName); if (!Job_RunTarget(".STALE", gn->fname)) fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s, " "found %s\n", progname, gn->fname, gn->lineno, makeDependfile, gn->name, fullName); } } } DIR_DEBUG2("Found '%s' as '%s'\n", gn->name, fullName ? fullName : "(not found)"); } } else { fullName = gn->path; } - if (fullName == NULL) { + if (fullName == NULL) fullName = bmake_strdup(gn->name); - } - if (cached_stats(&mtimes, fullName, &mst, recheck ? CST_UPDATE : 0) < 0) { + if (cached_stats(fullName, &cst, recheck ? CST_UPDATE : CST_NONE) < 0) { if (gn->type & OP_MEMBER) { if (fullName != gn->path) free(fullName); - return Arch_MemMTime(gn); - } else { - mst.mst_mtime = 0; + Arch_UpdateMemberMTime(gn); + return; } + + cst.cst_mtime = 0; } if (fullName != NULL && gn->path == NULL) gn->path = fullName; - gn->mtime = mst.mst_mtime; - return gn->mtime; + gn->mtime = cst.cst_mtime; } /* Read the list of filenames in the directory and store the result * in openDirectories. * * If a path is given, append the directory to that path. * * Input: * path The path to which the directory should be * added, or NULL to only add the directory to * openDirectories * name The name of the directory to add. * The name is not normalized in any way. */ CachedDir * Dir_AddDir(SearchPath *path, const char *name) { CachedDir *dir = NULL; /* the added directory */ DIR *d; struct dirent *dp; if (path != NULL && strcmp(name, ".DOTLAST") == 0) { SearchPathNode *ln; + /* XXX: Linear search gets slow with thousands of entries. */ for (ln = path->first; ln != NULL; ln = ln->next) { CachedDir *pathDir = ln->datum; if (strcmp(pathDir->name, name) == 0) return pathDir; } dotLast->refCount++; Lst_Prepend(path, dotLast); } if (path != NULL) dir = OpenDirs_Find(&openDirs, name); if (dir != NULL) { if (Lst_FindDatum(path, dir) == NULL) { dir->refCount++; Lst_Append(path, dir); } return dir; } DIR_DEBUG1("Caching %s ...", name); if ((d = opendir(name)) != NULL) { - dir = bmake_malloc(sizeof(CachedDir)); + dir = bmake_malloc(sizeof *dir); dir->name = bmake_strdup(name); dir->hits = 0; dir->refCount = 1; HashTable_Init(&dir->files); while ((dp = readdir(d)) != NULL) { #if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */ /* * The sun directory library doesn't check for a 0 inode * (0-inode slots just take up space), so we have to do * it ourselves. */ if (dp->d_fileno == 0) { continue; } #endif /* sun && d_ino */ (void)HashTable_CreateEntry(&dir->files, dp->d_name, NULL); } (void)closedir(d); OpenDirs_Add(&openDirs, dir); if (path != NULL) Lst_Append(path, dir); } DIR_DEBUG0("done\n"); return dir; } /* Return a copy of dirSearchPath, incrementing the reference counts for * the contained directories. */ SearchPath * Dir_CopyDirSearchPath(void) { SearchPath *path = Lst_New(); SearchPathNode *ln; for (ln = dirSearchPath->first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; dir->refCount++; Lst_Append(path, dir); } return path; } /*- *----------------------------------------------------------------------- * Dir_MakeFlags -- * Make a string by taking all the directories in the given search * path and preceding them by the given flag. Used by the suffix * module to create variables for compilers based on suffix search * paths. * * Input: * flag flag which should precede each directory * path list of directories * * Results: * The string mentioned above. Note that there is no space between * the given flag and each directory. The empty string is returned if * Things don't go well. * * Side Effects: * None *----------------------------------------------------------------------- */ char * Dir_MakeFlags(const char *flag, SearchPath *path) { Buffer buf; SearchPathNode *ln; - Buf_Init(&buf, 0); + Buf_Init(&buf); if (path != NULL) { for (ln = path->first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; Buf_AddStr(&buf, " "); Buf_AddStr(&buf, flag); Buf_AddStr(&buf, dir->name); } } return Buf_Destroy(&buf, FALSE); } /* Nuke a directory descriptor, if possible. Callback procedure for the * suffixes module when destroying a search path. * * Input: * dirp The directory descriptor to nuke */ void Dir_Destroy(void *dirp) { CachedDir *dir = dirp; dir->refCount--; if (dir->refCount == 0) { OpenDirs_Remove(&openDirs, dir->name); HashTable_Done(&dir->files); free(dir->name); free(dir); } } /* Clear out all elements from the given search path. * The path is set to the empty list but is not destroyed. */ void Dir_ClearPath(SearchPath *path) { while (!Lst_IsEmpty(path)) { CachedDir *dir = Lst_Dequeue(path); Dir_Destroy(dir); } } /* Concatenate two paths, adding the second to the end of the first, * skipping duplicates. */ void Dir_Concat(SearchPath *dst, SearchPath *src) { SearchPathNode *ln; for (ln = src->first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (Lst_FindDatum(dst, dir) == NULL) { dir->refCount++; Lst_Append(dst, dir); } } } static int percentage(int num, int den) { return den != 0 ? num * 100 / den : 0; } /********** DEBUG INFO **********/ void Dir_PrintDirectories(void) { CachedDirListNode *ln; debug_printf("#*** Directory Cache:\n"); debug_printf("# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n", hits, misses, nearmisses, bigmisses, percentage(hits, hits + bigmisses + nearmisses)); debug_printf("# %-20s referenced\thits\n", "directory"); for (ln = openDirs.list->first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; debug_printf("# %-20s %10d\t%4d\n", dir->name, dir->refCount, dir->hits); } } void Dir_PrintPath(SearchPath *path) { SearchPathNode *node; for (node = path->first; node != NULL; node = node->next) { const CachedDir *dir = node->datum; debug_printf("%s ", dir->name); } } Index: head/contrib/bmake/dir.h =================================================================== --- head/contrib/bmake/dir.h (revision 367862) +++ head/contrib/bmake/dir.h (revision 367863) @@ -1,123 +1,123 @@ -/* $NetBSD: dir.h,v 1.32 2020/10/25 10:00:20 rillig Exp $ */ +/* $NetBSD: dir.h,v 1.34 2020/11/14 19:24:24 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * * 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: @(#)dir.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: @(#)dir.h 8.1 (Berkeley) 6/6/93 */ #ifndef MAKE_DIR_H #define MAKE_DIR_H /* A cache for the filenames in a directory. */ typedef struct CachedDir { char *name; /* Name of directory, either absolute or * relative to the current directory. * The name is not normalized in any way, * that is, "." and "./." are different. * * Not sure what happens when .CURDIR is * assigned a new value; see Parse_DoVar. */ int refCount; /* Number of SearchPaths with this directory */ int hits; /* The number of times a file in this * directory has been found */ HashTable files; /* Hash set of files in directory; * all values are NULL. */ } CachedDir; void Dir_Init(void); void Dir_InitDir(const char *); void Dir_InitCur(const char *); void Dir_InitDot(void); void Dir_End(void); void Dir_SetPATH(void); Boolean Dir_HasWildcards(const char *); void Dir_Expand(const char *, SearchPath *, StringList *); char *Dir_FindFile(const char *, SearchPath *); char *Dir_FindHereOrAbove(const char *, const char *); -time_t Dir_MTime(GNode *, Boolean); +void Dir_UpdateMTime(GNode *, Boolean); CachedDir *Dir_AddDir(SearchPath *, const char *); char *Dir_MakeFlags(const char *, SearchPath *); void Dir_ClearPath(SearchPath *); void Dir_Concat(SearchPath *, SearchPath *); void Dir_PrintDirectories(void); void Dir_PrintPath(SearchPath *); void Dir_Destroy(void *); SearchPath *Dir_CopyDirSearchPath(void); /* Stripped-down variant of struct stat. */ -struct make_stat { - time_t mst_mtime; - mode_t mst_mode; +struct cached_stat { + time_t cst_mtime; + mode_t cst_mode; }; -int cached_lstat(const char *, struct make_stat *); -int cached_stat(const char *, struct make_stat *); +int cached_lstat(const char *, struct cached_stat *); +int cached_stat(const char *, struct cached_stat *); #endif /* MAKE_DIR_H */ Index: head/contrib/bmake/filemon/filemon_dev.c =================================================================== --- head/contrib/bmake/filemon/filemon_dev.c (revision 367862) +++ head/contrib/bmake/filemon/filemon_dev.c (revision 367863) @@ -1,151 +1,151 @@ -/* $NetBSD: filemon_dev.c,v 1.3 2020/07/10 15:53:30 sjg Exp $ */ +/* $NetBSD: filemon_dev.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */ /*- * Copyright (c) 2020 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Taylor R. Campbell. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "filemon.h" #include #include #include #include #include #ifdef HAVE_FILEMON_H # include #endif #ifndef _PATH_FILEMON #define _PATH_FILEMON "/dev/filemon" #endif struct filemon { int fd; }; const char * filemon_path(void) { return _PATH_FILEMON; } struct filemon * filemon_open(void) { struct filemon *F; unsigned i; int error; /* Allocate and zero a struct filemon object. */ - F = calloc(1, sizeof(*F)); + F = calloc(1, sizeof *F); if (F == NULL) return NULL; /* Try opening /dev/filemon, up to six times (cargo cult!). */ for (i = 0; (F->fd = open(_PATH_FILEMON, O_RDWR|O_CLOEXEC)) == -1; i++) { if (i == 5) { error = errno; goto fail0; } } /* Success! */ return F; fail0: free(F); errno = error; return NULL; } int filemon_setfd(struct filemon *F, int fd) { /* Point the kernel at this file descriptor. */ if (ioctl(F->fd, FILEMON_SET_FD, &fd) == -1) return -1; /* No need for it in userland any more; close it. */ (void)close(fd); /* Success! */ return 0; } void filemon_setpid_parent(struct filemon *F, pid_t pid) { /* Nothing to do! */ } int filemon_setpid_child(const struct filemon *F, pid_t pid) { /* Just pass it on to the kernel. */ return ioctl(F->fd, FILEMON_SET_PID, &pid); } int filemon_close(struct filemon *F) { int error = 0; /* Close the filemon device fd. */ if (close(F->fd) == -1 && error == 0) error = errno; /* Free the filemon descriptor. */ free(F); /* Set errno and return -1 if anything went wrong. */ if (error) { errno = error; return -1; } /* Success! */ return 0; } int filemon_readfd(const struct filemon *F) { return -1; } int filemon_process(struct filemon *F) { return 0; } Index: head/contrib/bmake/filemon/filemon_ktrace.c =================================================================== --- head/contrib/bmake/filemon/filemon_ktrace.c (revision 367862) +++ head/contrib/bmake/filemon/filemon_ktrace.c (revision 367863) @@ -1,878 +1,878 @@ -/* $NetBSD: filemon_ktrace.c,v 1.3 2020/10/18 11:54:43 rillig Exp $ */ +/* $NetBSD: filemon_ktrace.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */ /*- * Copyright (c) 2019 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Taylor R. Campbell. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #define _KERNTYPES /* register_t */ #include "filemon.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef AT_CWD #define AT_CWD -1 #endif struct filemon; struct filemon_key; struct filemon_state; typedef struct filemon_state *filemon_syscall_t(struct filemon *, const struct filemon_key *, const struct ktr_syscall *); static filemon_syscall_t filemon_sys_chdir; static filemon_syscall_t filemon_sys_execve; static filemon_syscall_t filemon_sys_exit; static filemon_syscall_t filemon_sys_fork; static filemon_syscall_t filemon_sys_link; static filemon_syscall_t filemon_sys_open; static filemon_syscall_t filemon_sys_openat; static filemon_syscall_t filemon_sys_symlink; static filemon_syscall_t filemon_sys_unlink; static filemon_syscall_t filemon_sys_rename; static filemon_syscall_t *const filemon_syscalls[] = { [SYS_chdir] = &filemon_sys_chdir, [SYS_execve] = &filemon_sys_execve, [SYS_exit] = &filemon_sys_exit, [SYS_fork] = &filemon_sys_fork, [SYS_link] = &filemon_sys_link, [SYS_open] = &filemon_sys_open, [SYS_openat] = &filemon_sys_openat, [SYS_symlink] = &filemon_sys_symlink, [SYS_unlink] = &filemon_sys_unlink, [SYS_rename] = &filemon_sys_rename, }; struct filemon { int ktrfd; /* kernel writes ktrace events here */ FILE *in; /* we read ktrace events from here */ FILE *out; /* we write filemon events to here */ rb_tree_t active; pid_t child; /* I/O state machine. */ enum { FILEMON_START = 0, FILEMON_HEADER, FILEMON_PAYLOAD, FILEMON_ERROR, } state; unsigned char *p; size_t resid; /* I/O buffer. */ struct ktr_header hdr; union { struct ktr_syscall syscall; struct ktr_sysret sysret; char namei[PATH_MAX]; unsigned char buf[4096]; } payload; }; struct filemon_state { struct filemon_key { pid_t pid; lwpid_t lid; } key; struct rb_node node; int syscode; void (*show)(struct filemon *, const struct filemon_state *, const struct ktr_sysret *); unsigned i; unsigned npath; char *path[/*npath*/]; }; static int compare_filemon_states(void *cookie, const void *na, const void *nb) { const struct filemon_state *Sa = na; const struct filemon_state *Sb = nb; if (Sa->key.pid < Sb->key.pid) return -1; if (Sa->key.pid > Sb->key.pid) return +1; if (Sa->key.lid < Sb->key.lid) return -1; if (Sa->key.lid > Sb->key.lid) return +1; return 0; } static int compare_filemon_key(void *cookie, const void *n, const void *k) { const struct filemon_state *S = n; const struct filemon_key *key = k; if (S->key.pid < key->pid) return -1; if (S->key.pid > key->pid) return +1; if (S->key.lid < key->lid) return -1; if (S->key.lid > key->lid) return +1; return 0; } static const rb_tree_ops_t filemon_rb_ops = { .rbto_compare_nodes = &compare_filemon_states, .rbto_compare_key = &compare_filemon_key, .rbto_node_offset = offsetof(struct filemon_state, node), .rbto_context = NULL, }; /* * filemon_path() * * Return a pointer to a constant string denoting the `path' of * the filemon. */ const char * filemon_path(void) { return "ktrace"; } /* * filemon_open() * * Allocate a filemon descriptor. Returns NULL and sets errno on * failure. */ struct filemon * filemon_open(void) { struct filemon *F; int ktrpipe[2]; int error; /* Allocate and zero a struct filemon object. */ - F = calloc(1, sizeof(*F)); + F = calloc(1, sizeof *F); if (F == NULL) return NULL; /* Create a pipe for ktrace events. */ if (pipe2(ktrpipe, O_CLOEXEC|O_NONBLOCK) == -1) { error = errno; goto fail0; } /* Create a file stream for reading the ktrace events. */ if ((F->in = fdopen(ktrpipe[0], "r")) == NULL) { error = errno; goto fail1; } ktrpipe[0] = -1; /* claimed by fdopen */ /* * Set the fd for writing ktrace events and initialize the * rbtree. The rest can be safely initialized to zero. */ F->ktrfd = ktrpipe[1]; rb_tree_init(&F->active, &filemon_rb_ops); /* Success! */ return F; fail2: __unused (void)fclose(F->in); fail1: (void)close(ktrpipe[0]); (void)close(ktrpipe[1]); fail0: free(F); errno = error; return NULL; } /* * filemon_closefd(F) * * Internal subroutine to try to flush and close the output file. * If F is not open for output, do nothing. Never leaves F open * for output even on failure. Returns 0 on success; sets errno * and return -1 on failure. */ static int filemon_closefd(struct filemon *F) { int error = 0; /* If we're not open, nothing to do. */ if (F->out == NULL) return 0; /* * Flush it, close it, and null it unconditionally, but be * careful to return the earliest error in errno. */ if (fflush(F->out) == EOF && error == 0) error = errno; if (fclose(F->out) == EOF && error == 0) error = errno; F->out = NULL; /* Set errno and return -1 if anything went wrong. */ if (error) { errno = error; return -1; } /* Success! */ return 0; } /* * filemon_setfd(F, fd) * * Cause filemon activity on F to be sent to fd. Claims ownership * of fd; caller should not use fd afterward, and any duplicates * of fd may see their file positions changed. */ int filemon_setfd(struct filemon *F, int fd) { /* * Close an existing output file if done. Fail now if there's * an error closing. */ if ((filemon_closefd(F)) == -1) return -1; assert(F->out == NULL); /* Open a file stream and claim ownership of the fd. */ if ((F->out = fdopen(fd, "a")) == NULL) return -1; /* * Print the opening output. Any failure will be deferred * until closing. For hysterical raisins, we show the parent * pid, not the child pid. */ fprintf(F->out, "# filemon version 4\n"); fprintf(F->out, "# Target pid %jd\n", (intmax_t)getpid()); fprintf(F->out, "V 4\n"); /* Success! */ return 0; } /* * filemon_setpid_parent(F, pid) * * Set the traced pid, from the parent. Never fails. */ void filemon_setpid_parent(struct filemon *F, pid_t pid) { F->child = pid; } /* * filemon_setpid_child(F, pid) * * Set the traced pid, from the child. Returns 0 on success; sets * errno and returns -1 on failure. */ int filemon_setpid_child(const struct filemon *F, pid_t pid) { int ops, trpoints; ops = KTROP_SET|KTRFLAG_DESCEND; trpoints = KTRFACv2; trpoints |= KTRFAC_SYSCALL|KTRFAC_NAMEI|KTRFAC_SYSRET; trpoints |= KTRFAC_INHERIT; if (fktrace(F->ktrfd, ops, trpoints, pid) == -1) return -1; return 0; } /* * filemon_close(F) * * Close F for output if necessary, and free a filemon descriptor. * Returns 0 on success; sets errno and returns -1 on failure, but * frees the filemon descriptor either way; */ int filemon_close(struct filemon *F) { struct filemon_state *S; int error = 0; /* Close for output. */ if (filemon_closefd(F) == -1 && error == 0) error = errno; /* Close the ktrace pipe. */ if (fclose(F->in) == EOF && error == 0) error = errno; if (close(F->ktrfd) == -1 && error == 0) error = errno; /* Free any active records. */ while ((S = RB_TREE_MIN(&F->active)) != NULL) { rb_tree_remove_node(&F->active, S); free(S); } /* Free the filemon descriptor. */ free(F); /* Set errno and return -1 if anything went wrong. */ if (error) { errno = error; return -1; } /* Success! */ return 0; } /* * filemon_readfd(F) * * Returns a file descriptor which will select/poll ready for read * when there are filemon events to be processed by * filemon_process, or -1 if anything has gone wrong. */ int filemon_readfd(const struct filemon *F) { if (F->state == FILEMON_ERROR) return -1; return fileno(F->in); } /* * filemon_dispatch(F) * * Internal subroutine to dispatch a filemon ktrace event. * Silently ignore events that we don't recognize. */ static void filemon_dispatch(struct filemon *F) { const struct filemon_key key = { .pid = F->hdr.ktr_pid, .lid = F->hdr.ktr_lid, }; struct filemon_state *S; switch (F->hdr.ktr_type) { case KTR_SYSCALL: { struct ktr_syscall *call = &F->payload.syscall; struct filemon_state *S1; /* Validate the syscall code. */ if (call->ktr_code < 0 || (size_t)call->ktr_code >= __arraycount(filemon_syscalls) || filemon_syscalls[call->ktr_code] == NULL) break; /* * Invoke the syscall-specific logic to create a new * active state. */ S = (*filemon_syscalls[call->ktr_code])(F, &key, call); if (S == NULL) break; /* * Insert the active state, or ignore it if there * already is one. * * Collisions shouldn't happen because the states are * keyed by , in which syscalls should happen * sequentially in CALL/RET pairs, but let's be * defensive. */ S1 = rb_tree_insert_node(&F->active, S); if (S1 != S) { /* XXX Which one to drop? */ free(S); break; } break; } case KTR_NAMEI: /* Find an active syscall state, or drop it. */ S = rb_tree_find_node(&F->active, &key); if (S == NULL) break; /* Find the position of the next path, or drop it. */ if (S->i >= S->npath) break; /* Record the path. */ S->path[S->i++] = strndup(F->payload.namei, sizeof F->payload.namei); break; case KTR_SYSRET: { struct ktr_sysret *ret = &F->payload.sysret; unsigned i; /* Find and remove an active syscall state, or drop it. */ S = rb_tree_find_node(&F->active, &key); if (S == NULL) break; rb_tree_remove_node(&F->active, S); /* * If the active syscall state matches this return, * invoke the syscall-specific logic to show a filemon * event. */ /* XXX What to do if syscall code doesn't match? */ if (S->i == S->npath && S->syscode == ret->ktr_code) S->show(F, S, ret); /* Free the state now that it is no longer active. */ for (i = 0; i < S->i; i++) free(S->path[i]); free(S); break; } default: /* Ignore all other ktrace events. */ break; } } /* * filemon_process(F) * * Process all pending events after filemon_readfd(F) has * selected/polled ready for read. * * Returns -1 on failure, 0 on end of events, and anything else if * there may be more events. * * XXX What about fairness to other activities in the event loop? * If we stop while there's events buffered in F->in, then select * or poll may not return ready even though there's work queued up * in the buffer of F->in, but if we don't stop then ktrace events * may overwhelm all other activity in the event loop. */ int filemon_process(struct filemon *F) { size_t nread; top: /* If the child has exited, nothing to do. */ /* XXX What if one thread calls exit while another is running? */ if (F->child == 0) return 0; /* If we're waiting for input, read some. */ if (F->resid) { nread = fread(F->p, 1, F->resid, F->in); if (nread == 0) { if (feof(F->in)) return 0; assert(ferror(F->in)); /* * If interrupted or would block, there may be * more events. Otherwise fail. */ if (errno == EAGAIN || errno == EINTR) return 1; F->state = FILEMON_ERROR; F->p = NULL; F->resid = 0; return -1; } assert(nread <= F->resid); F->p += nread; F->resid -= nread; if (F->resid) /* may be more events */ return 1; } /* Process a state transition now that we've read a buffer. */ switch (F->state) { case FILEMON_START: /* just started filemon; read header next */ F->state = FILEMON_HEADER; F->p = (void *)&F->hdr; F->resid = sizeof F->hdr; goto top; case FILEMON_HEADER: /* read header */ /* Sanity-check ktrace header; then read payload. */ if (F->hdr.ktr_len < 0 || (size_t)F->hdr.ktr_len > sizeof F->payload) { F->state = FILEMON_ERROR; F->p = NULL; F->resid = 0; errno = EIO; return -1; } F->state = FILEMON_PAYLOAD; F->p = (void *)&F->payload; F->resid = (size_t)F->hdr.ktr_len; goto top; case FILEMON_PAYLOAD: /* read header and payload */ /* Dispatch ktrace event; then read next header. */ filemon_dispatch(F); F->state = FILEMON_HEADER; F->p = (void *)&F->hdr; F->resid = sizeof F->hdr; goto top; default: /* paranoia */ F->state = FILEMON_ERROR; /*FALLTHROUGH*/ case FILEMON_ERROR: /* persistent error indicator */ F->p = NULL; F->resid = 0; errno = EIO; return -1; } } static struct filemon_state * syscall_enter(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call, unsigned npath, void (*show)(struct filemon *, const struct filemon_state *, const struct ktr_sysret *)) { struct filemon_state *S; unsigned i; S = calloc(1, offsetof(struct filemon_state, path[npath])); if (S == NULL) return NULL; S->key = *key; S->show = show; S->syscode = call->ktr_code; S->i = 0; S->npath = npath; for (i = 0; i < npath; i++) S->path[i] = NULL; /* paranoia */ return S; } static void show_paths(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret, const char *prefix) { unsigned i; /* Caller must ensure all paths have been specified. */ assert(S->i == S->npath); /* * Ignore it if it failed or yielded EJUSTRETURN (-2), or if * we're not producing output. */ if (ret->ktr_error && ret->ktr_error != -2) return; if (F->out == NULL) return; /* * Print the prefix, pid, and paths -- with the paths quoted if * there's more than one. */ fprintf(F->out, "%s %jd", prefix, (intmax_t)S->key.pid); for (i = 0; i < S->npath; i++) { const char *q = S->npath > 1 ? "'" : ""; fprintf(F->out, " %s%s%s", q, S->path[i], q); } fprintf(F->out, "\n"); } static void show_retval(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret, const char *prefix) { /* * Ignore it if it failed or yielded EJUSTRETURN (-2), or if * we're not producing output. */ if (ret->ktr_error && ret->ktr_error != -2) return; if (F->out == NULL) return; fprintf(F->out, "%s %jd %jd\n", prefix, (intmax_t)S->key.pid, (intmax_t)ret->ktr_retval); } static void show_chdir(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { show_paths(F, S, ret, "C"); } static void show_execve(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { return show_paths(F, S, ret, "E"); } static void show_fork(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { show_retval(F, S, ret, "F"); } static void show_link(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { show_paths(F, S, ret, "L"); /* XXX same as symlink */ } static void show_open_read(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { show_paths(F, S, ret, "R"); } static void show_open_write(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { show_paths(F, S, ret, "W"); } static void show_open_readwrite(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { show_paths(F, S, ret, "R"); show_paths(F, S, ret, "W"); } static void show_openat_read(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { if (S->path[0][0] != '/') show_paths(F, S, ret, "A"); show_paths(F, S, ret, "R"); } static void show_openat_write(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { if (S->path[0][0] != '/') show_paths(F, S, ret, "A"); show_paths(F, S, ret, "W"); } static void show_openat_readwrite(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { if (S->path[0][0] != '/') show_paths(F, S, ret, "A"); show_paths(F, S, ret, "R"); show_paths(F, S, ret, "W"); } static void show_symlink(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { show_paths(F, S, ret, "L"); /* XXX same as link */ } static void show_unlink(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { show_paths(F, S, ret, "D"); } static void show_rename(struct filemon *F, const struct filemon_state *S, const struct ktr_sysret *ret) { show_paths(F, S, ret, "M"); } static struct filemon_state * filemon_sys_chdir(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { return syscall_enter(F, key, call, 1, &show_chdir); } static struct filemon_state * filemon_sys_execve(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { return syscall_enter(F, key, call, 1, &show_execve); } static struct filemon_state * filemon_sys_exit(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { const register_t *args = (const void *)&call[1]; int status = (int)args[0]; if (F->out) { fprintf(F->out, "X %jd %d\n", (intmax_t)key->pid, status); if (key->pid == F->child) { fprintf(F->out, "# Bye bye\n"); F->child = 0; } } return NULL; } static struct filemon_state * filemon_sys_fork(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { return syscall_enter(F, key, call, 0, &show_fork); } static struct filemon_state * filemon_sys_link(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { return syscall_enter(F, key, call, 2, &show_link); } static struct filemon_state * filemon_sys_open(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { const register_t *args = (const void *)&call[1]; int flags; if (call->ktr_argsize < 2) return NULL; flags = (int)args[1]; if ((flags & O_RDWR) == O_RDWR) return syscall_enter(F, key, call, 1, &show_open_readwrite); else if ((flags & O_WRONLY) == O_WRONLY) return syscall_enter(F, key, call, 1, &show_open_write); else if ((flags & O_RDONLY) == O_RDONLY) return syscall_enter(F, key, call, 1, &show_open_read); else return NULL; /* XXX Do we care if no read or write? */ } static struct filemon_state * filemon_sys_openat(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { const register_t *args = (const void *)&call[1]; int flags, fd; if (call->ktr_argsize < 3) return NULL; fd = (int)args[0]; flags = (int)args[2]; if (fd == AT_CWD) { if ((flags & O_RDWR) == O_RDWR) return syscall_enter(F, key, call, 1, &show_open_readwrite); else if ((flags & O_WRONLY) == O_WRONLY) return syscall_enter(F, key, call, 1, &show_open_write); else if ((flags & O_RDONLY) == O_RDONLY) return syscall_enter(F, key, call, 1, &show_open_read); else return NULL; } else { if ((flags & O_RDWR) == O_RDWR) return syscall_enter(F, key, call, 1, &show_openat_readwrite); else if ((flags & O_WRONLY) == O_WRONLY) return syscall_enter(F, key, call, 1, &show_openat_write); else if ((flags & O_RDONLY) == O_RDONLY) return syscall_enter(F, key, call, 1, &show_openat_read); else return NULL; } } static struct filemon_state * filemon_sys_symlink(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { return syscall_enter(F, key, call, 2, &show_symlink); } static struct filemon_state * filemon_sys_unlink(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { return syscall_enter(F, key, call, 1, &show_unlink); } static struct filemon_state * filemon_sys_rename(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { return syscall_enter(F, key, call, 2, &show_rename); } Index: head/contrib/bmake/for.c =================================================================== --- head/contrib/bmake/for.c (revision 367862) +++ head/contrib/bmake/for.c (revision 367863) @@ -1,499 +1,479 @@ -/* $NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $ */ +/* $NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $ */ /* * Copyright (c) 1992, The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /*- * Handling of .for/.endfor loops in a makefile. * * For loops are of the form: * * .for in * ... * .endfor * * When a .for line is parsed, all following lines are accumulated into a * buffer, up to but excluding the corresponding .endfor line. To find the * corresponding .endfor, the number of nested .for and .endfor directives * are counted. * * During parsing, any nested .for loops are just passed through; they get * handled recursively in For_Eval when the enclosing .for loop is evaluated * in For_Run. * * When the .for loop has been parsed completely, the variable expressions * for the iteration variables are replaced with expressions of the form * ${:Uvalue}, and then this modified body is "included" as a special file. * * Interface: * For_Eval Evaluate the loop in the passed line. * * For_Run Run accumulated loop */ #include "make.h" /* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $"); +MAKE_RCSID("$NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $"); -/* The .for loop substitutes the items as ${:U...}, which means - * that characters that break this syntax must be backslash-escaped. */ -typedef enum ForEscapes { - FOR_SUB_ESCAPE_CHAR = 0x0001, - FOR_SUB_ESCAPE_BRACE = 0x0002, - FOR_SUB_ESCAPE_PAREN = 0x0004 -} ForEscapes; - static int forLevel = 0; /* Nesting level */ /* One of the variables to the left of the "in" in a .for loop. */ typedef struct ForVar { char *name; size_t len; } ForVar; /* * State of a for loop. */ typedef struct For { Buffer body; /* Unexpanded body of the loop */ Vector /* of ForVar */ vars; /* Iteration variables */ Words items; /* Substitution items */ Buffer curBody; /* Expanded body of the current iteration */ /* Is any of the names 1 character long? If so, when the variable values * are substituted, the parser must handle $V expressions as well, not * only ${V} and $(V). */ Boolean short_var; unsigned int sub_next; /* Where to continue iterating */ } For; static For *accumFor; /* Loop being accumulated */ static void ForAddVar(For *f, const char *name, size_t len) { ForVar *var = Vector_Push(&f->vars); var->name = bmake_strldup(name, len); var->len = len; } static void For_Free(For *f) { Buf_Destroy(&f->body, TRUE); while (f->vars.len > 0) { ForVar *var = Vector_Pop(&f->vars); free(var->name); } Vector_Done(&f->vars); Words_Free(f->items); Buf_Destroy(&f->curBody, TRUE); free(f); } -static ForEscapes -GetEscapes(const char *word) -{ - const char *p; - ForEscapes escapes = 0; - - for (p = word; *p != '\0'; p++) { - switch (*p) { - case ':': - case '$': - case '\\': - escapes |= FOR_SUB_ESCAPE_CHAR; - break; - case ')': - escapes |= FOR_SUB_ESCAPE_PAREN; - break; - case '}': - escapes |= FOR_SUB_ESCAPE_BRACE; - break; - } - } - return escapes; -} - static Boolean IsFor(const char *p) { return p[0] == 'f' && p[1] == 'o' && p[2] == 'r' && ch_isspace(p[3]); } static Boolean IsEndfor(const char *p) { return p[0] == 'e' && strncmp(p, "endfor", 6) == 0 && (p[6] == '\0' || ch_isspace(p[6])); } /* Evaluate the for loop in the passed line. The line looks like this: * .for in * * Input: * line Line to parse * * Results: * 0: Not a .for statement, parse the line * 1: We found a for loop * -1: A .for statement with a bad syntax error, discard. */ int For_Eval(const char *line) { For *f; const char *p; p = line + 1; /* skip the '.' */ cpp_skip_whitespace(&p); if (!IsFor(p)) { if (IsEndfor(p)) { Parse_Error(PARSE_FATAL, "for-less endfor"); return -1; } return 0; } p += 3; /* * we found a for loop, and now we are going to parse it. */ f = bmake_malloc(sizeof *f); - Buf_Init(&f->body, 0); + Buf_Init(&f->body); Vector_Init(&f->vars, sizeof(ForVar)); f->items.words = NULL; f->items.freeIt = NULL; - Buf_Init(&f->curBody, 0); + Buf_Init(&f->curBody); f->short_var = FALSE; f->sub_next = 0; /* Grab the variables. Terminate on "in". */ for (;;) { size_t len; cpp_skip_whitespace(&p); if (*p == '\0') { Parse_Error(PARSE_FATAL, "missing `in' in for"); For_Free(f); return -1; } /* XXX: This allows arbitrary variable names; see directive-for.mk. */ for (len = 1; p[len] != '\0' && !ch_isspace(p[len]); len++) continue; if (len == 2 && p[0] == 'i' && p[1] == 'n') { p += 2; break; } if (len == 1) f->short_var = TRUE; ForAddVar(f, p, len); p += len; } if (f->vars.len == 0) { Parse_Error(PARSE_FATAL, "no iteration variables in for"); For_Free(f); return -1; } cpp_skip_whitespace(&p); { char *items; (void)Var_Subst(p, VAR_GLOBAL, VARE_WANTRES, &items); /* TODO: handle errors */ f->items = Str_Words(items, FALSE); free(items); if (f->items.len == 1 && f->items.words[0][0] == '\0') f->items.len = 0; /* .for var in ${:U} */ } { size_t nitems, nvars; if ((nitems = f->items.len) > 0 && nitems % (nvars = f->vars.len)) { Parse_Error(PARSE_FATAL, "Wrong number of words (%zu) in .for substitution list" " with %zu variables", nitems, nvars); /* * Return 'success' so that the body of the .for loop is * accumulated. * Remove all items so that the loop doesn't iterate. */ f->items.len = 0; } } accumFor = f; forLevel = 1; return 1; } /* * Add another line to a .for loop. * Returns FALSE when the matching .endfor is reached. */ Boolean For_Accum(const char *line) { const char *ptr = line; if (*ptr == '.') { ptr++; cpp_skip_whitespace(&ptr); if (IsEndfor(ptr)) { DEBUG1(FOR, "For: end for %d\n", forLevel); if (--forLevel <= 0) return FALSE; } else if (IsFor(ptr)) { forLevel++; DEBUG1(FOR, "For: new loop %d\n", forLevel); } } Buf_AddStr(&accumFor->body, line); Buf_AddByte(&accumFor->body, '\n'); return TRUE; } static size_t for_var_len(const char *var) { char ch, var_start, var_end; int depth; size_t len; var_start = *var; - if (var_start == 0) + if (var_start == '\0') /* just escape the $ */ return 0; if (var_start == '(') var_end = ')'; else if (var_start == '{') var_end = '}'; else /* Single char variable */ return 1; depth = 1; - for (len = 1; (ch = var[len++]) != 0;) { + for (len = 1; (ch = var[len++]) != '\0';) { if (ch == var_start) depth++; else if (ch == var_end && --depth == 0) return len; } /* Variable end not found, escape the $ */ return 0; } +/* The .for loop substitutes the items as ${:U...}, which means + * that characters that break this syntax must be backslash-escaped. */ +static Boolean +NeedsEscapes(const char *word, char endc) +{ + const char *p; + + for (p = word; *p != '\0'; p++) { + if (*p == ':' || *p == '$' || *p == '\\' || *p == endc) + return TRUE; + } + return FALSE; +} + /* While expanding the body of a .for loop, write the item in the ${:U...} - * expression, escaping characters as needed. See ApplyModifier_Defined. */ + * expression, escaping characters as needed. + * + * The result is later unescaped by ApplyModifier_Defined. */ static void Buf_AddEscaped(Buffer *cmds, const char *item, char ech) { - ForEscapes escapes = GetEscapes(item); char ch; - /* If there were no escapes, or the only escape is the other variable - * terminator, then just substitute the full string */ - if (!(escapes & (ech == ')' ? ~(unsigned)FOR_SUB_ESCAPE_BRACE - : ~(unsigned)FOR_SUB_ESCAPE_PAREN))) { + if (!NeedsEscapes(item, ech)) { Buf_AddStr(cmds, item); return; } /* Escape ':', '$', '\\' and 'ech' - these will be removed later by * :U processing, see ApplyModifier_Defined. */ while ((ch = *item++) != '\0') { if (ch == '$') { size_t len = for_var_len(item); if (len != 0) { Buf_AddBytes(cmds, item - 1, len + 1); item += len; continue; } Buf_AddByte(cmds, '\\'); } else if (ch == ':' || ch == '\\' || ch == ech) Buf_AddByte(cmds, '\\'); Buf_AddByte(cmds, ch); } } /* While expanding the body of a .for loop, replace expressions like * ${i}, ${i:...}, $(i) or $(i:...) with their ${:U...} expansion. */ static void SubstVarLong(For *f, const char **pp, const char **inout_mark, char ech) { size_t i; const char *p = *pp; for (i = 0; i < f->vars.len; i++) { ForVar *forVar = Vector_Get(&f->vars, i); char *var = forVar->name; size_t vlen = forVar->len; /* XXX: undefined behavior for p if vlen is longer than p? */ if (memcmp(p, var, vlen) != 0) continue; /* XXX: why test for backslash here? */ if (p[vlen] != ':' && p[vlen] != ech && p[vlen] != '\\') continue; /* Found a variable match. Replace with :U */ Buf_AddBytesBetween(&f->curBody, *inout_mark, p); Buf_AddStr(&f->curBody, ":U"); Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], ech); p += vlen; *inout_mark = p; break; } *pp = p; } /* While expanding the body of a .for loop, replace single-character * variable expressions like $i with their ${:U...} expansion. */ static void SubstVarShort(For *f, char const ch, const char **pp, const char **inout_mark) { const char *p = *pp; size_t i; /* Probably a single character name, ignore $$ and stupid ones. */ if (!f->short_var || strchr("}):$", ch) != NULL) { p++; *pp = p; return; } for (i = 0; i < f->vars.len; i++) { ForVar *var = Vector_Get(&f->vars, i); const char *varname = var->name; if (varname[0] != ch || varname[1] != '\0') continue; /* Found a variable match. Replace with ${:U} */ Buf_AddBytesBetween(&f->curBody, *inout_mark, p); Buf_AddStr(&f->curBody, "{:U"); Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], '}'); Buf_AddByte(&f->curBody, '}'); *inout_mark = ++p; break; } *pp = p; } /* * Scan the for loop body and replace references to the loop variables * with variable references that expand to the required text. * * Using variable expansions ensures that the .for loop can't generate * syntax, and that the later parsing will still see a variable. * We assume that the null variable will never be defined. * * The detection of substitutions of the loop control variable is naive. * Many of the modifiers use \ to escape $ (not $) so it is possible * to contrive a makefile where an unwanted substitution happens. */ static char * ForIterate(void *v_arg, size_t *out_len) { For *f = v_arg; const char *p; const char *mark; /* where the last replacement left off */ const char *body_end; char *cmds_str; if (f->sub_next + f->vars.len > f->items.len) { /* No more iterations */ For_Free(f); return NULL; } Buf_Empty(&f->curBody); mark = Buf_GetAll(&f->body, NULL); body_end = mark + Buf_Len(&f->body); for (p = mark; (p = strchr(p, '$')) != NULL;) { char ch, ech; ch = *++p; if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) { p++; /* Check variable name against the .for loop variables */ SubstVarLong(f, &p, &mark, ech); continue; } if (ch == '\0') break; SubstVarShort(f, ch, &p, &mark); } Buf_AddBytesBetween(&f->curBody, mark, body_end); *out_len = Buf_Len(&f->curBody); cmds_str = Buf_GetAll(&f->curBody, NULL); DEBUG1(FOR, "For: loop body:\n%s", cmds_str); f->sub_next += f->vars.len; return cmds_str; } /* Run the for loop, imitating the actions of an include file. */ void For_Run(int lineno) { For *f = accumFor; accumFor = NULL; if (f->items.len == 0) { /* Nothing to expand - possibly due to an earlier syntax error. */ For_Free(f); return; } Parse_SetInput(NULL, lineno, -1, ForIterate, f); } Index: head/contrib/bmake/hash.c =================================================================== --- head/contrib/bmake/hash.c (revision 367862) +++ head/contrib/bmake/hash.c (revision 367863) @@ -1,310 +1,318 @@ -/* $NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $ */ +/* $NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig 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. */ /* Hash tables with string keys. */ #include "make.h" /* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $"); +MAKE_RCSID("$NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig Exp $"); /* * The ratio of # entries to # buckets at which we rebuild the table to * make it larger. */ #define rebuildLimit 3 /* This hash function matches Gosling's emacs and java.lang.String. */ static unsigned int hash(const char *key, size_t *out_keylen) { unsigned int h = 0; const char *p = key; while (*p != '\0') h = (h << 5) - h + (unsigned char)*p++; if (out_keylen != NULL) *out_keylen = (size_t)(p - key); return h; } unsigned int Hash_Hash(const char *key) { return hash(key, NULL); } static HashEntry * HashTable_Find(HashTable *t, unsigned int h, const char *key) { HashEntry *e; unsigned int chainlen = 0; #ifdef DEBUG_HASH_LOOKUP DEBUG4(HASH, "%s: %p h=%08x key=%s\n", __func__, t, h, key); #endif for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { chainlen++; if (e->key_hash == h && strcmp(e->key, key) == 0) break; } if (chainlen > t->maxchain) t->maxchain = chainlen; return e; } /* Set up the hash table. */ void HashTable_Init(HashTable *t) { unsigned int n = 16, i; - HashEntry **buckets = bmake_malloc(sizeof(*buckets) * n); + HashEntry **buckets = bmake_malloc(sizeof *buckets * n); for (i = 0; i < n; i++) buckets[i] = NULL; t->buckets = buckets; t->bucketsSize = n; t->numEntries = 0; t->bucketsMask = n - 1; t->maxchain = 0; } /* Remove everything from the hash table and frees up the memory. */ void HashTable_Done(HashTable *t) { HashEntry **buckets = t->buckets; size_t i, n = t->bucketsSize; for (i = 0; i < n; i++) { HashEntry *he = buckets[i]; while (he != NULL) { HashEntry *next = he->next; free(he); he = next; } } free(t->buckets); #ifdef CLEANUP t->buckets = NULL; #endif } /* Find the entry corresponding to the key, or return NULL. */ HashEntry * HashTable_FindEntry(HashTable *t, const char *key) { unsigned int h = hash(key, NULL); return HashTable_Find(t, h, key); } /* Find the value corresponding to the key, or return NULL. */ void * HashTable_FindValue(HashTable *t, const char *key) { HashEntry *he = HashTable_FindEntry(t, key); return he != NULL ? he->value : NULL; } /* Find the value corresponding to the key and the precomputed hash, * or return NULL. */ void * HashTable_FindValueHash(HashTable *t, const char *key, unsigned int h) { HashEntry *he = HashTable_Find(t, h, key); return he != NULL ? he->value : NULL; } /* Make the hash table larger. Any bucket numbers from the old table become * invalid; the hash codes stay valid though. */ static void HashTable_Enlarge(HashTable *t) { unsigned int oldSize = t->bucketsSize; HashEntry **oldBuckets = t->buckets; unsigned int newSize = 2 * oldSize; unsigned int newMask = newSize - 1; - HashEntry **newBuckets = bmake_malloc(sizeof(*newBuckets) * newSize); + HashEntry **newBuckets = bmake_malloc(sizeof *newBuckets * newSize); size_t i; for (i = 0; i < newSize; i++) newBuckets[i] = NULL; for (i = 0; i < oldSize; i++) { HashEntry *he = oldBuckets[i]; while (he != NULL) { HashEntry *next = he->next; he->next = newBuckets[he->key_hash & newMask]; newBuckets[he->key_hash & newMask] = he; he = next; } } free(oldBuckets); t->bucketsSize = newSize; t->bucketsMask = newMask; t->buckets = newBuckets; DEBUG5(HASH, "%s: %p size=%d entries=%d maxchain=%d\n", __func__, t, t->bucketsSize, t->numEntries, t->maxchain); t->maxchain = 0; } /* Find or create an entry corresponding to the key. * Return in out_isNew whether a new entry has been created. */ HashEntry * HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew) { size_t keylen; unsigned int h = hash(key, &keylen); HashEntry *he = HashTable_Find(t, h, key); if (he != NULL) { if (out_isNew != NULL) *out_isNew = FALSE; return he; } if (t->numEntries >= rebuildLimit * t->bucketsSize) HashTable_Enlarge(t); - he = bmake_malloc(sizeof(*he) + keylen); + he = bmake_malloc(sizeof *he + keylen); he->value = NULL; he->key_hash = h; memcpy(he->key, key, keylen + 1); he->next = t->buckets[h & t->bucketsMask]; t->buckets[h & t->bucketsMask] = he; t->numEntries++; if (out_isNew != NULL) *out_isNew = TRUE; + return he; +} + +HashEntry * +HashTable_Set(HashTable *t, const char *key, void *value) +{ + HashEntry *he = HashTable_CreateEntry(t, key, NULL); + HashEntry_Set(he, value); return he; } /* Delete the entry from the table and free the associated memory. */ void HashTable_DeleteEntry(HashTable *t, HashEntry *he) { HashEntry **ref = &t->buckets[he->key_hash & t->bucketsMask]; HashEntry *p; for (; (p = *ref) != NULL; ref = &p->next) { if (p == he) { *ref = p->next; free(p); t->numEntries--; return; } } abort(); } /* Set things up for iterating over all entries in the hash table. */ void HashIter_Init(HashIter *hi, HashTable *t) { hi->table = t; hi->nextBucket = 0; hi->entry = NULL; } /* Return the next entry in the hash table, or NULL if the end of the table * is reached. */ HashEntry * HashIter_Next(HashIter *hi) { HashTable *t = hi->table; HashEntry *he = hi->entry; HashEntry **buckets = t->buckets; unsigned int bucketsSize = t->bucketsSize; if (he != NULL) he = he->next; /* skip the most recently returned entry */ while (he == NULL) { /* find the next nonempty chain */ if (hi->nextBucket >= bucketsSize) return NULL; he = buckets[hi->nextBucket++]; } hi->entry = he; return he; } void HashTable_DebugStats(HashTable *t, const char *name) { DEBUG4(HASH, "HashTable %s: size=%u numEntries=%u maxchain=%u\n", name, t->bucketsSize, t->numEntries, t->maxchain); } Index: head/contrib/bmake/hash.h =================================================================== --- head/contrib/bmake/hash.h (revision 367862) +++ head/contrib/bmake/hash.h (revision 367863) @@ -1,131 +1,132 @@ -/* $NetBSD: hash.h,v 1.31 2020/10/25 19:19:07 rillig Exp $ */ +/* $NetBSD: hash.h,v 1.33 2020/11/14 21:29:44 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * * 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: @(#)hash.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: @(#)hash.h 8.1 (Berkeley) 6/6/93 */ /* Hash tables with strings as keys and arbitrary pointers as values. */ #ifndef MAKE_HASH_H #define MAKE_HASH_H /* A single key-value entry in the hash table. */ typedef struct HashEntry { struct HashEntry *next; /* Used to link together all the entries * associated with the same bucket. */ void *value; unsigned int key_hash; /* hash value of the key */ char key[1]; /* key string, variable length */ } HashEntry; /* The hash table containing the entries. */ typedef struct HashTable { HashEntry **buckets; /* Pointers to HashEntry, one * for each bucket in the table. */ unsigned int bucketsSize; unsigned int numEntries; /* Number of entries in the table. */ unsigned int bucketsMask; /* Used to select the bucket for a hash. */ unsigned int maxchain; /* max length of chain detected */ } HashTable; /* State of an iteration over all entries in a table. */ typedef struct HashIter { HashTable *table; /* Table being searched. */ unsigned int nextBucket; /* Next bucket to check (after current). */ HashEntry *entry; /* Next entry to check in current bucket. */ } HashIter; -static inline MAKE_ATTR_UNUSED void * +MAKE_INLINE void * HashEntry_Get(HashEntry *h) { return h->value; } -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void HashEntry_Set(HashEntry *h, void *datum) { h->value = datum; } void HashTable_Init(HashTable *); void HashTable_Done(HashTable *); HashEntry *HashTable_FindEntry(HashTable *, const char *); void *HashTable_FindValue(HashTable *, const char *); unsigned int Hash_Hash(const char *); void *HashTable_FindValueHash(HashTable *, const char *, unsigned int); HashEntry *HashTable_CreateEntry(HashTable *, const char *, Boolean *); +HashEntry *HashTable_Set(HashTable *, const char *, void *); void HashTable_DeleteEntry(HashTable *, HashEntry *); void HashTable_DebugStats(HashTable *, const char *); void HashIter_Init(HashIter *, HashTable *); HashEntry *HashIter_Next(HashIter *); #endif /* MAKE_HASH_H */ Index: head/contrib/bmake/job.c =================================================================== --- head/contrib/bmake/job.c (revision 367862) +++ head/contrib/bmake/job.c (revision 367863) @@ -1,2911 +1,2853 @@ -/* $NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $ */ +/* $NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig 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. */ /*- * job.c -- * handle the creation etc. of our child processes. * * Interface: * Job_Init Called to initialize this module. In addition, * any commands attached to the .BEGIN target * are executed before this function returns. * Hence, the makefiles must have been parsed * before this function is called. * * Job_End Clean up any memory used. * * 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_ParseShell Given the line following a .SHELL target, parse * the line as a shell specification. Returns * FALSE 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. * * 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 #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 #if defined(HAVE_SYS_SOCKET_H) # include #endif #include "make.h" #include "dir.h" #include "job.h" #include "pathnames.h" #include "trace.h" /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $"); /* A shell defines how the commands are run. All commands for a target are * written into a single file, which is then given to the shell to execute * the commands from it. The commands are written to the file using a few * templates for echo control and error control. * * The name of the shell is the basename for the predefined shells, such as * "sh", "csh", "bash". For custom shells, it is the full pathname, and its * basename is used to select the type of shell; the longest match wins. * So /usr/pkg/bin/bash has type sh, /usr/local/bin/tcsh has type csh. * * The echoing of command lines is controlled using hasEchoCtl, echoOff, * echoOn, noPrint and noPrintLen. When echoOff is executed by the shell, it * still outputs something, but this something is not interesting, therefore * it is filtered out using noPrint and noPrintLen. * * The error checking for individual commands is controlled using hasErrCtl, * errOnOrEcho, errOffOrExecIgnore and errExit. * * If a shell doesn't have error control, errOnOrEcho becomes a printf template * for echoing the command, should echoing be on; errOffOrExecIgnore becomes * another printf template for executing the command while ignoring the return * status. Finally errExit is a printf template for running the command and * causing the shell to exit on error. If any of these strings are empty when * hasErrCtl is FALSE, the command will be executed anyway as is, and if it - * causes an error, so be it. Any templates setup to echo the command will + * causes an error, so be it. Any templates set up to echo the command will * escape any '$ ` \ "' characters in the command string to avoid common * problems with echo "%s\n" as a template. * * The command-line flags "echo" and "exit" also control the behavior. The * "echo" flag causes the shell to start echoing commands right away. The * "exit" flag causes the shell to exit when an error is detected in one of * the commands. */ typedef struct Shell { /* The name of the shell. For Bourne and C shells, this is used only to * find the shell description when used as the single source of a .SHELL * target. For user-defined shells, this is the full path of the shell. */ const char *name; Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */ const char *echoOff; /* command to turn off echo */ const char *echoOn; /* command to turn it back on again */ const char *noPrint; /* text to skip when printing output from * shell. This is usually the same as echoOff */ size_t noPrintLen; /* length of noPrint command */ Boolean hasErrCtl; /* set if can control error checking for * individual commands */ /* XXX: split into errOn and echoCmd */ const char *errOnOrEcho; /* template to turn on error checking */ /* XXX: split into errOff and execIgnore */ const char *errOffOrExecIgnore; /* template to turn off error checking */ const char *errExit; /* template to use for testing exit code */ /* string literal that results in a newline character when it appears * outside of any 'quote' or "quote" characters */ const char *newline; char commentChar; /* character used by shell for comment lines */ /* * command-line flags */ const char *echo; /* echo commands */ const char *exit; /* exit on error */ } Shell; /* * 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 */ typedef enum AbortReason { /* why is the make aborting? */ ABORT_NONE, ABORT_ERROR, /* Because of an error */ ABORT_INTERRUPT, /* Because it was interrupted */ ABORT_WAIT /* Waiting for jobs to finish */ } AbortReason; static AbortReason aborting = ABORT_NONE; #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; /* The number of commands actually printed to the shell commands file for * the current job. Should this number be 0, no shell will be executed. */ static int numCommands; typedef enum JobStartResult { JOB_RUNNING, /* Job is running */ JOB_ERROR, /* Error in starting the job */ JOB_FINISHED /* The job is already finished */ } JobStartResult; /* * 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 predefined 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, /* .name */ FALSE, /* .hasEchoCtl */ "", /* .echoOff */ "", /* .echoOn */ "", /* .noPrint */ 0, /* .noPrintLen */ FALSE, /* .hasErrCtl */ "echo \"%s\"\n", /* .errOnOrEcho */ "%s\n", /* .errOffOrExecIgnore */ "{ %s \n} || exit $?\n", /* .errExit */ "'\n'", /* .newline */ '#', /* .commentChar */ "", /* .echo */ "", /* .exit */ }, #endif /* DEFSHELL_CUSTOM */ /* * SH description. Echo control is also possible and, under * sun UNIX anyway, one can even control error checking. */ { "sh", /* .name */ FALSE, /* .hasEchoCtl */ "", /* .echoOff */ "", /* .echoOn */ "", /* .noPrint */ 0, /* .noPrintLen */ FALSE, /* .hasErrCtl */ "echo \"%s\"\n", /* .errOnOrEcho */ "%s\n", /* .errOffOrExecIgnore */ "{ %s \n} || exit $?\n", /* .errExit */ "'\n'", /* .newline */ '#', /* .commentChar*/ #if defined(MAKE_NATIVE) && defined(__NetBSD__) "q", /* .echo */ #else "", /* .echo */ #endif "", /* .exit */ }, /* * KSH description. */ { "ksh", /* .name */ TRUE, /* .hasEchoCtl */ "set +v", /* .echoOff */ "set -v", /* .echoOn */ "set +v", /* .noPrint */ 6, /* .noPrintLen */ FALSE, /* .hasErrCtl */ "echo \"%s\"\n", /* .errOnOrEcho */ "%s\n", /* .errOffOrExecIgnore */ "{ %s \n} || exit $?\n", /* .errExit */ "'\n'", /* .newline */ '#', /* .commentChar */ "v", /* .echo */ "", /* .exit */ }, /* * 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", /* .name */ TRUE, /* .hasEchoCtl */ "unset verbose", /* .echoOff */ "set verbose", /* .echoOn */ "unset verbose", /* .noPrint */ 13, /* .noPrintLen */ FALSE, /* .hasErrCtl */ "echo \"%s\"\n", /* .errOnOrEcho */ /* XXX: Mismatch between errOn and execIgnore */ "csh -c \"%s || exit 0\"\n", /* .errOffOrExecIgnore */ "", /* .errExit */ "'\\\n'", /* .newline */ '#', /* .commentChar */ "v", /* .echo */ "e", /* .exit */ } }; /* This is the shell to which we pass all commands in the Makefile. * It is set by the Job_ParseShell function. */ static Shell *commandShell = &shells[DEFSHELL_INDEX]; const char *shellPath = NULL; /* full pathname of executable image */ const char *shellName = NULL; /* last component of shellPath */ char *shellErrFlag = NULL; static char *shellArgv = NULL; /* Custom shell args */ static Job *job_table; /* The structures that describe them */ static Job *job_table_end; /* job_table + maxJobs */ static unsigned 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) */ +static Boolean lurking_children = FALSE; +static Boolean make_suspended = FALSE; /* Whether 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 nfds_t 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" enum { npseudojobs = 2 }; /* number of pseudo-jobs */ #define TARG_FMT "%s %s ---\n" /* Default format */ #define MESSAGE(fp, gn) \ if (opts.maxJobs != 1 && targPrefix && *targPrefix) \ (void)fprintf(fp, TARG_FMT, targPrefix, gn->name) static sigset_t caught_signals; /* Set of signals we handle */ static void JobDoOutput(Job *, Boolean); static void JobInterrupt(int, int) MAKE_ATTR_DEAD; static void JobRestartJobs(void); static void JobSigReset(void); static unsigned nfds_per_job(void) { #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) return 2; #endif return 1; } static void job_table_dump(const char *where) { Job *job; debug_printf("job table @ %s\n", where); for (job = job_table; job < job_table_end; job++) { debug_printf("job %d, status %d, flags %d, pid %d\n", - (int)(job - job_table), job->job_state, job->flags, job->pid); + (int)(job - job_table), job->status, job->flags, job->pid); } } /* * Delete the target of a failed, interrupted, or otherwise * unsuccessful job unless inhibited by .PRECIOUS. */ static void JobDeleteTarget(GNode *gn) { const char *file; if (gn->type & OP_JOIN) return; if (gn->type & OP_PHONY) return; if (Targ_Precious(gn)) return; if (opts.noExecute) return; file = GNode_Path(gn); if (eunlink(file) != -1) Error("*** %s removed", file); } /* * 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, flags; int pipe_fds[2]; if (pipe(pipe_fds) == -1) Punt("Cannot create pipe: %s", strerror(errno)); for (i = 0; i < 2; i++) { /* Avoid using low numbered fds */ fd = fcntl(pipe_fds[i], F_DUPFD, minfd); if (fd != -1) { close(pipe_fds[i]); pipe_fds[i] = fd; } } job->inPipe = pipe_fds[0]; job->outPipe = pipe_fds[1]; /* Set close-on-exec flag for both */ if (fcntl(job->inPipe, F_SETFD, FD_CLOEXEC) == -1) Punt("Cannot set close-on-exec: %s", strerror(errno)); if (fcntl(job->outPipe, F_SETFD, FD_CLOEXEC) == -1) Punt("Cannot set close-on-exec: %s", strerror(errno)); /* * 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. */ flags = fcntl(job->inPipe, F_GETFL, 0); if (flags == -1) Punt("Cannot get flags: %s", strerror(errno)); flags |= O_NONBLOCK; if (fcntl(job->inPipe, F_SETFL, flags) == -1) Punt("Cannot set flags: %s", strerror(errno)); } /* Pass the signal to each running job. */ static void JobCondPassSig(int signo) { Job *job; DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo); for (job = job_table; job < job_table_end; job++) { - if (job->job_state != JOB_ST_RUNNING) + if (job->status != JOB_ST_RUNNING) continue; DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n", signo, job->pid); KILLPG(job->pid, signo); } } /* SIGCHLD handler. * * 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; } /* Resume all stopped jobs. */ static void JobContinueSig(int signo MAKE_ATTR_UNUSED) { /* * Defer sending SIGCONT to our stopped children until we return * from the signal handler. */ while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 && errno == EAGAIN) continue; } /* Pass a signal on to all jobs, then resend to ourselves. * We die by the same signal. */ MAKE_ATTR_DEAD static void JobPassSig_int(int signo) { /* Run .INTERRUPT target then exit */ JobInterrupt(TRUE, signo); } /* Pass a signal on to all jobs, then resend to ourselves. * We die by the same signal. */ 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; + make_suspended = TRUE; /* 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)) debug_printf("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 passing 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); } static Job * -JobFindPid(int pid, JobState status, Boolean isJobs) +JobFindPid(int pid, JobStatus status, Boolean isJobs) { Job *job; for (job = job_table; job < job_table_end; job++) { - if ((job->job_state == status) && job->pid == pid) + if (job->status == status && job->pid == pid) return job; } if (DEBUG(JOB) && isJobs) job_table_dump("no pid"); return NULL; } /* Parse leading '@', '-' and '+', which control the exact execution mode. */ static void ParseRunOptions( char **pp, Boolean *out_shutUp, Boolean *out_errOff, Boolean *out_runAlways) { char *p = *pp; *out_shutUp = FALSE; *out_errOff = FALSE; *out_runAlways = FALSE; for (;;) { if (*p == '@') *out_shutUp = !DEBUG(LOUD); else if (*p == '-') *out_errOff = TRUE; else if (*p == '+') *out_runAlways = TRUE; else break; p++; } pp_skip_whitespace(&p); *pp = p; } /* Escape a string for a double-quoted string literal in sh, csh and ksh. */ static char * EscapeShellDblQuot(const char *cmd) { size_t i, j; /* Worst that could happen is every char needs escaping. */ char *esc = 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] == '"') esc[j++] = '\\'; esc[j] = cmd[i]; } esc[j] = '\0'; return esc; } +static void +JobPrintf(Job *job, const char *fmt, const char *arg) +{ + if (DEBUG(JOB)) + debug_printf(fmt, arg); + + (void)fprintf(job->cmdFILE, fmt, arg); + (void)fflush(job->cmdFILE); +} + +static void +JobPrintln(Job *job, const char *line) +{ + JobPrintf(job, "%s\n", line); +} + /*- *----------------------------------------------------------------------- * 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 + * was reached. These commands are later attached to the .END * node and executed by Job_End when all things are done. * * 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. * numCommands is incremented if the command is actually printed. *----------------------------------------------------------------------- */ static void JobPrintCommand(Job *job, char *cmd) { const char *const cmdp = cmd; Boolean noSpecials; /* true if we shouldn't worry about * inserting special commands into * the input stream. */ Boolean shutUp; /* true if we put a no echo command * into the command file */ Boolean errOff; /* true if we turned error checking * off before printing the command * and need to turn it back on */ Boolean runAlways; const char *cmdTemplate; /* Template to use when printing the * command */ char *cmdStart; /* Start of expanded command */ char *escCmd = NULL; /* Command with quotes/backticks escaped */ noSpecials = !GNode_ShouldExecute(job->node); -#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \ - debug_printf(fmt, arg); \ - } \ - (void)fprintf(job->cmdFILE, fmt, arg); \ - (void)fflush(job->cmdFILE); - numCommands++; Var_Subst(cmd, job->node, VARE_WANTRES, &cmd); /* TODO: handle errors */ cmdStart = cmd; cmdTemplate = "%s\n"; ParseRunOptions(&cmd, &shutUp, &errOff, &runAlways); if (runAlways && noSpecials) { /* * We're not actually executing anything... * but this one needs to be - use compat mode just for it. */ Compat_RunCommand(cmdp, job->node); free(cmdStart); return; } /* * 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) escCmd = EscapeShellDblQuot(cmd); if (shutUp) { if (!(job->flags & JOB_SILENT) && !noSpecials && - commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); + (commandShell->hasEchoCtl)) { + JobPrintln(job, 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->errOffOrExecIgnore); - DBPRINTF("%s\n", commandShell->echoOn); + (commandShell->hasEchoCtl)) { + JobPrintln(job, commandShell->echoOff); + JobPrintln(job, commandShell->errOffOrExecIgnore); + JobPrintln(job, commandShell->echoOn); } else { - DBPRINTF("%s\n", commandShell->errOffOrExecIgnore); + JobPrintln(job, commandShell->errOffOrExecIgnore); } } else if (commandShell->errOffOrExecIgnore && - commandShell->errOffOrExecIgnore[0] != '\0') - { + commandShell->errOffOrExecIgnore[0] != '\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 * errOnOrEcho 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->errOnOrEcho, escCmd); - shutUp = TRUE; + if (commandShell->hasEchoCtl) { + JobPrintln(job, commandShell->echoOff); + } + JobPrintf(job, commandShell->errOnOrEcho, escCmd); + shutUp = TRUE; } else { - if (!shutUp) { - DBPRINTF(commandShell->errOnOrEcho, escCmd); - } + if (!shutUp) + JobPrintf(job, commandShell->errOnOrEcho, escCmd); } cmdTemplate = commandShell->errOffOrExecIgnore; /* * The error ignoration (hee hee) is already taken care * of by the errOffOrExecIgnore 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 errExit template, then setup commands to run + * but does supply an errExit template, then set up commands to run * through it. */ if (!commandShell->hasErrCtl && commandShell->errExit && commandShell->errExit[0] != '\0') { - if (!(job->flags & JOB_SILENT) && !shutUp) { - if (commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); - } - DBPRINTF(commandShell->errOnOrEcho, 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->errOffOrExecIgnore; - else - cmdTemplate = commandShell->errExit; - errOff = FALSE; + if (!(job->flags & JOB_SILENT) && !shutUp) { + if (commandShell->hasEchoCtl) + JobPrintln(job, commandShell->echoOff); + JobPrintf(job, commandShell->errOnOrEcho, 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->errOffOrExecIgnore; + else + cmdTemplate = commandShell->errExit; + errOff = FALSE; } } if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 && - (job->flags & JOB_TRACED) == 0) { - DBPRINTF("set -%s\n", "x"); - job->flags |= JOB_TRACED; + !(job->flags & JOB_TRACED)) { + JobPrintln(job, "set -x"); + job->flags |= JOB_TRACED; } - DBPRINTF(cmdTemplate, cmd); + JobPrintf(job, cmdTemplate, cmd); free(cmdStart); 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); + if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl) { + JobPrintln(job, commandShell->echoOff); shutUp = TRUE; } - DBPRINTF("%s\n", commandShell->errOnOrEcho); + JobPrintln(job, commandShell->errOnOrEcho); } - if (shutUp && commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOn); - } + if (shutUp && commandShell->hasEchoCtl) + JobPrintln(job, commandShell->echoOn); } /* Print all commands to the shell file that is later executed. * * The special command "..." stops printing and saves the remaining commands * to be executed later. */ static void JobPrintCommands(Job *job) { StringListNode *ln; for (ln = job->node->commands->first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; if (strcmp(cmd, "...") == 0) { job->node->type |= OP_SAVE_CMDS; - if ((job->flags & JOB_IGNDOTS) == 0) { - job->tailCmds = ln->next; - break; - } - } else - JobPrintCommand(job, ln->datum); + job->tailCmds = ln->next; + break; + } + + JobPrintCommand(job, ln->datum); } } /* Save the delayed commands, to be executed when everything else is done. */ static void JobSaveCommands(Job *job) { StringListNode *node; for (node = job->tailCmds; node != NULL; node = node->next) { const char *cmd = node->datum; char *expanded_cmd; /* XXX: This Var_Subst is only intended to expand the dynamic * variables such as .TARGET, .IMPSRC. It is not intended to * expand the other variables as well; see deptgt-end.mk. */ (void)Var_Subst(cmd, job->node, VARE_WANTRES, &expanded_cmd); /* TODO: handle errors */ Lst_Append(Targ_GetEndNode()->commands, expanded_cmd); } } /* Called to close both input and output pipes when a job is finished. */ static void -JobClose(Job *job) +JobClosePipes(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. +/* Do final processing for the given job including updating parent nodes and + * starting new jobs as available/necessary. * + * Deferred commands for the job are placed on the .END node. + * + * If there was a serious error (errors != 0; not an ignored one), no more + * jobs will be started. + * * Input: * job job to finish * status sub-why job went away - * - * 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. - *----------------------------------------------------------------------- */ static void JobFinish (Job *job, WAIT_T status) { Boolean done, return_job_token; DEBUG3(JOB, "JobFinish: %d [%s], status %d\n", job->pid, job->node->name, status); if ((WIFEXITED(status) && - (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) || + ((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); + JobClosePipes(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. + * telling of the ignored error as well as to run the next command. + * */ 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); + JobClosePipes(job); } else { /* * No need to close things down or anything. */ done = FALSE; } if (done) { if (WIFEXITED(status)) { DEBUG2(JOB, "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 - if (!dieQuietly(job->node, -1)) + if (!shouldDieQuietly(job->node, -1)) (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 { if (deleteOnError) { JobDeleteTarget(job->node); } 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)); if (deleteOnError) { JobDeleteTarget(job->node); } } (void)fflush(stdout); } #ifdef USE_META if (useMeta) { - int x; - - if ((x = meta_job_finish(job)) != 0 && status == 0) { - status = x; - } + int meta_status = meta_job_finish(job); + if (meta_status != 0 && status == 0) + status = meta_status; } #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)) + if (WAIT_STATUS(status) != 0 || + (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT) return_job_token = TRUE; } - if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && + 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. */ JobSaveCommands(job); job->node->made = MADE; if (!(job->flags & JOB_SPECIAL)) return_job_token = TRUE; Make_Update(job->node); - job->job_state = JOB_ST_FREE; + job->status = JOB_ST_FREE; } else if (WAIT_STATUS(status)) { errors++; - job->job_state = JOB_ST_FREE; + job->status = JOB_ST_FREE; } - /* - * Set aborting if any error. - */ - if (errors && !opts.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 (errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT) + aborting = ABORT_ERROR; /* Prevent more jobs from getting started. */ 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. - */ + if (aborting == ABORT_ERROR && jobTokensRunning == 0) Finish(errors); +} + +static void +TouchRegular(GNode *gn) +{ + const char *file = GNode_Path(gn); + struct utimbuf times = { now, now }; + int fd; + char c; + + if (utime(file, ×) >= 0) + return; + + fd = open(file, O_RDWR | O_CREAT, 0666); + if (fd < 0) { + (void)fprintf(stderr, "*** couldn't touch %s: %s\n", + file, strerror(errno)); + (void)fflush(stderr); + return; /* XXX: What about propagating the error? */ } + + /* Last resort: update the file's time stamps in the traditional way. + * XXX: This doesn't work for empty files, which are sometimes used + * as marker files. */ + if (read(fd, &c, 1) == 1) { + (void)lseek(fd, 0, SEEK_SET); + while (write(fd, &c, 1) == -1 && errno == EAGAIN) + continue; + } + (void)close(fd); /* XXX: What about propagating the error? */ } /* Touch the given target. Called by JobStart when the -t flag was given. * * The modification date of the file is changed. * 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. - */ + /* These are "virtual" targets and should not really be created. */ return; } if (!silent || !GNode_ShouldExecute(gn)) { (void)fprintf(stdout, "touch %s\n", gn->name); (void)fflush(stdout); } - if (!GNode_ShouldExecute(gn)) { + if (!GNode_ShouldExecute(gn)) return; - } if (gn->type & OP_ARCHV) { Arch_Touch(gn); - } else if (gn->type & OP_LIB) { - Arch_TouchLib(gn); - } else { - const char *file = GNode_Path(gn); + return; + } - 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); - } - } + if (gn->type & OP_LIB) { + Arch_TouchLib(gn); + return; } + + TouchRegular(gn); } /* Make sure the given node has all the commands it needs. * * The node will have commands from the .DEFAULT rule added to it if it * needs them. * * Input: * gn The target whose commands need verifying * abortProc Function to abort with message * * Results: * TRUE if the commands list is/was ok. */ Boolean Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) { if (GNode_IsTarget(gn)) return TRUE; if (!Lst_IsEmpty(gn->commands)) return TRUE; if ((gn->type & OP_LIB) && !Lst_IsEmpty(gn->children)) return TRUE; /* * No commands. Look for .DEFAULT rule from which we might infer - * commands + * commands. */ - if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) && - (gn->type & OP_SPECIAL) == 0) { + if (defaultNode != NULL && !Lst_IsEmpty(defaultNode->commands) && + !(gn->type & OP_SPECIAL)) { /* - * 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 + * The traditional Make only looks for a .DEFAULT if the node was + * never the target of an operator, so that's what we do too. + * + * 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); + Make_HandleUse(defaultNode, gn); Var_Set(IMPSRC, GNode_VarTarget(gn), gn); return TRUE; } - if (Dir_MTime(gn, 0) != 0 || (gn->type & OP_SPECIAL)) + Dir_UpdateMTime(gn, FALSE); + if (gn->mtime != 0 || (gn->type & OP_SPECIAL)) return TRUE; /* * 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. */ 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: don't know how to make %s (%s)\n", progname, gn->name, "ignored"); (void)fflush(stdout); return TRUE; } if (opts.keepgoing) { (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", progname, gn->name, "continuing"); (void)fflush(stdout); return FALSE; } abortProc("%s: don't know how to make %s. Stop", progname, gn->name); return FALSE; } /* Execute the shell for the given job. * - * A shell is executed, its output is altered and the Job structure added - * to the job table. - */ + * See Job_CatchOutput for handling the output of the shell. */ static void JobExec(Job *job, char **argv) { int cpid; /* ID of new child */ - sigset_t mask; + sigset_t mask; job->flags &= ~JOB_TRACED; if (DEBUG(JOB)) { int i; - debug_printf("Running %s %sly\n", job->node->name, "local"); + debug_printf("Running %s\n", job->node->name); debug_printf("\tCommand: "); for (i = 0; argv[i] != NULL; i++) { debug_printf("%s ", argv[i]); } debug_printf("\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; + job->status = 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) execDie("dup2", "job->cmdFILE"); if (fcntl(0, F_SETFD, 0) == -1) execDie("fcntl clear close-on-exec", "stdin"); - if (lseek(0, (off_t)0, SEEK_SET) == -1) + if (lseek(0, 0, SEEK_SET) == -1) execDie("lseek to 0", "stdin"); if (Always_pass_job_queue || (job->node->type & (OP_MAKE | OP_SUBMAKE))) { - /* - * Pass job token pipe to submakes. - */ - if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) - execDie("clear close-on-exec", "tokenWaitJob.inPipe"); - if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) - execDie("clear close-on-exec", "tokenWaitJob.outPipe"); + /* + * Pass job token pipe to submakes. + */ + if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) + execDie("clear close-on-exec", "tokenWaitJob.inPipe"); + if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) + execDie("clear close-on-exec", "tokenWaitJob.outPipe"); } /* * Set up the child's output to be routed through the pipe * we've created for it. */ if (dup2(job->outPipe, 1) == -1) execDie("dup2", "job->outPipe"); /* * 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. */ if (fcntl(1, F_SETFD, 0) == -1) execDie("clear close-on-exec", "stdout"); if (dup2(1, 2) == -1) execDie("dup2", "1, 2"); /* * 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); execDie("exec", shellPath); } /* Parent, continuing after the child exec */ job->pid = cpid; Trace_Log(JOBSTART, job); #ifdef USE_META if (useMeta) { meta_job_parent(job, cpid); } #endif /* * 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)) { debug_printf("JobExec(%s): pid %d added to jobs table\n", job->node->name, job->pid); job_table_dump("job started"); } JobSigUnlock(&mask); } /* Create the argv needed to execute the shell for a given job. */ 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[0] != '-') || (commandShell->echo && commandShell->echo[0] != '-')) { /* * 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", + (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. + * NB: The return value is ignored by everyone. *----------------------------------------------------------------------- */ static JobStartResult -JobStart(GNode *gn, int flags) +JobStart(GNode *gn, JobFlags 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) + if (job->status == 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; + job->status = JOB_ST_SET_UP; - /* - * 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; + if (gn->type & OP_SPECIAL) + flags |= JOB_SPECIAL; + if (Targ_Ignore(gn)) + flags |= JOB_IGNERR; + if (Targ_Silent(gn)) + 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) && !opts.noRecursiveExecute) || - (!opts.noExecute && !opts.touchFlag)) { + (!opts.noExecute && !opts.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); + (void)eunlink(tfile); JobSigUnlock(&mask); job->cmdFILE = fdopen(tfd, "w+"); - if (job->cmdFILE == NULL) { + if (job->cmdFILE == NULL) Punt("Could not fdopen %s", tfile); - } + (void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC); /* * 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 */ + 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; JobPrintCommands(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 (!GNode_ShouldExecute(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) JobPrintCommands(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); + 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; - } + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (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 == ABORT_NONE) { JobSaveCommands(job); job->node->made = MADE; Make_Update(job->node); } - job->job_state = JOB_ST_FREE; + job->status = 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; } +/* Print the output of the shell command, skipping the noPrint command of + * the shell, if any. */ static char * JobOutput(Job *job, char *cp, char *endp) { char *ecp; - if (commandShell->noPrint && commandShell->noPrint[0] != '\0') { - while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) { - if (cp != ecp) { - *ecp = '\0'; - /* - * 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->noPrintLen; - 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++; - } - } else { - return cp; - } + if (commandShell->noPrint == NULL || commandShell->noPrint[0] == '\0') + return cp; + + while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) { + if (ecp != cp) { + *ecp = '\0'; + /* + * 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->noPrintLen; + if (cp != endp) { + /* + * Still more to print, look again after skipping + * the whitespace following the non-printable + * command.... + */ + cp++; + pp_skip_whitespace(&cp); + } 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, transferring 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. +/* + * This function is 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. * + * In the output of the shell, the 'noPrint' lines are removed. 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 - * - * 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 */ size_t nr; /* number of bytes read */ size_t i; /* auxiliary index into outBuf */ size_t max; /* limit for i (end of current data) */ ssize_t nRead; /* (Temporary) number of bytes read */ /* * Read as many bytes as will fit in the buffer. */ -end_loop: +again: gotNL = FALSE; fbuf = FALSE; nRead = read(job->inPipe, &job->outBuf[job->curPos], - JOB_BUFSIZE - job->curPos); + JOB_BUFSIZE - job->curPos); if (nRead < 0) { if (errno == EAGAIN) return; if (DEBUG(JOB)) { perror("JobDoOutput(piperead)"); } nr = 0; } else { nr = (size_t)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)) { + 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 != (size_t)-1; 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]); /* * 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 (!opts.beSilent && job->node != lastNode) { MESSAGE(stdout, job->node); lastNode = job->node; } #ifdef USE_META if (useMeta) { meta_job_output(job, cp, gotNL ? "\n" : ""); } #endif (void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : ""); (void)fflush(stdout); } } /* * max is the last offset still in the buffer. Move any remaining * characters to the start of the buffer and update the end marker * curPos. */ if (i < max) { (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1)); job->curPos = max - (i + 1); } else { assert(i == max); job->curPos = 0; } } if (finish) { /* * If the finish flag is true, we must loop until we hit * end-of-file on the pipe. This is guaranteed to happen * eventually since the other end of the pipe is now closed * (we closed it explicitly and the child has exited). When * we do get an EOF, finish will be set FALSE and we'll fall * through and out. */ - goto end_loop; + goto again; } } static void JobRun(GNode *targ) { #if 0 /* * Unfortunately it is too complicated to run .BEGIN, .END, and * .INTERRUPT job in the parallel job module. As of 2020-09-25, * unit-tests/deptgt-end-jobs.mk hangs in an endless loop. * * Running these jobs in compat mode also guarantees that these * jobs do not overlap with other unrelated jobs. */ List *lst = Lst_New(); Lst_Append(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 } /* Handle the exit of a child. Called from Make_Make. * * 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) { DEBUG2(JOB, "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)) { DEBUG2(JOB, "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; + job->suspended = TRUE; } (void)fflush(stdout); return; } - job->job_state = JOB_ST_FINISHED; + job->status = JOB_ST_FINISHED; job->exit_status = WAIT_STATUS(status); JobFinish(job, status); } /* 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. */ void Job_CatchOutput(void) { int nready; Job *job; unsigned 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; + nready--; } Job_CatchChildren(); if (nready == 0) return; for (i = npseudojobs * nfds_per_job(); i < nfds; i++) { if (!fds[i].revents) continue; job = jobfds[i]; - if (job->job_state == JOB_ST_RUNNING) + if (job->status == JOB_ST_RUNNING) JobDoOutput(job, FALSE); #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) /* * With meta mode, we may have activity on the job's filemon * descriptor too, which at the moment is any pollfd other than * job->inPollfd. */ if (useMeta && job->inPollfd != &fds[i]) { if (meta_job_event(job) <= 0) { fds[i].events = 0; /* never mind */ } } #endif if (--nready == 0) return; } } /* Start the creation of a target. Basically a front-end for JobStart used by * the Make module. */ void Job_Make(GNode *gn) { - (void)JobStart(gn, 0); + (void)JobStart(gn, JOB_NONE); } 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_concat3(_PATH_DEFSHELLDIR, "/", shellName); } - Var_Set_with_flags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY); + Var_SetWithFlags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY); if (commandShell->exit == NULL) { commandShell->exit = ""; } if (commandShell->echo == NULL) { commandShell->echo = ""; } if (commandShell->hasErrCtl && commandShell->exit[0] != '\0') { if (shellErrFlag && strcmp(commandShell->exit, &shellErrFlag[1]) != 0) { free(shellErrFlag); shellErrFlag = NULL; } if (!shellErrFlag) { size_t n = strlen(commandShell->exit) + 2; shellErrFlag = bmake_malloc(n); if (shellErrFlag) { snprintf(shellErrFlag, n, "-%s", commandShell->exit); } } } else if (shellErrFlag) { free(shellErrFlag); shellErrFlag = NULL; } } /* Return 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); } (void)Var_Subst("${" MAKE_JOB_PREFIX "}", VAR_GLOBAL, VARE_WANTRES, &targPrefix); /* TODO: handle errors */ } /* Initialize the process module. */ void Job_Init(void) { Job_SetPrefix(); /* Allocate space for all the job info */ job_table = bmake_malloc((size_t)opts.maxJobs * sizeof *job_table); memset(job_table, 0, (size_t)opts.maxJobs * sizeof *job_table); job_table_end = job_table + opts.maxJobs; wantToken = 0; aborting = ABORT_NONE; errors = 0; lastNode = NULL; - Always_pass_job_queue = getBoolean(MAKE_ALWAYS_PASS_JOB_QUEUE, + Always_pass_job_queue = GetBooleanVar(MAKE_ALWAYS_PASS_JOB_QUEUE, Always_pass_job_queue); - Job_error_token = getBoolean(MAKE_JOB_ERROR_TOKEN, Job_error_token); + Job_error_token = GetBooleanVar(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; + lurking_children = TRUE; break; } Shell_Init(); JobCreatePipe(&childExitJob, 3); /* Preallocate enough for the maximum number of jobs. */ - fds = bmake_malloc(sizeof(*fds) * + fds = bmake_malloc(sizeof *fds * (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); - jobfds = bmake_malloc(sizeof(*jobfds) * + jobfds = bmake_malloc(sizeof *jobfds * (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); /* 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); /* Create the .END node now, even though no code in the unit tests * depends on it. See also Targ_GetEndNode in Compat_Run. */ (void)Targ_GetEndNode(); } 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); } /* Find a shell in 'shells' given its name, or return NULL. */ static Shell * FindShellByName(const char *name) { Shell *sh = shells; const Shell *shellsEnd = sh + sizeof shells / sizeof shells[0]; for (sh = shells; sh < shellsEnd; 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: * FALSE 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. * *----------------------------------------------------------------------- */ Boolean Job_ParseShell(char *line) { Words wordsList; char **words; char **argv; size_t argc; char *path; Shell newShell; Boolean fullSpec = FALSE; Shell *sh; pp_skip_whitespace(&line); free(shellArgv); - memset(&newShell, 0, sizeof(newShell)); + memset(&newShell, 0, sizeof newShell); /* * Parse the specification by keyword */ wordsList = Str_Words(line, TRUE); words = wordsList.words; argc = wordsList.len; path = wordsList.freeIt; if (words == NULL) { Error("Unterminated quoted string [%s]", line); return FALSE; } shellArgv = path; for (path = NULL, argv = words; argc != 0; argc--, argv++) { char *arg = *argv; if (strncmp(arg, "path=", 5) == 0) { path = arg + 5; } else if (strncmp(arg, "name=", 5) == 0) { newShell.name = arg + 5; } else { if (strncmp(arg, "quiet=", 6) == 0) { newShell.echoOff = arg + 6; } else if (strncmp(arg, "echo=", 5) == 0) { newShell.echoOn = arg + 5; } else if (strncmp(arg, "filter=", 7) == 0) { newShell.noPrint = arg + 7; newShell.noPrintLen = strlen(newShell.noPrint); } else if (strncmp(arg, "echoFlag=", 9) == 0) { newShell.echo = arg + 9; } else if (strncmp(arg, "errFlag=", 8) == 0) { newShell.exit = arg + 8; } else if (strncmp(arg, "hasErrCtl=", 10) == 0) { char c = arg[10]; newShell.hasErrCtl = c == 'Y' || c == 'y' || c == 'T' || c == 't'; } else if (strncmp(arg, "newline=", 8) == 0) { newShell.newline = arg + 8; } else if (strncmp(arg, "check=", 6) == 0) { newShell.errOnOrEcho = arg + 6; } else if (strncmp(arg, "ignore=", 7) == 0) { newShell.errOffOrExecIgnore = arg + 7; } else if (strncmp(arg, "errout=", 7) == 0) { newShell.errExit = arg + 7; } else if (strncmp(arg, "comment=", 8) == 0) { newShell.commentChar = arg[8]; } else { Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", arg); free(words); return FALSE; } 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 FindShellByName * 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 FALSE; } else { if ((sh = FindShellByName(newShell.name)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", newShell.name); free(words); return FALSE; } 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++; } if (newShell.name != NULL) { shellName = newShell.name; } else { shellName = path; } if (!fullSpec) { if ((sh = FindShellByName(shellName)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", shellName); free(words); return FALSE; } commandShell = sh; } else { - commandShell = bmake_malloc(sizeof(Shell)); + commandShell = bmake_malloc(sizeof *commandShell); *commandShell = newShell; } /* this will take care of shellErrFlag */ Shell_Init(); } if (commandShell->echoOn && commandShell->echoOff) { commandShell->hasEchoCtl = TRUE; } if (!commandShell->hasErrCtl) { if (commandShell->errOnOrEcho == NULL) { commandShell->errOnOrEcho = ""; } if (commandShell->errOffOrExecIgnore == NULL) { commandShell->errOffOrExecIgnore = "%s\n"; } } /* * Do not free up the words themselves, since they might be in use by the * shell specification. */ free(words); return TRUE; } /* Handle the receipt of an interrupt. * * All children are killed. Another job will be started if the .INTERRUPT * target is defined. * * Input: * runINTERRUPT Non-zero if commands for the .INTERRUPT target * should be executed * signo signal received */ 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) + if (job->status != JOB_ST_RUNNING) continue; gn = job->node; JobDeleteTarget(gn); if (job->pid) { DEBUG2(JOB, "JobInterrupt passing signal %d to child %d.\n", signo, job->pid); KILLPG(job->pid, signo); } } JobSigUnlock(&mask); if (runINTERRUPT && !opts.touchFlag) { interrupt = Targ_FindNode(".INTERRUPT"); if (interrupt != NULL) { opts.ignoreErrors = FALSE; JobRun(interrupt); } } - Trace_Log(MAKEINTR, 0); + Trace_Log(MAKEINTR, NULL); exit(signo); } /* Do the final processing, i.e. run the commands attached to the .END target. * * Return the number of errors reported. */ int Job_Finish(void) { GNode *endNode = Targ_GetEndNode(); if (!Lst_IsEmpty(endNode->commands) || !Lst_IsEmpty(endNode->children)) { if (errors) { Error("Errors reported so .END ignored"); } else { JobRun(endNode); } } return errors; } /* Clean up any memory used by the jobs module. */ void Job_End(void) { #ifdef CLEANUP free(shellArgv); #endif } /* Waits for all running jobs to finish and returns. * Sets 'aborting' to ABORT_WAIT to prevent other jobs from starting. */ void Job_Wait(void) { aborting = ABORT_WAIT; while (jobTokensRunning != 0) { Job_CatchOutput(); } aborting = ABORT_NONE; } /* 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. * * 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) + if (job->status != 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; } /* Tries to restart stopped jobs if there are slots available. * Called in process context in response to a SIGCONT. */ 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 (job->status == JOB_ST_RUNNING && + (make_suspended || job->suspended)) { DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid); - if (job->job_suspended) { + if (job->suspended) { (void)printf("*** [%s] Continued\n", job->node->name); (void)fflush(stdout); } - job->job_suspended = 0; + job->suspended = FALSE; if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { debug_printf("Failed to send SIGCONT to %d\n", job->pid); } } - if (job->job_state == JOB_ST_FINISHED) + if (job->status == JOB_ST_FINISHED) /* Job exit deferred after calling waitpid() in a signal handler */ JobFinish(job, job->exit_status); } - make_suspended = 0; + make_suspended = FALSE; } 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++; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) { fds[nfds].fd = meta_job_fd(job); fds[nfds].events = fds[nfds].fd == -1 ? 0 : POLLIN; jobfds[nfds] = job; nfds++; } #endif } static void clearfd(Job *job) { size_t i; if (job->inPollfd == NULL) Punt("Unwatching unwatched job"); i = (size_t)(job->inPollfd - fds); nfds--; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) { /* * Sanity check: there should be two fds per job, so the job's * pollfd number should be even. */ assert(nfds_per_job() == 2); if (i % 2) Punt("odd-numbered fd with meta"); nfds--; } #endif /* * 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]; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) { fds[i + 1] = fds[nfds + 1]; jobfds[i + 1] = jobfds[nfds + 1]; } #endif } job->inPollfd = NULL; } static int readyfd(Job *job) { if (job->inPollfd == NULL) Punt("Polling unwatched job"); return (job->inPollfd->revents & POLLIN) != 0; } /* Put a token (back) into the job pipe. * This allows a make process to start a build job. */ 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; DEBUG3(JOB, "(%d) aborting %d, deposit token %c\n", getpid(), aborting, tok); while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; } /* 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, FD_CLOEXEC); (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC); return; } JobCreatePipe(&tokenWaitJob, 15); - snprintf(jobarg, sizeof(jobarg), "%d,%d", + 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(); } /* Return a withdrawn token to the pool. */ void Job_TokenReturn(void) { jobTokensRunning--; if (jobTokensRunning < 0) Punt("token botch"); if (jobTokensRunning || JOB_TOKENS[aborting] != '+') JobTokenAdd(); } /* Attempt to withdraw a token from the pool. * * If pool is empty, set wantToken so that we wake up when a token is * released. * * Returns TRUE if a token was withdrawn, and FALSE if the pool is currently * empty. */ Boolean Job_TokenWithdraw(void) { char tok, tok1; ssize_t count; wantToken = 0; DEBUG3(JOB, "Job_TokenWithdraw(%d): aborting %d, running %d\n", getpid(), aborting, jobTokensRunning); if (aborting != ABORT_NONE || (jobTokensRunning >= opts.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)); } DEBUG1(JOB, "(%d) blocked for token\n", getpid()); return FALSE; } if (count == 1 && tok != '+') { /* make being abvorted - remove any other job tokens */ DEBUG2(JOB, "(%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; - if (dieQuietly(NULL, 1)) + if (shouldDieQuietly(NULL, 1)) exit(2); 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++; DEBUG1(JOB, "(%d) withdrew token\n", getpid()); return TRUE; } /* Run the named target if found. If a filename is specified, then set that * to the sources. * * Exits if the target fails. */ Boolean Job_RunTarget(const char *target, const char *fname) { GNode *gn = Targ_FindNode(target); if (gn == NULL) return FALSE; if (fname) Var_Set(ALLSRC, fname, gn); 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); + nselect = select(maxfd + 1, &rfds, &wfds, NULL, 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/job.h =================================================================== --- head/contrib/bmake/job.h (revision 367862) +++ head/contrib/bmake/job.h (revision 367863) @@ -1,215 +1,215 @@ -/* $NetBSD: job.h,v 1.58 2020/10/26 21:34:10 rillig Exp $ */ +/* $NetBSD: job.h,v 1.63 2020/11/14 13:27:01 rillig 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: @(#)job.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: @(#)job.h 8.1 (Berkeley) 6/6/93 */ /* * Running of jobs in parallel mode. */ #ifndef MAKE_JOB_H #define MAKE_JOB_H #define TMPPAT "makeXXXXXX" /* relative to tmpdir */ #ifdef USE_SELECT /* * Emulate poll() in terms of select(). This is not a complete * emulation but it is sufficient for make's purposes. */ #define poll emul_poll #define pollfd emul_pollfd struct emul_pollfd { int fd; short events; short revents; }; #define POLLIN 0x0001 #define POLLOUT 0x0004 int emul_poll(struct pollfd *fd, int nfd, int timeout); #endif /* * The POLL_MSEC constant determines the maximum number of milliseconds spent * in poll before coming out to see if a child has finished. */ #define POLL_MSEC 5000 struct pollfd; #ifdef USE_META # include "meta.h" #endif -typedef enum JobState { +typedef enum JobStatus { JOB_ST_FREE = 0, /* Job is available */ - JOB_ST_SETUP = 1, /* Job is allocated but otherwise invalid */ + JOB_ST_SET_UP = 1, /* Job is allocated but otherwise invalid */ + /* XXX: What about the 2? */ JOB_ST_RUNNING = 3, /* Job is running, pid valid */ JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */ -} JobState; +} JobStatus; typedef enum JobFlags { + JOB_NONE = 0, /* Ignore non-zero exits */ - JOB_IGNERR = 0x001, + JOB_IGNERR = 1 << 0, /* no output */ - JOB_SILENT = 0x002, + JOB_SILENT = 1 << 1, /* Target is a special one. i.e. run it locally * if we can't export it and maxLocal is 0 */ - JOB_SPECIAL = 0x004, - /* Ignore "..." lines when processing commands */ - JOB_IGNDOTS = 0x008, + JOB_SPECIAL = 1 << 2, /* we've sent 'set -x' */ - JOB_TRACED = 0x400 + JOB_TRACED = 1 << 10 } JobFlags; /* A Job manages the shell commands that are run to create a single target. * Each job is run in a separate subprocess by a shell. Several jobs can run * in parallel. * * The shell commands for the target are written to a temporary file, * then the shell is run with the temporary file as stdin, and the output * of that shell is captured via a pipe. * * When a job is finished, Make_Update updates all parents of the node * that was just remade, marking them as ready to be made next if all * other dependencies are finished as well. */ typedef struct Job { /* The process ID of the shell running the commands */ int pid; /* The target the child is making */ GNode *node; /* If one of the shell commands is "...", all following commands are * delayed until the .END node is made. This list node points to the * first of these commands, if any. */ StringListNode *tailCmds; /* When creating the shell script, this is where the commands go. * This is only used before the job is actually started. */ FILE *cmdFILE; int exit_status; /* from wait4() in signal handler */ - JobState job_state; /* status of the job entry */ + JobStatus status; - char job_suspended; + Boolean suspended; JobFlags flags; /* Flags to control treatment of job */ int inPipe; /* Pipe for reading output from job */ int outPipe; /* Pipe for writing control commands */ struct pollfd *inPollfd; /* pollfd associated with inPipe */ #define JOB_BUFSIZE 1024 /* Buffer for storing the output of the job, line by line. */ char outBuf[JOB_BUFSIZE + 1]; size_t curPos; /* Current position in outBuf. */ #ifdef USE_META struct BuildMon bm; #endif } Job; extern const char *shellPath; extern const char *shellName; extern char *shellErrFlag; extern int jobTokensRunning; /* tokens currently "out" */ void Shell_Init(void); const char *Shell_GetNewline(void); void Job_Touch(GNode *, Boolean); Boolean Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...)); void Job_CatchChildren(void); void Job_CatchOutput(void); void Job_Make(GNode *); void Job_Init(void); Boolean Job_ParseShell(char *); int Job_Finish(void); void Job_End(void); void Job_Wait(void); void Job_AbortAll(void); void Job_TokenReturn(void); Boolean Job_TokenWithdraw(void); void Job_ServerStart(int, int, int); void Job_SetPrefix(void); Boolean Job_RunTarget(const char *, const char *); #endif /* MAKE_JOB_H */ Index: head/contrib/bmake/lst.c =================================================================== --- head/contrib/bmake/lst.c (revision 367862) +++ head/contrib/bmake/lst.c (revision 367863) @@ -1,319 +1,311 @@ -/* $NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $ */ +/* $NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig 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. */ #include "make.h" -MAKE_RCSID("$NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $"); +MAKE_RCSID("$NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig Exp $"); #ifdef HAVE_INTTYPES_H #include #elif defined(HAVE_STDINT_H) #include #endif static ListNode * LstNodeNew(ListNode *prev, ListNode *next, void *datum) { - ListNode *node = bmake_malloc(sizeof *node); - node->prev = prev; - node->next = next; - node->datum = datum; - return node; + ListNode *ln = bmake_malloc(sizeof *ln); + ln->prev = prev; + ln->next = next; + ln->datum = datum; + return ln; } /* Create and initialize a new, empty list. */ List * Lst_New(void) { List *list = bmake_malloc(sizeof *list); list->first = NULL; list->last = NULL; return list; } /* Free a list and all its nodes. The node data are not freed though. */ void Lst_Free(List *list) { - ListNode *node; - ListNode *next; + ListNode *ln, *next; - for (node = list->first; node != NULL; node = next) { - next = node->next; - free(node); + for (ln = list->first; ln != NULL; ln = next) { + next = ln->next; + free(ln); } free(list); } /* Destroy a list and free all its resources. The freeProc is called with the * datum from each node in turn before the node is freed. */ void Lst_Destroy(List *list, LstFreeProc freeProc) { - ListNode *node; - ListNode *next; + ListNode *ln, *next; - for (node = list->first; node != NULL; node = next) { - next = node->next; - freeProc(node->datum); - free(node); + for (ln = list->first; ln != NULL; ln = next) { + next = ln->next; + freeProc(ln->datum); + free(ln); } free(list); } -/* - * Functions to modify a list - */ - /* Insert a new node with the datum before the given node. */ void -Lst_InsertBefore(List *list, ListNode *node, void *datum) +Lst_InsertBefore(List *list, ListNode *ln, void *datum) { ListNode *newNode; assert(datum != NULL); - newNode = LstNodeNew(node->prev, node, datum); + newNode = LstNodeNew(ln->prev, ln, datum); - if (node->prev != NULL) - node->prev->next = newNode; - node->prev = newNode; + if (ln->prev != NULL) + ln->prev->next = newNode; + ln->prev = newNode; - if (node == list->first) + if (ln == list->first) list->first = newNode; } /* Add a piece of data at the start of the given list. */ void Lst_Prepend(List *list, void *datum) { - ListNode *node; + ListNode *ln; assert(datum != NULL); - node = LstNodeNew(NULL, list->first, datum); + ln = LstNodeNew(NULL, list->first, datum); if (list->first == NULL) { - list->first = node; - list->last = node; + list->first = ln; + list->last = ln; } else { - list->first->prev = node; - list->first = node; + list->first->prev = ln; + list->first = ln; } } /* Add a piece of data at the end of the given list. */ void Lst_Append(List *list, void *datum) { - ListNode *node; + ListNode *ln; assert(datum != NULL); - node = LstNodeNew(list->last, NULL, datum); + ln = LstNodeNew(list->last, NULL, datum); if (list->last == NULL) { - list->first = node; - list->last = node; + list->first = ln; + list->last = ln; } else { - list->last->next = node; - list->last = node; + list->last->next = ln; + list->last = ln; } } /* Remove the given node from the given list. * The datum stored in the node must be freed by the caller, if necessary. */ void -Lst_Remove(List *list, ListNode *node) +Lst_Remove(List *list, ListNode *ln) { /* unlink it from its neighbors */ - if (node->next != NULL) - node->next->prev = node->prev; - if (node->prev != NULL) - node->prev->next = node->next; + if (ln->next != NULL) + ln->next->prev = ln->prev; + if (ln->prev != NULL) + ln->prev->next = ln->next; /* unlink it from the list */ - if (list->first == node) - list->first = node->next; - if (list->last == node) - list->last = node->prev; + if (list->first == ln) + list->first = ln->next; + if (list->last == ln) + list->last = ln->prev; } /* Replace the datum in the given node with the new datum. */ void -LstNode_Set(ListNode *node, void *datum) +LstNode_Set(ListNode *ln, void *datum) { assert(datum != NULL); - node->datum = datum; + ln->datum = datum; } -/* Replace the datum in the given node to NULL. +/* Replace the datum in the given node with NULL. * Having NULL values in a list is unusual though. */ void -LstNode_SetNull(ListNode *node) +LstNode_SetNull(ListNode *ln) { - node->datum = NULL; + ln->datum = NULL; } -/* - * Functions for entire lists - */ - -/* Return the first node that contains the given datum, or NULL. */ +/* Return the first node that contains the given datum, or NULL. + * + * Time complexity: O(length(list)) */ ListNode * Lst_FindDatum(List *list, const void *datum) { - ListNode *node; + ListNode *ln; assert(datum != NULL); - for (node = list->first; node != NULL; node = node->next) - if (node->datum == datum) - return node; + for (ln = list->first; ln != NULL; ln = ln->next) + if (ln->datum == datum) + return ln; return NULL; } int Lst_ForEachUntil(List *list, LstActionUntilProc proc, void *procData) { - ListNode *node; + ListNode *ln; int result = 0; - for (node = list->first; node != NULL; node = node->next) { - result = proc(node->datum, procData); + for (ln = list->first; ln != NULL; ln = ln->next) { + result = proc(ln->datum, procData); if (result != 0) break; } return result; } -/* Move all nodes from list2 to the end of list1. - * List2 is destroyed and freed. */ +/* Move all nodes from src to the end of dst. + * The source list is destroyed and freed. */ void -Lst_MoveAll(List *list1, List *list2) +Lst_MoveAll(List *dst, List *src) { - if (list2->first != NULL) { - list2->first->prev = list1->last; - if (list1->last != NULL) - list1->last->next = list2->first; + if (src->first != NULL) { + src->first->prev = dst->last; + if (dst->last != NULL) + dst->last->next = src->first; else - list1->first = list2->first; + dst->first = src->first; - list1->last = list2->last; + dst->last = src->last; } - free(list2); + free(src); } /* Copy the element data from src to the start of dst. */ void Lst_PrependAll(List *dst, List *src) { ListNode *node; for (node = src->last; node != NULL; node = node->prev) Lst_Prepend(dst, node->datum); } /* Copy the element data from src to the end of dst. */ void Lst_AppendAll(List *dst, List *src) { ListNode *node; for (node = src->first; node != NULL; node = node->next) Lst_Append(dst, node->datum); } /* * for using the list as a queue */ /* Add the datum to the tail of the given list. */ void Lst_Enqueue(List *list, void *datum) { Lst_Append(list, datum); } /* Remove and return the datum at the head of the given list. */ void * Lst_Dequeue(List *list) { void *datum = list->first->datum; Lst_Remove(list, list->first); assert(datum != NULL); /* since NULL would mean end of the list */ return datum; } void Vector_Init(Vector *v, size_t itemSize) { v->len = 0; v->priv_cap = 10; v->itemSize = itemSize; v->items = bmake_malloc(v->priv_cap * v->itemSize); } /* Add space for a new item to the vector and return a pointer to that space. * The returned data is valid until the next modifying operation. */ void * Vector_Push(Vector *v) { if (v->len >= v->priv_cap) { v->priv_cap *= 2; v->items = bmake_realloc(v->items, v->priv_cap * v->itemSize); } v->len++; return Vector_Get(v, v->len - 1); } /* Return the pointer to the last item in the vector. * The returned data is valid until the next modifying operation. */ void * Vector_Pop(Vector *v) { assert(v->len > 0); v->len--; return Vector_Get(v, v->len); } void Vector_Done(Vector *v) { free(v->items); } Index: head/contrib/bmake/lst.h =================================================================== --- head/contrib/bmake/lst.h (revision 367862) +++ head/contrib/bmake/lst.h (revision 367863) @@ -1,187 +1,187 @@ -/* $NetBSD: lst.h,v 1.84 2020/10/28 02:43:16 rillig Exp $ */ +/* $NetBSD: lst.h,v 1.85 2020/11/10 00:32:12 rillig 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 */ /* Doubly-linked lists of arbitrary pointers. */ #ifndef MAKE_LST_H #define MAKE_LST_H #include #include #include /* A doubly-linked list of pointers. */ typedef struct List List; /* A single node in the doubly-linked list. */ typedef struct ListNode ListNode; struct ListNode { ListNode *prev; /* previous node in list, or NULL */ ListNode *next; /* next node in list, or NULL */ union { void *datum; /* datum associated with this element */ const struct GNode *priv_gnode; /* alias, just for debugging */ const char *priv_str; /* alias, just for debugging */ }; }; struct List { ListNode *first; /* first node in list */ ListNode *last; /* last node in list */ }; /* Free the datum of a node, called before freeing the node itself. */ typedef void LstFreeProc(void *); /* An action for Lst_ForEachUntil and Lst_ForEachUntilConcurrent. */ typedef int LstActionUntilProc(void *datum, void *args); /* Create or destroy a list */ /* Create a new list. */ List *Lst_New(void); /* Free the list, leaving the node data unmodified. */ void Lst_Free(List *); /* Free the list, freeing the node data using the given function. */ void Lst_Destroy(List *, LstFreeProc); /* Get information about a list */ -static inline MAKE_ATTR_UNUSED Boolean +MAKE_INLINE Boolean Lst_IsEmpty(List *list) { return list->first == NULL; } /* Find the first node that contains the given datum, or NULL. */ ListNode *Lst_FindDatum(List *, const void *); /* Modify a list */ /* Insert a datum before the given node. */ void Lst_InsertBefore(List *, ListNode *, void *); /* Place a datum at the front of the list. */ void Lst_Prepend(List *, void *); /* Place a datum at the end of the list. */ void Lst_Append(List *, void *); /* Remove the node from the list. */ void Lst_Remove(List *, ListNode *); void Lst_PrependAll(List *, List *); void Lst_AppendAll(List *, List *); void Lst_MoveAll(List *, List *); /* Node-specific functions */ /* Replace the value of the node. */ void LstNode_Set(ListNode *, void *); /* Set the value of the node to NULL. Having NULL in a list is unusual. */ void LstNode_SetNull(ListNode *); /* Iterating over a list, using a callback function */ /* Run the action for each datum of the list, until the action returns * non-zero. * * During this iteration, the list must not be modified structurally. */ int Lst_ForEachUntil(List *, LstActionUntilProc, void *); /* Using the list as a queue */ /* Add a datum at the tail of the queue. */ void Lst_Enqueue(List *, void *); /* Remove the head node of the queue and return its datum. */ void *Lst_Dequeue(List *); /* A vector is an ordered collection of items, allowing for fast indexed * access. */ typedef struct Vector { void *items; /* memory holding the items */ size_t itemSize; /* size of a single item in bytes */ size_t len; /* number of actually usable elements */ size_t priv_cap; /* capacity */ } Vector; void Vector_Init(Vector *, size_t); /* Return the pointer to the given item in the vector. * The returned data is valid until the next modifying operation. */ -static inline MAKE_ATTR_UNUSED void * +MAKE_INLINE void * Vector_Get(Vector *v, size_t i) { unsigned char *items = v->items; return items + i * v->itemSize; } void *Vector_Push(Vector *); void *Vector_Pop(Vector *); void Vector_Done(Vector *); #endif /* MAKE_LST_H */ Index: head/contrib/bmake/main.c =================================================================== --- head/contrib/bmake/main.c (revision 367862) +++ head/contrib/bmake/main.c (revision 367863) @@ -1,2283 +1,2269 @@ -/* $NetBSD: main.c,v 1.421 2020/11/01 00:24:57 rillig Exp $ */ +/* $NetBSD: main.c,v 1.476 2020/11/16 22:08:20 rillig 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. */ -/*- - * main.c -- - * The main file for this entire program. Exit routines etc - * reside here. +/* The main file for this entire program. Exit routines etc. reside here. * * Utility functions defined in this file: - * Main_ParseArgLine Takes a line of arguments, breaks them and - * treats them as if they were given when first - * invoked. Used by the parse module to implement - * the .MFLAGS target. * - * Error Print a tagged error message. The global - * MAKE variable must have been defined. This - * takes a format string and optional arguments - * for it. + * Main_ParseArgLine Parse and process command line arguments from + * a single string. Used to implement the + * special targets .MFLAGS and .MAKEFLAGS. * - * Fatal Print an error message and exit. Also takes - * a format string and arguments for it. + * Error Print a tagged error message. * - * Punt Aborts all jobs and exits with a message. Also - * takes a format string and arguments for it. + * Fatal Print an error message and exit. * + * Punt Abort all jobs and exit with a message. + * * Finish Finish things up by printing the number of - * errors which occurred, as passed to it, and - * exiting. + * errors which occurred, and exit. */ #include #include #include #include #include #if defined(MAKE_NATIVE) && defined(HAVE_SYSCTL) #include #endif #include #include "wait.h" #include #include #include #include #include "make.h" #include "dir.h" #include "job.h" #include "pathnames.h" #include "trace.h" /* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: main.c,v 1.421 2020/11/01 00:24:57 rillig Exp $"); +MAKE_RCSID("$NetBSD: main.c,v 1.476 2020/11/16 22:08:20 rillig Exp $"); #if defined(MAKE_NATIVE) && !defined(lint) __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " "The Regents of the University of California. " "All rights reserved."); #endif -#ifndef DEFMAXLOCAL -#define DEFMAXLOCAL DEFMAXJOBS +#ifndef DEFMAXLOCAL +#define DEFMAXLOCAL DEFMAXJOBS #endif #ifndef __arraycount # define __arraycount(__x) (sizeof(__x) / sizeof(__x[0])) #endif CmdOpts opts; -time_t now; /* Time at start of make */ -GNode *DEFAULT; /* .DEFAULT node */ -Boolean allPrecious; /* .PRECIOUS given on line by itself */ -Boolean deleteOnError; /* .DELETE_ON_ERROR: set */ +time_t now; /* Time at start of make */ +GNode *defaultNode; /* .DEFAULT node */ +Boolean allPrecious; /* .PRECIOUS given on line by itself */ +Boolean deleteOnError; /* .DELETE_ON_ERROR: set */ -static int maxJobTokens; /* -j argument */ -Boolean enterFlagObj; /* -w and objdir != srcdir */ +static int maxJobTokens; /* -j argument */ +Boolean enterFlagObj; /* -w and objdir != srcdir */ -Boolean oldVars; /* variable substitution style */ -static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ -Boolean doing_depend; /* Set while reading .depend */ -static Boolean jobsRunning; /* TRUE if the jobs might be running */ -static const char * tracefile; -static int ReadMakefile(const char *); -static void usage(void) MAKE_ATTR_DEAD; -static void purge_cached_realpaths(void); +Boolean preserveUndefined; +static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ +Boolean doing_depend; /* Set while reading .depend */ +static Boolean jobsRunning; /* TRUE if the jobs might be running */ +static const char *tracefile; +static int ReadMakefile(const char *); +static void purge_relative_cached_realpaths(void); -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 */ +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; static int errors = 0; +static HashTable cached_realpaths; /* - * On some systems MACHINE is defined as something other than - * what we want. - */ -#ifdef FORCE_MACHINE -# undef MACHINE -# define MACHINE FORCE_MACHINE -#endif - -extern SearchPath *parseIncPath; - -/* * For compatibility with the POSIX version of MAKEFLAGS that includes - * all the options with out -, convert flags to -f -l -a -g -s. + * all the options without '-', convert 'flags' to '-f -l -a -g -s'. */ static char * explode(const char *flags) { - size_t len; - char *nf, *st; - const char *f; + size_t len; + char *nf, *st; + const char *f; - if (flags == NULL) - return NULL; + if (flags == NULL) + return NULL; - for (f = flags; *f; f++) - if (!ch_isalpha(*f)) - break; + for (f = flags; *f; f++) + if (!ch_isalpha(*f)) + break; - if (*f) - return bmake_strdup(flags); + 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; + len = strlen(flags); + st = nf = bmake_malloc(len * 3 + 1); + while (*flags) { + *nf++ = '-'; + *nf++ = *flags++; + *nf++ = ' '; + } + *nf = '\0'; + return st; } +/* + * usage -- + * exit with usage message + */ +MAKE_ATTR_DEAD static void +usage(void) +{ + size_t prognameLen = strcspn(progname, "["); + + (void)fprintf(stderr, +"usage: %.*s [-BeikNnqrSstWwX]\n" +" [-C directory] [-D variable] [-d flags] [-f makefile]\n" +" [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n" +" [-V variable] [-v variable] [variable=value] [target ...]\n", + (int)prognameLen, progname); + exit(2); +} + static void parse_debug_option_F(const char *modules) { - const char *mode; - size_t len; - char *fname; + const char *mode; + size_t len; + char *fname; - if (opts.debug_file != stdout && opts.debug_file != stderr) - fclose(opts.debug_file); + if (opts.debug_file != stdout && opts.debug_file != stderr) + fclose(opts.debug_file); - if (*modules == '+') { - modules++; - mode = "a"; - } else - mode = "w"; + if (*modules == '+') { + modules++; + mode = "a"; + } else + mode = "w"; - if (strcmp(modules, "stdout") == 0) { - opts.debug_file = stdout; - return; - } - if (strcmp(modules, "stderr") == 0) { - opts.debug_file = stderr; - return; - } + if (strcmp(modules, "stdout") == 0) { + opts.debug_file = stdout; + return; + } + if (strcmp(modules, "stderr") == 0) { + opts.debug_file = stderr; + return; + } - len = strlen(modules); - fname = bmake_malloc(len + 20); - memcpy(fname, modules, len + 1); + len = strlen(modules); + fname = bmake_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()); + /* Let the filename be modified by the pid */ + if (strcmp(fname + len - 3, ".%d") == 0) + snprintf(fname + len - 2, 20, "%d", getpid()); - opts.debug_file = fopen(fname, mode); - if (!opts.debug_file) { - fprintf(stderr, "Cannot open debug file %s\n", - fname); - usage(); - } - free(fname); + opts.debug_file = fopen(fname, mode); + if (opts.debug_file == NULL) { + fprintf(stderr, "Cannot open debug file %s\n", + fname); + usage(); + } + free(fname); } static void parse_debug_options(const char *argvalue) { const char *modules; + DebugFlags debug = opts.debug; for (modules = argvalue; *modules; ++modules) { switch (*modules) { case '0': /* undocumented, only intended for tests */ - opts.debug &= DEBUG_LINT; + debug = DEBUG_NONE; break; case 'A': - opts.debug = ~(0|DEBUG_LINT); + debug = DEBUG_ALL; break; case 'a': - opts.debug |= DEBUG_ARCH; + debug |= DEBUG_ARCH; break; case 'C': - opts.debug |= DEBUG_CWD; + debug |= DEBUG_CWD; break; case 'c': - opts.debug |= DEBUG_COND; + debug |= DEBUG_COND; break; case 'd': - opts.debug |= DEBUG_DIR; + debug |= DEBUG_DIR; break; case 'e': - opts.debug |= DEBUG_ERROR; + debug |= DEBUG_ERROR; break; case 'f': - opts.debug |= DEBUG_FOR; + debug |= DEBUG_FOR; break; case 'g': if (modules[1] == '1') { - opts.debug |= DEBUG_GRAPH1; - ++modules; + debug |= DEBUG_GRAPH1; + modules++; + } else if (modules[1] == '2') { + debug |= DEBUG_GRAPH2; + modules++; + } else if (modules[1] == '3') { + debug |= DEBUG_GRAPH3; + modules++; } - else if (modules[1] == '2') { - opts.debug |= DEBUG_GRAPH2; - ++modules; - } - else if (modules[1] == '3') { - opts.debug |= DEBUG_GRAPH3; - ++modules; - } break; case 'h': - opts.debug |= DEBUG_HASH; + debug |= DEBUG_HASH; break; case 'j': - opts.debug |= DEBUG_JOB; + debug |= DEBUG_JOB; break; case 'L': - opts.debug |= DEBUG_LINT; + opts.lint = TRUE; break; case 'l': - opts.debug |= DEBUG_LOUD; + debug |= DEBUG_LOUD; break; case 'M': - opts.debug |= DEBUG_META; + debug |= DEBUG_META; break; case 'm': - opts.debug |= DEBUG_MAKE; + debug |= DEBUG_MAKE; break; case 'n': - opts.debug |= DEBUG_SCRIPT; + debug |= DEBUG_SCRIPT; break; case 'p': - opts.debug |= DEBUG_PARSE; + debug |= DEBUG_PARSE; break; case 's': - opts.debug |= DEBUG_SUFF; + debug |= DEBUG_SUFF; break; case 't': - opts.debug |= DEBUG_TARG; + debug |= DEBUG_TARG; break; case 'V': opts.debugVflag = TRUE; break; case 'v': - opts.debug |= DEBUG_VAR; + debug |= DEBUG_VAR; break; case 'x': - opts.debug |= DEBUG_SHELL; + debug |= DEBUG_SHELL; break; case 'F': parse_debug_option_F(modules + 1); goto debug_setbuf; default: (void)fprintf(stderr, "%s: illegal argument to d option -- %c\n", progname, *modules); usage(); } } + debug_setbuf: + opts.debug = debug; + /* * Make the debug_file unbuffered, and make * stdout line buffered (unless debugfile == stdout). */ setvbuf(opts.debug_file, NULL, _IONBF, 0); if (opts.debug_file != stdout) { setvbuf(stdout, NULL, _IOLBF, 0); } } /* * does path contain any relative components */ static Boolean is_relpath(const char *path) { const char *cp; if (path[0] != '/') return TRUE; cp = path; while ((cp = strstr(cp, "/.")) != NULL) { cp += 2; + if (*cp == '.') + cp++; if (cp[0] == '/' || cp[0] == '\0') return TRUE; - else if (cp[0] == '.') { - if (cp[1] == '/' || cp[1] == '\0') - return TRUE; - } } return FALSE; } static void MainParseArgChdir(const char *argvalue) { struct stat sa, sb; 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); } if (!is_relpath(argvalue) && stat(argvalue, &sa) != -1 && stat(curdir, &sb) != -1 && sa.st_ino == sb.st_ino && sa.st_dev == sb.st_dev) strncpy(curdir, argvalue, MAXPATHLEN); ignorePWD = TRUE; } static void MainParseArgJobsInternal(const char *argvalue) { - if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) { + char end; + if (sscanf(argvalue, "%d,%d%c", &jp_0, &jp_1, &end) != 2) { (void)fprintf(stderr, - "%s: internal error -- J option malformed (%s)\n", - progname, argvalue); + "%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; opts.compatMake = TRUE; } else { Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); } } static void MainParseArgJobs(const char *argvalue) { char *p; forceJobs = TRUE; opts.maxJobs = (int)strtol(argvalue, &p, 0); if (*p != '\0' || opts.maxJobs < 1) { (void)fprintf(stderr, "%s: illegal argument to -j -- must be positive integer!\n", progname); exit(1); /* XXX: why not 2? */ } Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); Var_Set(".MAKE.JOBS", argvalue, VAR_GLOBAL); maxJobTokens = opts.maxJobs; } static void MainParseArgSysInc(const char *argvalue) { /* look for magic parent directory search string */ if (strncmp(".../", argvalue, 4) == 0) { char *found_path = Dir_FindHereOrAbove(curdir, argvalue + 4); if (found_path == NULL) return; (void)Dir_AddDir(sysIncPath, found_path); free(found_path); } else { (void)Dir_AddDir(sysIncPath, argvalue); } Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); } static Boolean MainParseArg(char c, const char *argvalue) { switch (c) { case '\0': break; case 'B': opts.compatMake = TRUE; Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL); Var_Set(MAKE_MODE, "compat", VAR_GLOBAL); break; case 'C': MainParseArgChdir(argvalue); break; case 'D': if (argvalue[0] == '\0') return FALSE; Var_Set(argvalue, "1", VAR_GLOBAL); Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); break; case 'I': Parse_AddIncludeDir(argvalue); Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); break; case 'J': MainParseArgJobsInternal(argvalue); break; case 'N': opts.noExecute = TRUE; opts.noRecursiveExecute = TRUE; Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL); break; case 'S': opts.keepgoing = FALSE; Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL); break; case 'T': tracefile = bmake_strdup(argvalue); Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); break; case 'V': case 'v': - opts.printVars = c == 'v' ? EXPAND_VARS : COMPAT_VARS; + opts.printVars = c == 'v' ? PVM_EXPANDED : PVM_UNEXPANDED; Lst_Append(opts.variables, bmake_strdup(argvalue)); /* XXX: Why always -V? */ Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); break; case 'W': opts.parseWarnFatal = TRUE; + /* XXX: why no Var_Append? */ break; case 'X': opts.varNoExportEnv = TRUE; Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL); break; case 'd': /* 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': opts.checkEnvFirst = TRUE; Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL); break; case 'f': Lst_Append(opts.makefiles, bmake_strdup(argvalue)); break; case 'i': opts.ignoreErrors = TRUE; Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL); break; case 'j': MainParseArgJobs(argvalue); break; case 'k': opts.keepgoing = TRUE; Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL); break; case 'm': MainParseArgSysInc(argvalue); + /* XXX: why no Var_Append? */ break; case 'n': opts.noExecute = TRUE; Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL); break; case 'q': opts.queryFlag = TRUE; /* Kind of nonsensical, wot? */ Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL); break; case 'r': opts.noBuiltins = TRUE; Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL); break; case 's': opts.beSilent = TRUE; Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL); break; case 't': opts.touchFlag = TRUE; Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL); break; case 'w': opts.enterFlag = TRUE; Var_Append(MAKEFLAGS, "-w", VAR_GLOBAL); break; default: case '?': usage(); } return TRUE; } /* Parse the given arguments. Called from main() and from * Main_ParseArgLine() when the .MAKEFLAGS target is used. * * The arguments must be treated as read-only and will be freed after the * call. * * XXX: Deal with command line overriding .MAKEFLAGS in makefile */ static void MainParseArgs(int argc, char **argv) { char c; int arginc; char *argvalue; char *optscan; Boolean inOption, dashDash = FALSE; const char *optspecs = "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstv:w"; /* Can't actually use getopt(3) because rescanning is not portable */ rearg: inOption = FALSE; optscan = NULL; while (argc > 1) { const char *optspec; if (!inOption) optscan = argv[1]; c = *optscan++; arginc = 0; if (inOption) { if (c == '\0') { - ++argv; - --argc; + argv++; + argc--; inOption = FALSE; continue; } } else { if (c != '-' || dashDash) break; inOption = TRUE; c = *optscan++; } /* '-' found at some earlier point */ optspec = strchr(optspecs, c); if (c != '\0' && optspec != NULL && optspec[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 '-': dashDash = TRUE; break; default: if (!MainParseArg(c, argvalue)) goto noarg; } 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) { VarAssign var; if (Parse_IsVar(argv[1], &var)) { Parse_DoVar(&var, VAR_CMDLINE); } else { - if (!*argv[1]) + if (argv[1][0] == '\0') Punt("illegal (null) argument."); - if (*argv[1] == '-' && !dashDash) + if (argv[1][0] == '-' && !dashDash) goto rearg; Lst_Append(opts.create, bmake_strdup(argv[1])); } } return; noarg: (void)fprintf(stderr, "%s: option requires an argument -- %c\n", progname, c); usage(); } /* Break a line of arguments into words and parse them. * * Used when a .MFLAGS or .MAKEFLAGS target is encountered during parsing and * by main() when reading the MAKEFLAGS environment variable. */ void Main_ParseArgLine(const char *line) { Words words; - void *p1; - const char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1); char *buf; if (line == NULL) return; for (; *line == ' '; ++line) continue; - if (!*line) + if (line[0] == '\0') 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 = str_concat3(argv0, " ", line); - free(p1); + { + void *freeIt; + const char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &freeIt); + buf = str_concat3(argv0, " ", line); + free(freeIt); + } words = Str_Words(buf, TRUE); if (words.words == NULL) { Error("Unterminated quoted string [%s]", buf); free(buf); return; } free(buf); MainParseArgs((int)words.len, words.words); Words_Free(words); } Boolean -Main_SetObjdir(const char *fmt, ...) +Main_SetObjdir(Boolean writable, const char *fmt, ...) { struct stat sb; char *path; char buf[MAXPATHLEN + 1]; char buf2[MAXPATHLEN + 1]; Boolean rc = FALSE; va_list ap; va_start(ap, fmt); vsnprintf(path = buf, MAXPATHLEN, fmt, ap); va_end(ap); if (path[0] != '/') { snprintf(buf2, MAXPATHLEN, "%s/%s", curdir, path); path = buf2; } /* look for the directory and try to chdir there */ if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { - /* if not .CURDIR it must be writable */ - if ((strcmp(path, curdir) != 0 && access(path, W_OK) != 0) || - chdir(path)) { - (void)fprintf(stderr, "make warning: %s: %s.\n", - path, strerror(errno)); + if ((writable && access(path, W_OK) != 0) || + (chdir(path) != 0)) { + (void)fprintf(stderr, "%s warning: %s: %s.\n", + progname, path, strerror(errno)); } else { snprintf(objdir, sizeof objdir, "%s", path); Var_Set(".OBJDIR", objdir, VAR_GLOBAL); setenv("PWD", objdir, 1); Dir_InitDot(); - purge_cached_realpaths(); + purge_relative_cached_realpaths(); rc = TRUE; if (opts.enterFlag && strcmp(objdir, curdir) != 0) enterFlagObj = TRUE; } } return rc; } static Boolean -Main_SetVarObjdir(const char *var, const char *suffix) +SetVarObjdir(Boolean writable, const char *var, const char *suffix) { void *path_freeIt; const char *path = Var_Value(var, VAR_CMDLINE, &path_freeIt); const char *xpath; char *xpath_freeIt; if (path == NULL || path[0] == '\0') { bmake_free(path_freeIt); return FALSE; } /* expand variable substitutions */ xpath = path; xpath_freeIt = NULL; if (strchr(path, '$') != 0) { (void)Var_Subst(path, VAR_GLOBAL, VARE_WANTRES, &xpath_freeIt); /* TODO: handle errors */ xpath = xpath_freeIt; } - (void)Main_SetObjdir("%s%s", xpath, suffix); + (void)Main_SetObjdir(writable, "%s%s", xpath, suffix); bmake_free(xpath_freeIt); bmake_free(path_freeIt); return TRUE; } -/* Read and parse the makefile. - * Return TRUE if reading the makefile succeeded. */ -static int -ReadMakefileSucceeded(void *fname, void *unused) -{ - return ReadMakefile(fname) == 0; -} - +/* Splits str into words, adding them to the list. + * The string must be kept alive as long as the list. */ int -str2Lst_Append(StringList *lp, char *str, const char *sep) +str2Lst_Append(StringList *lp, char *str) { - char *cp; - int n; + char *cp; + int n; - if (!sep) - sep = " \t"; + const char *sep = " \t"; - for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) { - Lst_Append(lp, cp); - n++; - } - return n; + for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) { + Lst_Append(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) + if (getcwd(dir, sizeof dir) == NULL) return; - len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir); + 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 *mode_freeIt = NULL; + char *mode_freeIt = NULL; - if (mode == NULL) { - (void)Var_Subst("${" MAKE_MODE ":tl}", - VAR_GLOBAL, VARE_WANTRES, &mode_freeIt); - /* TODO: handle errors */ - mode = mode_freeIt; - } - - if (mode[0] != '\0') { - if (strstr(mode, "compat")) { - opts.compatMake = TRUE; - forceJobs = FALSE; + if (mode == NULL) { + (void)Var_Subst("${" MAKE_MODE ":tl}", + VAR_GLOBAL, VARE_WANTRES, &mode_freeIt); + /* TODO: handle errors */ + mode = mode_freeIt; } + + if (mode[0] != '\0') { + if (strstr(mode, "compat")) { + opts.compatMake = TRUE; + forceJobs = FALSE; + } #if USE_META - if (strstr(mode, "meta")) - meta_mode_init(mode); + if (strstr(mode, "meta")) + meta_mode_init(mode); #endif - } + } - free(mode_freeIt); + free(mode_freeIt); } static void PrintVar(const char *varname, Boolean expandVars) { if (strchr(varname, '$')) { char *evalue; (void)Var_Subst(varname, VAR_GLOBAL, VARE_WANTRES, &evalue); /* TODO: handle errors */ printf("%s\n", evalue); bmake_free(evalue); } else if (expandVars) { char *expr = str_concat3("${", varname, "}"); char *evalue; (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &evalue); /* TODO: handle errors */ free(expr); printf("%s\n", evalue); bmake_free(evalue); } else { void *freeIt; const char *value = Var_Value(varname, VAR_GLOBAL, &freeIt); printf("%s\n", value ? value : ""); bmake_free(freeIt); } } +/* + * Return a Boolean based on a variable. + * + * If the knob is not set, return the fallback. + * If set, anything that looks or smells like "No", "False", "Off", "0", etc. + * is FALSE, otherwise TRUE. + */ +Boolean +GetBooleanVar(const char *varname, Boolean fallback) +{ + char *expr = str_concat3("${", varname, ":U}"); + char *value; + Boolean res; + + (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &value); + /* TODO: handle errors */ + res = ParseBoolean(value, fallback); + free(value); + free(expr); + return res; +} + static void doPrintVars(void) { StringListNode *ln; Boolean expandVars; - if (opts.printVars == EXPAND_VARS) + if (opts.printVars == PVM_EXPANDED) expandVars = TRUE; else if (opts.debugVflag) expandVars = FALSE; else - expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE); + expandVars = GetBooleanVar(".MAKE.EXPAND_VARIABLES", FALSE); for (ln = opts.variables->first; ln != NULL; ln = ln->next) { const char *varname = ln->datum; PrintVar(varname, expandVars); } } static Boolean runTargets(void) { - GNodeList *targs; /* target nodes to create -- passed to Make_Init */ + GNodeList *targs; /* target nodes to create */ Boolean outOfDate; /* FALSE if all targets up to date */ /* * 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(opts.create)) targs = Parse_MainName(); else targs = Targ_FindList(opts.create); if (!opts.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 (!opts.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); outOfDate = FALSE; } Lst_Free(targs); return outOfDate; } /* * 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. */ static void InitVarTargets(void) { StringListNode *ln; if (Lst_IsEmpty(opts.create)) { Var_Set(".TARGETS", "", VAR_GLOBAL); return; } for (ln = opts.create->first; ln != NULL; ln = ln->next) { char *name = ln->datum; Var_Append(".TARGETS", name, VAR_GLOBAL); } } static void InitRandom(void) { struct timeval tv; gettimeofday(&tv, NULL); srandom((unsigned int)(tv.tv_sec + tv.tv_usec)); } static const char * -init_machine(const struct utsname *utsname) +InitVarMachine(const struct utsname *utsname) { #ifdef FORCE_MACHINE - const char *machine = FORCE_MACHINE; + return FORCE_MACHINE; #else const char *machine = getenv("MACHINE"); -#endif + if (machine != NULL) return machine; -#ifdef MAKE_NATIVE +#if defined(MAKE_NATIVE) return utsname->machine; -#else -#ifdef MAKE_MACHINE +#elif defined(MAKE_MACHINE) return MAKE_MACHINE; #else return "unknown"; #endif #endif } static const char * -init_machine_arch(void) +InitVarMachineArch(void) { +#ifdef FORCE_MACHINE_ARCH + return FORCE_MACHINE_ARCH; +#else const char *env = getenv("MACHINE_ARCH"); if (env != NULL) return env; #if defined(MAKE_NATIVE) && defined(CTL_HW) { struct utsname utsname; - static char machine_arch_buf[sizeof(utsname.machine)]; + static char machine_arch_buf[sizeof utsname.machine]; const int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; - size_t len = sizeof(machine_arch_buf); + 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); } return machine_arch_buf; } -#else -#ifndef MACHINE_ARCH -#ifdef MAKE_MACHINE_ARCH +#elif defined(MACHINE_ARCH) + return MACHINE_ARCH; +#elif defined(MAKE_MACHINE_ARCH) return MAKE_MACHINE_ARCH; #else return "unknown"; #endif -#else - return MACHINE_ARCH; #endif -#endif } #ifndef NO_PWD_OVERRIDE /* * 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 variable expression. */ static void HandlePWD(const struct stat *curdir_st) { char *pwd; void *prefix_freeIt, *makeobjdir_freeIt; const char *makeobjdir; struct stat pwd_st; if (ignorePWD || (pwd = getenv("PWD")) == NULL) return; - if (Var_Value("MAKEOBJDIRPREFIX", VAR_CMDLINE, &prefix_freeIt) != NULL) { + if (Var_Value("MAKEOBJDIRPREFIX", VAR_CMDLINE, &prefix_freeIt) != + NULL) { bmake_free(prefix_freeIt); return; } makeobjdir = Var_Value("MAKEOBJDIR", VAR_CMDLINE, &makeobjdir_freeIt); if (makeobjdir != NULL && strchr(makeobjdir, '$') != NULL) goto ignore_pwd; if (stat(pwd, &pwd_st) == 0 && curdir_st->st_ino == pwd_st.st_ino && curdir_st->st_dev == pwd_st.st_dev) (void)strncpy(curdir, pwd, MAXPATHLEN); ignore_pwd: bmake_free(makeobjdir_freeIt); } #endif /* * 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-MACHINE_ARCH, _PATH_OBJDIR.MACHINE, * and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none * of these paths exist, just use .CURDIR. */ static void InitObjdir(const char *machine, const char *machine_arch) { + Boolean writable; + Dir_InitDir(curdir); - (void)Main_SetObjdir("%s", curdir); + writable = GetBooleanVar("MAKE_OBJDIR_CHECK_WRITABLE", TRUE); + (void)Main_SetObjdir(FALSE, "%s", curdir); - if (!Main_SetVarObjdir("MAKEOBJDIRPREFIX", curdir) && - !Main_SetVarObjdir("MAKEOBJDIR", "") && - !Main_SetObjdir("%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) && - !Main_SetObjdir("%s.%s", _PATH_OBJDIR, machine) && - !Main_SetObjdir("%s", _PATH_OBJDIR)) - (void)Main_SetObjdir("%s%s", _PATH_OBJDIRPREFIX, curdir); + if (!SetVarObjdir(writable, "MAKEOBJDIRPREFIX", curdir) && + !SetVarObjdir(writable, "MAKEOBJDIR", "") && + !Main_SetObjdir(writable, "%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) && + !Main_SetObjdir(writable, "%s.%s", _PATH_OBJDIR, machine) && + !Main_SetObjdir(writable, "%s", _PATH_OBJDIR)) + (void)Main_SetObjdir(writable, "%s%s", _PATH_OBJDIRPREFIX, curdir); } /* get rid of resource limit on file descriptors */ static void UnlimitFiles(void) { #if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)) 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 } static void CmdOpts_Init(void) { opts.compatMake = FALSE; /* No compat mode */ opts.debug = 0; /* No debug verbosity, please. */ /* opts.debug_file has been initialized earlier */ + opts.lint = FALSE; opts.debugVflag = FALSE; opts.checkEnvFirst = FALSE; opts.makefiles = Lst_New(); opts.ignoreErrors = FALSE; /* Pay attention to non-zero returns */ opts.maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */ opts.keepgoing = FALSE; /* Stop on error */ opts.noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ opts.noExecute = FALSE; /* Execute all commands */ opts.queryFlag = FALSE; /* This is not just a check-run */ opts.noBuiltins = FALSE; /* Read the built-in rules */ opts.beSilent = FALSE; /* Print commands as executed */ opts.touchFlag = FALSE; /* Actually update targets */ - opts.printVars = 0; + opts.printVars = PVM_NONE; opts.variables = Lst_New(); opts.parseWarnFatal = FALSE; opts.enterFlag = FALSE; opts.varNoExportEnv = FALSE; opts.create = Lst_New(); } /* Initialize MAKE and .MAKE to the path of the executable, so that it can be * found by execvp(3) and the shells, even after a chdir. * * If it's a relative path and contains a '/', resolve it to an absolute path. * Otherwise keep it as is, assuming it will be found in the PATH. */ static void InitVarMake(const char *argv0) { const char *make = argv0; if (argv0[0] != '/' && strchr(argv0, '/') != NULL) { char pathbuf[MAXPATHLEN]; const char *abs = cached_realpath(argv0, pathbuf); struct stat st; if (abs != NULL && abs[0] == '/' && stat(make, &st) == 0) make = abs; } Var_Set("MAKE", make, VAR_GLOBAL); Var_Set(".MAKE", make, VAR_GLOBAL); } +/* Add the directories from the colon-separated syspath to defSysIncPath. + * After returning, the contents of syspath is unspecified. */ static void InitDefSysIncPath(char *syspath) { static char defsyspath[] = _PATH_DEFSYSPATH; char *start, *cp; /* * 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. */ - /* XXX: mismatch: the -m option sets sysIncPath, not syspath */ if (syspath == NULL || syspath[0] == '\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 == ':') { + if (*cp == ':') *cp++ = '\0'; - } + /* look for magic parent directory search string */ - if (strncmp(".../", start, 4) != 0) { - (void)Dir_AddDir(defSysIncPath, start); - } else { + if (strncmp(start, ".../", 4) == 0) { char *dir = Dir_FindHereOrAbove(curdir, start + 4); if (dir != NULL) { (void)Dir_AddDir(defSysIncPath, dir); free(dir); } + } else { + (void)Dir_AddDir(defSysIncPath, start); } } if (syspath != defsyspath) free(syspath); } static void ReadBuiltinRules(void) { + StringListNode *ln; StringList *sysMkPath = Lst_New(); + Dir_Expand(_PATH_DEFSYSMK, - Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath, - sysMkPath); + Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath, + sysMkPath); if (Lst_IsEmpty(sysMkPath)) Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); - if (!Lst_ForEachUntil(sysMkPath, ReadMakefileSucceeded, NULL)) - Fatal("%s: cannot open %s.", progname, - (char *)sysMkPath->first->datum); - /* XXX: sysMkPath is not freed */ + + for (ln = sysMkPath->first; ln != NULL; ln = ln->next) + if (ReadMakefile(ln->datum) == 0) + break; + + if (ln == NULL) + Fatal("%s: cannot open %s.", + progname, (const char *)sysMkPath->first->datum); + + /* Free the list but not the actual filenames since these may still + * be used in GNodes. */ + Lst_Free(sysMkPath); } static void InitMaxJobs(void) { char *value; int n; if (forceJobs || opts.compatMake || !Var_Exists(".MAKE.JOBS", VAR_GLOBAL)) return; (void)Var_Subst("${.MAKE.JOBS}", VAR_GLOBAL, VARE_WANTRES, &value); /* TODO: handle errors */ n = (int)strtol(value, NULL, 0); if (n < 1) { (void)fprintf(stderr, - "%s: illegal value for .MAKE.JOBS " - "-- must be positive integer!\n", - progname); + "%s: illegal value for .MAKE.JOBS " + "-- must be positive integer!\n", + progname); exit(1); } if (n != opts.maxJobs) { Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); Var_Append(MAKEFLAGS, value, VAR_GLOBAL); } opts.maxJobs = n; maxJobTokens = opts.maxJobs; forceJobs = TRUE; free(value); } /* * 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 environment * variable, i.e. ::... */ static void InitVpath(void) { char *vpath, savec, *path; if (!Var_Exists("VPATH", VAR_CMDLINE)) return; (void)Var_Subst("${VPATH}", VAR_CMDLINE, VARE_WANTRES, &vpath); /* TODO: handle errors */ path = vpath; do { char *cp; /* 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); } static void -ReadMakefiles(void) +ReadAllMakefiles(StringList *makefiles) { - if (opts.makefiles->first != NULL) { - StringListNode *ln; + StringListNode *ln; - for (ln = opts.makefiles->first; ln != NULL; ln = ln->next) { - if (ReadMakefile(ln->datum) != 0) - Fatal("%s: cannot open %s.", - progname, (char *)ln->datum); - } - } else { - char *p1; - (void)Var_Subst("${" MAKEFILE_PREFERENCE "}", - VAR_CMDLINE, VARE_WANTRES, &p1); - /* TODO: handle errors */ - (void)str2Lst_Append(opts.makefiles, p1, NULL); - (void)Lst_ForEachUntil(opts.makefiles, - ReadMakefileSucceeded, NULL); - free(p1); + for (ln = makefiles->first; ln != NULL; ln = ln->next) { + const char *fname = ln->datum; + if (ReadMakefile(fname) != 0) + Fatal("%s: cannot open %s.", progname, fname); } } static void -CleanUp(void) +ReadFirstDefaultMakefile(void) { -#ifdef CLEANUP - Lst_Destroy(opts.variables, free); - Lst_Free(opts.makefiles); /* don't free, may be used in GNodes */ - Lst_Destroy(opts.create, free); -#endif + StringListNode *ln; + char *prefs; - /* print the graph now it's been processed if the user requested it */ - if (DEBUG(GRAPH2)) - Targ_PrintGraph(2); + (void)Var_Subst("${" MAKE_MAKEFILE_PREFERENCE "}", + VAR_CMDLINE, VARE_WANTRES, &prefs); + /* TODO: handle errors */ - Trace_Log(MAKEEND, 0); + /* XXX: This should use a local list instead of opts.makefiles + * since these makefiles do not come from the command line. They + * also have different semantics in that only the first file that + * is found is processed. See ReadAllMakefiles. */ + (void)str2Lst_Append(opts.makefiles, prefs); - if (enterFlagObj) - printf("%s: Leaving directory `%s'\n", progname, objdir); - if (opts.enterFlag) - printf("%s: Leaving directory `%s'\n", progname, curdir); + for (ln = opts.makefiles->first; ln != NULL; ln = ln->next) + if (ReadMakefile(ln->datum) == 0) + break; -#ifdef USE_META - meta_finish(); -#endif - Suff_End(); - Targ_End(); - Arch_End(); - Var_End(); - Parse_End(); - Dir_End(); - Job_End(); - Trace_End(); + free(prefs); } -/*- - * 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) +/* Initialize variables such as MAKE, MACHINE, .MAKEFLAGS. + * Initialize a few modules. + * Parse the arguments from MAKEFLAGS and the command line. */ +static void +main_Init(int argc, char **argv) { - Boolean outOfDate; /* FALSE if all targets up to date */ struct stat sa; const char *machine; const char *machine_arch; char *syspath = getenv("MAKESYSPATH"); struct utsname utsname; /* default to writing debug to stderr */ opts.debug_file = stderr; + HashTable_Init(&cached_realpaths); + #ifdef SIGINFO (void)bmake_signal(SIGINFO, siginfo); #endif InitRandom(); if ((progname = strrchr(argv[0], '/')) != NULL) progname++; else progname = argv[0]; UnlimitFiles(); if (uname(&utsname) == -1) { - (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, - strerror(errno)); - exit(2); + (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. */ - machine = init_machine(&utsname); - machine_arch = init_machine_arch(); + machine = InitVarMachine(&utsname); + machine_arch = InitVarMachineArch(); - myPid = getpid(); /* remember this for vFork() */ + 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 */ + Targ_Init(); + Var_Init(); Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL); Var_Set("MACHINE", machine, VAR_GLOBAL); Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL); #ifdef MAKE_VERSION Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL); #endif Var_Set(".newline", "\n", VAR_GLOBAL); /* 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); + Var_Set(MAKE_MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST, VAR_GLOBAL); Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL); CmdOpts_Init(); - allPrecious = FALSE; /* Remove targets when interrupted */ - deleteOnError = FALSE; /* Historical default behavior */ + allPrecious = FALSE; /* Remove targets when interrupted */ + deleteOnError = FALSE; /* Historical default behavior */ jobsRunning = FALSE; maxJobTokens = opts.maxJobs; 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(); InitVarMake(argv[0]); Var_Set(MAKEFLAGS, "", VAR_GLOBAL); Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL); Var_Set("MFLAGS", "", VAR_GLOBAL); Var_Set(".ALLTARGETS", "", VAR_GLOBAL); /* some makefiles need to know this */ Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMDLINE); - /* - * Set some other useful macros - */ + /* Set some other useful variables. */ { - char tmp[64], *ep; + char tmp[64], *ep = getenv(MAKE_LEVEL_ENV); - 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); - snprintf(tmp, sizeof(tmp), "%u", myPid); - Var_Set(".MAKE.PID", tmp, VAR_GLOBAL); - snprintf(tmp, sizeof(tmp), "%u", getppid()); - Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL); + makelevel = ep != NULL && ep[0] != '\0' ? atoi(ep) : 0; + if (makelevel < 0) + makelevel = 0; + snprintf(tmp, sizeof tmp, "%d", makelevel); + Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL); + snprintf(tmp, sizeof tmp, "%u", myPid); + Var_Set(".MAKE.PID", tmp, VAR_GLOBAL); + snprintf(tmp, sizeof tmp, "%u", getppid()); + Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL); } if (makelevel > 0) { char pn[1024]; - snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel); + snprintf(pn, sizeof pn, "%s[%d]", progname, makelevel); progname = bmake_strdup(pn); } #ifdef USE_META meta_init(); #endif Dir_Init(); /* * 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 { - char *p1 = explode(getenv("MAKEFLAGS")); - Main_ParseArgLine(p1); - free(p1); + char *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 (opts.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); + (void)fprintf(stderr, "%s: %s: %s.\n", + progname, curdir, strerror(errno)); + exit(2); } #ifndef NO_PWD_OVERRIDE HandlePWD(&sa); #endif Var_Set(".CURDIR", curdir, VAR_GLOBAL); InitObjdir(machine, machine_arch); /* * Initialize archive, target and suffix modules in preparation for * parsing the makefile(s) */ Arch_Init(); - Targ_Init(); Suff_Init(); Trace_Init(tracefile); - DEFAULT = NULL; + defaultNode = NULL; (void)time(&now); Trace_Log(MAKESTART, NULL); InitVarTargets(); InitDefSysIncPath(syspath); +} - /* - * Read in the built-in rules first, followed by the specified - * makefiles, or the default makefile and Makefile, in that order, - * if no makefiles were given on the command line. - */ +/* Read the system makefile followed by either makefile, Makefile or the + * files given by the -f option. Exit on parse errors. */ +static void +main_ReadFiles(void) +{ + if (!opts.noBuiltins) ReadBuiltinRules(); - ReadMakefiles(); - + + if (!Lst_IsEmpty(opts.makefiles)) + ReadAllMakefiles(opts.makefiles); + else + ReadFirstDefaultMakefile(); +} + +/* Compute the dependency graph. */ +static void +main_PrepareMaking(void) +{ /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ - if (!opts.noBuiltins || !opts.printVars) { - /* ignore /dev/null and anything starting with "no" */ - (void)Var_Subst("${.MAKE.DEPENDFILE:N/dev/null:Nno*:T}", - VAR_CMDLINE, VARE_WANTRES, &makeDependfile); - if (makeDependfile[0] != '\0') { - /* TODO: handle errors */ - doing_depend = TRUE; - (void)ReadMakefile(makeDependfile); - doing_depend = FALSE; - } + if (!opts.noBuiltins || opts.printVars == PVM_NONE) { + /* ignore /dev/null and anything starting with "no" */ + (void)Var_Subst("${.MAKE.DEPENDFILE:N/dev/null:Nno*:T}", + VAR_CMDLINE, VARE_WANTRES, &makeDependfile); + if (makeDependfile[0] != '\0') { + /* TODO: handle errors */ + doing_depend = TRUE; + (void)ReadMakefile(makeDependfile); + doing_depend = FALSE; + } } if (enterFlagObj) printf("%s: Entering directory `%s'\n", progname, objdir); MakeMode(NULL); { - void *freeIt; - Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &freeIt), - VAR_GLOBAL); - bmake_free(freeIt); - + void *freeIt; + Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &freeIt), + VAR_GLOBAL); + bmake_free(freeIt); } InitMaxJobs(); /* - * Be compatible if user did not specify -j and did not explicitly - * turned compatibility on + * Be compatible if the user did not specify -j and did not explicitly + * turn compatibility on. */ - if (!opts.compatMake && !forceJobs) { - opts.compatMake = TRUE; - } + if (!opts.compatMake && !forceJobs) + opts.compatMake = TRUE; if (!opts.compatMake) - Job_ServerStart(maxJobTokens, jp_0, jp_1); + Job_ServerStart(maxJobTokens, jp_0, jp_1); DEBUG5(JOB, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n", - jp_0, jp_1, opts.maxJobs, maxJobTokens, opts.compatMake ? 1 : 0); + jp_0, jp_1, opts.maxJobs, maxJobTokens, opts.compatMake ? 1 : 0); - if (!opts.printVars) - Main_ExportMAKEFLAGS(TRUE); /* initial export */ + if (opts.printVars == PVM_NONE) + Main_ExportMAKEFLAGS(TRUE); /* initial export */ InitVpath(); /* * 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 (opts.printVars) { +/* Make the targets. + * If the -v or -V options are given, print variables instead. + * Return whether any of the targets is out-of-date. */ +static Boolean +main_Run(void) +{ + if (opts.printVars != PVM_NONE) { + /* print the values of any variables requested by the user */ doPrintVars(); - outOfDate = FALSE; + return FALSE; } else { - outOfDate = runTargets(); + return runTargets(); } +} - CleanUp(); +/* Clean up after making the targets. */ +static void +main_CleanUp(void) +{ +#ifdef CLEANUP + Lst_Destroy(opts.variables, free); + Lst_Free(opts.makefiles); /* don't free, may be used in GNodes */ + Lst_Destroy(opts.create, free); +#endif - if (DEBUG(LINT) && (errors > 0 || Parse_GetFatals() > 0)) - return 2; /* Not 1 so -q can distinguish error */ + /* print the graph now it's been processed if the user requested it */ + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + + Trace_Log(MAKEEND, NULL); + + if (enterFlagObj) + printf("%s: Leaving directory `%s'\n", progname, objdir); + if (opts.enterFlag) + printf("%s: Leaving directory `%s'\n", progname, curdir); + +#ifdef USE_META + meta_finish(); +#endif + Suff_End(); + Targ_End(); + Arch_End(); + Var_End(); + Parse_End(); + Dir_End(); + Job_End(); + Trace_End(); +} + +/* Determine the exit code. */ +static int +main_Exit(Boolean outOfDate) +{ + if (opts.lint && (errors > 0 || Parse_GetFatals() > 0)) + return 2; /* Not 1 so -q can distinguish error */ return outOfDate ? 1 : 0; } +int +main(int argc, char **argv) +{ + Boolean outOfDate; + + main_Init(argc, argv); + main_ReadFiles(); + main_PrepareMaking(); + outOfDate = main_Run(); + main_CleanUp(); + return main_Exit(outOfDate); +} + /* Open and parse the given makefile, with all its side effects. * * Results: * 0 if ok. -1 if couldn't open file. */ static int ReadMakefile(const char *fname) { int fd; char *name, *path = NULL; - if (!strcmp(fname, "-")) { + if (strcmp(fname, "-") == 0) { Parse_File(NULL /*stdin*/, -1); Var_Set("MAKEFILE", "", VAR_INTERNAL); } else { /* if we've chdir'd, rebuild the path name */ - if (strcmp(curdir, objdir) && *fname != '/') { + if (strcmp(curdir, objdir) != 0 && *fname != '/') { path = str_concat3(curdir, "/", fname); fd = open(path, O_RDONLY); if (fd != -1) { fname = path; goto found; } free(path); /* If curdir failed, try objdir (ala .depend) */ path = str_concat3(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) { + if (name == NULL) { SearchPath *sysInc = Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath; name = Dir_FindFile(fname, sysInc); } - if (!name || (fd = open(name, O_RDONLY)) == -1) { + if (name == NULL || (fd = open(name, O_RDONLY)) == -1) { 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); 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. In the output, newlines are replaced with spaces. * * Results: * A string containing the output of the command, or the empty string. * *errfmt returns a format string describing the command failure, * if any, using a single %s conversion specification. * * Side Effects: * The string must be freed by the caller. */ char * Cmd_Exec(const char *cmd, const char **errfmt) { - const char *args[4]; /* Args for invoking the shell */ - int fds[2]; /* Pipe streams */ - int cpid; /* Child PID */ - int pid; /* PID from wait() */ - WAIT_T status; /* command exit status */ - Buffer buf; /* buffer to store the result */ - ssize_t bytes_read; - char *res; /* result */ - size_t res_len; - char *cp; - int savederr; /* saved errno */ + const char *args[4]; /* Args for invoking the shell */ + int fds[2]; /* Pipe streams */ + int cpid; /* Child PID */ + int pid; /* PID from wait() */ + int status; /* command exit status */ + Buffer buf; /* buffer to store the result */ + ssize_t bytes_read; + char *res; /* result */ + size_t res_len; + char *cp; + int savederr; /* saved errno */ - *errfmt = NULL; + *errfmt = NULL; - if (!shellName) - Shell_Init(); - /* - * Set up arguments for shell - */ - args[0] = shellName; - args[1] = "-c"; - args[2] = cmd; - args[3] = 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) { - *errfmt = "Couldn't create pipe for \"%s\""; - goto bad; - } - - /* - * Fork - */ - switch (cpid = vFork()) { - case 0: /* - * Close input side of pipe + * Open a pipe for fetching its output */ - (void)close(fds[0]); + if (pipe(fds) == -1) { + *errfmt = "Couldn't create pipe for \"%s\""; + goto bad; + } /* - * 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? + * Fork */ - (void)dup2(fds[1], 1); - (void)close(fds[1]); + switch (cpid = vFork()) { + case 0: + (void)close(fds[0]); /* Close input side of pipe */ - Var_ExportVars(); + /* + * 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]); - (void)execv(shellPath, UNCONST(args)); - _exit(1); - /*NOTREACHED*/ + Var_ExportVars(); - case -1: - *errfmt = "Couldn't exec \"%s\""; - goto bad; + (void)execv(shellPath, UNCONST(args)); + _exit(1); + /*NOTREACHED*/ - default: - /* - * No need for the writing half - */ - (void)close(fds[1]); + case -1: + *errfmt = "Couldn't exec \"%s\""; + goto bad; - savederr = 0; - Buf_Init(&buf, 0); + default: + (void)close(fds[1]); /* No need for the writing half */ - do { - char result[BUFSIZ]; - bytes_read = read(fds[0], result, sizeof(result)); - if (bytes_read > 0) - Buf_AddBytes(&buf, result, (size_t)bytes_read); - } - while (bytes_read > 0 || (bytes_read == -1 && errno == EINTR)); - if (bytes_read == -1) - savederr = errno; + savederr = 0; + Buf_Init(&buf); - /* - * Close the input side of the pipe. - */ - (void)close(fds[0]); + do { + char result[BUFSIZ]; + bytes_read = read(fds[0], result, sizeof result); + if (bytes_read > 0) + Buf_AddBytes(&buf, result, (size_t)bytes_read); + } while (bytes_read > 0 || + (bytes_read == -1 && errno == EINTR)); + if (bytes_read == -1) + savederr = errno; - /* - * Wait for the process to exit. - */ - while(((pid = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0)) { - JobReapChild(pid, status, FALSE); - continue; - } - res_len = Buf_Len(&buf); - res = Buf_Destroy(&buf, FALSE); + (void)close( + fds[0]); /* Close the input side of the pipe. */ - if (savederr != 0) - *errfmt = "Couldn't read shell's output for \"%s\""; + /* Wait for the process to exit. */ + while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0) + JobReapChild(pid, status, FALSE); - if (WIFSIGNALED(status)) - *errfmt = "\"%s\" exited on a signal"; - else if (WEXITSTATUS(status) != 0) - *errfmt = "\"%s\" returned non-zero status"; + res_len = Buf_Len(&buf); + res = Buf_Destroy(&buf, FALSE); - /* Convert newlines to spaces. A final newline is just stripped */ - if (res_len > 0 && res[res_len - 1] == '\n') - res[res_len - 1] = '\0'; - for (cp = res; *cp != '\0'; cp++) - if (*cp == '\n') - *cp = ' '; - break; - } - return res; + if (savederr != 0) + *errfmt = "Couldn't read shell's output for \"%s\""; + + if (WIFSIGNALED(status)) + *errfmt = "\"%s\" exited on a signal"; + else if (WEXITSTATUS(status) != 0) + *errfmt = "\"%s\" returned non-zero status"; + + /* Convert newlines to spaces. A final newline is just stripped */ + if (res_len > 0 && res[res_len - 1] == '\n') + res[res_len - 1] = '\0'; + for (cp = res; *cp != '\0'; cp++) + if (*cp == '\n') + *cp = ' '; + break; + } + return res; bad: - return bmake_strdup(""); + return bmake_strdup(""); } /* Print a printf-style error message. * - * This error message has no consequences, in particular it does not affect - * the exit status. */ + * In default mode, this error message has no consequences, in particular it + * does not affect the exit status. Only in lint mode (-dL) it does. */ void Error(const char *fmt, ...) { va_list ap; FILE *err_file; err_file = opts.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; } errors++; } -/* Produce a Fatal error message, then exit immediately. +/* Wait for any running jobs to finish, then produce an error message, + * finally exit immediately. * - * If jobs are running, wait for them to finish. */ + * Exiting immediately differs from Parse_Error, which exits only after the + * current top-level makefile has been parsed completely. */ void Fatal(const char *fmt, ...) { va_list ap; - va_start(ap, fmt); if (jobsRunning) Job_Wait(); (void)fflush(stdout); + va_start(ap, fmt); (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); + Trace_Log(MAKEERROR, NULL); exit(2); /* Not 1 so -q can distinguish error */ } /* Major exception once jobs are being created. * Kills all jobs, prints a message and exits. */ 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(); } /* Exit without giving a message. */ void DieHorribly(void) { if (jobsRunning) Job_AbortAll(); if (DEBUG(GRAPH2)) Targ_PrintGraph(2); - Trace_Log(MAKEERROR, 0); + Trace_Log(MAKEERROR, NULL); exit(2); /* Not 1, so -q can distinguish error */ } /* Called when aborting due to errors in child shell to signal abnormal exit. * The program exits. * Errors is the number of errors encountered in Make_Make. */ void Finish(int errs) { - if (dieQuietly(NULL, -1)) + if (shouldDieQuietly(NULL, -1)) exit(2); Fatal("%d error%s", errs, errs == 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); } static void write_all(int fd, const void *data, size_t n) { const char *mem = data; while (n > 0) { ssize_t written = write(fd, mem, n); if (written == -1 && errno == EAGAIN) continue; if (written == -1) break; mem += written; n -= (size_t)written; } } /* * execDie -- * Print why exec failed, avoiding stdio. */ void MAKE_ATTR_DEAD execDie(const char *af, const char *av) { Buffer buf; - Buf_Init(&buf, 0); + Buf_Init(&buf); Buf_AddStr(&buf, progname); Buf_AddStr(&buf, ": "); Buf_AddStr(&buf, af); Buf_AddStr(&buf, "("); Buf_AddStr(&buf, av); Buf_AddStr(&buf, ") failed ("); Buf_AddStr(&buf, strerror(errno)); Buf_AddStr(&buf, ")\n"); write_all(STDERR_FILENO, Buf_GetAll(&buf, NULL), Buf_Len(&buf)); Buf_Destroy(&buf, TRUE); _exit(1); } -/* - * 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] [-v variable] [variable=value] [target ...]\n", - progname); - exit(2); -} - -/* - * realpath(3) can get expensive, cache results... - */ -static GNode *cached_realpaths = NULL; - -static GNode * -get_cached_realpaths(void) -{ - - if (!cached_realpaths) { - cached_realpaths = Targ_NewGN("Realpath"); -#ifndef DEBUG_REALPATH_CACHE - cached_realpaths->flags = INTERNAL; -#endif - } - - return cached_realpaths; -} - /* purge any relative paths */ static void -purge_cached_realpaths(void) +purge_relative_cached_realpaths(void) { - GNode *cache = get_cached_realpaths(); - HashEntry *he, *nhe; - HashIter hi; + HashEntry *he, *nhe; + HashIter hi; - HashIter_Init(&hi, &cache->context); - he = HashIter_Next(&hi); - while (he != NULL) { - nhe = HashIter_Next(&hi); - if (he->key[0] != '/') { - if (DEBUG(DIR)) - fprintf(stderr, "cached_realpath: purging %s\n", he->key); - HashTable_DeleteEntry(&cache->context, he); + HashIter_Init(&hi, &cached_realpaths); + he = HashIter_Next(&hi); + while (he != NULL) { + nhe = HashIter_Next(&hi); + if (he->key[0] != '/') { + DEBUG1(DIR, "cached_realpath: purging %s\n", he->key); + HashTable_DeleteEntry(&cached_realpaths, he); + /* XXX: What about the allocated he->value? Either + * free them or document why they cannot be freed. */ + } + he = nhe; } - he = nhe; - } } char * cached_realpath(const char *pathname, char *resolved) { - GNode *cache; - const char *rp; - void *freeIt; + const char *rp; - if (!pathname || !pathname[0]) - return NULL; + if (pathname == NULL || pathname[0] == '\0') + return NULL; - cache = get_cached_realpaths(); + rp = HashTable_FindValue(&cached_realpaths, pathname); + if (rp != NULL) { + /* a hit */ + strncpy(resolved, rp, MAXPATHLEN); + resolved[MAXPATHLEN - 1] = '\0'; + return resolved; + } - if ((rp = Var_Value(pathname, cache, &freeIt)) != NULL) { - /* a hit */ - strlcpy(resolved, rp, MAXPATHLEN); - } else if ((rp = realpath(pathname, resolved)) != NULL) { - Var_Set(pathname, rp, cache); - } /* else should we negative-cache? */ + rp = realpath(pathname, resolved); + if (rp != NULL) { + HashTable_Set(&cached_realpaths, pathname, bmake_strdup(rp)); + DEBUG2(DIR, "cached_realpath: %s -> %s\n", pathname, rp); + return resolved; + } - bmake_free(freeIt); - return rp ? resolved : NULL; + /* should we negative-cache? */ + return NULL; } /* * Return true if we should die without noise. - * For example our failing child was a sub-make - * or failure happend elsewhere. + * For example our failing child was a sub-make or failure happened elsewhere. */ -int -dieQuietly(GNode *gn, int bf) +Boolean +shouldDieQuietly(GNode *gn, int bf) { - static int quietly = -1; + static int quietly = -1; - if (quietly < 0) { - if (DEBUG(JOB) || !getBoolean(".MAKE.DIE_QUIETLY", TRUE)) - quietly = 0; - else if (bf >= 0) - quietly = bf; - else - quietly = gn != NULL ? ((gn->type & (OP_MAKE)) != 0) : 0; - } - return quietly; + if (quietly < 0) { + if (DEBUG(JOB) || !GetBooleanVar(".MAKE.DIE_QUIETLY", TRUE)) + quietly = 0; + else if (bf >= 0) + quietly = bf; + else + quietly = gn != NULL && (gn->type & OP_MAKE); + } + return quietly; } static void SetErrorVars(GNode *gn) { - StringListNode *ln; + StringListNode *ln; - /* - * We can print this even if there is no .ERROR target. - */ - Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL); - Var_Delete(".ERROR_CMD", VAR_GLOBAL); + /* + * We can print this even if there is no .ERROR target. + */ + Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL); + Var_Delete(".ERROR_CMD", VAR_GLOBAL); - for (ln = gn->commands->first; ln != NULL; ln = ln->next) { - const char *cmd = ln->datum; + for (ln = gn->commands->first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; - if (cmd == NULL) - break; - Var_Append(".ERROR_CMD", cmd, VAR_GLOBAL); - } + if (cmd == NULL) + break; + Var_Append(".ERROR_CMD", cmd, VAR_GLOBAL); + } } +/* Print some helpful information in case of an error. + * The caller should exit soon after calling this function. */ void -PrintOnError(GNode *gn, const char *s) +PrintOnError(GNode *gn, const char *msg) { - static GNode *en = NULL; - const char *expr; - char *cp; + static GNode *errorNode = NULL; - if (DEBUG(HASH)) { - Targ_Stats(); - Var_Stats(); - } + if (DEBUG(HASH)) { + Targ_Stats(); + Var_Stats(); + } - /* we generally want to keep quiet if a sub-make died */ - if (dieQuietly(gn, -1)) - return; + /* we generally want to keep quiet if a sub-make died */ + if (shouldDieQuietly(gn, -1)) + return; - if (s) - printf("%s", s); + if (msg != NULL) + printf("%s", msg); + printf("\n%s: stopped in %s\n", progname, curdir); - printf("\n%s: stopped in %s\n", progname, curdir); + if (errorNode != NULL) + return; /* we've been here! */ - if (en) - return; /* we've been here! */ - if (gn) - SetErrorVars(gn); - expr = "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}"; - (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &cp); - /* TODO: handle errors */ - printf("%s", cp); - free(cp); - fflush(stdout); + if (gn != NULL) + SetErrorVars(gn); - /* - * Finally, see if there is a .ERROR target, and run it if so. - */ - en = Targ_FindNode(".ERROR"); - if (en) { - en->type |= OP_SPECIAL; - Compat_Make(en, en); - } + { + char *errorVarsValues; + (void)Var_Subst("${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", + VAR_GLOBAL, VARE_WANTRES, &errorVarsValues); + /* TODO: handle errors */ + printf("%s", errorVarsValues); + free(errorVarsValues); + } + + fflush(stdout); + + /* + * Finally, see if there is a .ERROR target, and run it if so. + */ + errorNode = Targ_FindNode(".ERROR"); + if (errorNode != NULL) { + errorNode->type |= OP_SPECIAL; + Compat_Make(errorNode, errorNode); + } } void Main_ExportMAKEFLAGS(Boolean first) { - static Boolean once = TRUE; - const char *expr; - char *s; + static Boolean once = TRUE; + const char *expr; + char *s; - if (once != first) - return; - once = FALSE; + if (once != first) + return; + once = FALSE; - expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}"; - (void)Var_Subst(expr, VAR_CMDLINE, VARE_WANTRES, &s); - /* TODO: handle errors */ - if (s[0] != '\0') { + expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}"; + (void)Var_Subst(expr, VAR_CMDLINE, VARE_WANTRES, &s); + /* TODO: handle errors */ + if (s[0] != '\0') { #ifdef POSIX - setenv("MAKEFLAGS", s, 1); + setenv("MAKEFLAGS", s, 1); #else - setenv("MAKE", s, 1); + setenv("MAKE", s, 1); #endif - } + } } char * getTmpdir(void) { - static char *tmpdir = NULL; - - if (!tmpdir) { + static char *tmpdir = NULL; struct stat st; - /* - * Honor $TMPDIR but only if it is valid. - * Ensure it ends with /. - */ - (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL, - VARE_WANTRES, &tmpdir); + if (tmpdir != NULL) + return tmpdir; + + /* Honor $TMPDIR but only if it is valid. Ensure it ends with '/'. */ + (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/", + VAR_GLOBAL, VARE_WANTRES, &tmpdir); /* TODO: handle errors */ + if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { - free(tmpdir); - tmpdir = bmake_strdup(_PATH_TMP); + free(tmpdir); + tmpdir = bmake_strdup(_PATH_TMP); } - } - return tmpdir; + return tmpdir; } /* * Create and open a temp file using "pattern". * If out_fname is provided, set it to a copy of the filename created. * Otherwise unlink the file once open. */ int mkTempFile(const char *pattern, char **out_fname) { - static char *tmpdir = NULL; - char tfile[MAXPATHLEN]; - int fd; + static char *tmpdir = NULL; + char tfile[MAXPATHLEN]; + int fd; - if (pattern != NULL) - pattern = TMPPAT; - if (tmpdir == NULL) - 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 (out_fname) { - *out_fname = bmake_strdup(tfile); - } else { - unlink(tfile); /* we just want the descriptor */ - } - return fd; + if (pattern == NULL) + pattern = TMPPAT; + if (tmpdir == NULL) + 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 (out_fname) { + *out_fname = bmake_strdup(tfile); + } else { + unlink( + tfile); /* we just want the descriptor */ + } + return fd; } /* - * Convert a string representation of a boolean. - * Anything that looks like "No", "False", "Off", "0" etc, - * is FALSE, otherwise TRUE. + * Convert a string representation of a boolean into a boolean value. + * Anything that looks like "No", "False", "Off", "0" etc. is FALSE, + * the empty string is the fallback, everything else is TRUE. */ Boolean -s2Boolean(const char *s, Boolean bf) +ParseBoolean(const char *s, Boolean fallback) { - switch(s[0]) { - case '\0': /* not set - the default wins */ - break; - case '0': - case 'F': - case 'f': - case 'N': - case 'n': - return FALSE; - case 'O': - case 'o': - return s[1] != 'F' && s[1] != 'f'; - default: + char ch = ch_tolower(s[0]); + if (ch == '\0') + return fallback; + if (ch == '0' || ch == 'f' || ch == 'n') + return FALSE; + if (ch == 'o') + return ch_tolower(s[1]) != 'f'; return TRUE; - } - return bf; -} - -/* - * Return a Boolean based on a variable. - * - * If the knob is not set, return the fallback. - * If set, anything that looks or smells like "No", "False", "Off", "0", etc. - * is FALSE, otherwise TRUE. - */ -Boolean -getBoolean(const char *varname, Boolean fallback) -{ - char *expr = str_concat3("${", varname, ":U}"); - char *value; - Boolean res; - - (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &value); - /* TODO: handle errors */ - res = s2Boolean(value, fallback); - free(value); - free(expr); - return res; } Index: head/contrib/bmake/make-bootstrap.sh.in =================================================================== --- head/contrib/bmake/make-bootstrap.sh.in (revision 367862) +++ head/contrib/bmake/make-bootstrap.sh.in (revision 367863) @@ -1,94 +1,95 @@ #!/bin/sh set -e srcdir=@srcdir@ DEFAULT_SYS_PATH="@default_sys_path@" case "@use_meta@" in yes) XDEFS="-DUSE_META ${XDEFS}";; esac CC="@CC@" CFLAGS="@CFLAGS@ -I. -I${srcdir} @DEFS@ @CPPFLAGS@ -DMAKE_NATIVE ${XDEFS} -DBMAKE_PATH_MAX=@bmake_path_max@" MAKE_VERSION=@_MAKE_VERSION@ MDEFS="-DMAKE_VERSION=\"$MAKE_VERSION\" \ --D@force_machine@MACHINE=\"@machine@\" -DMACHINE_ARCH=\"@machine_arch@\" \ +-D@force_machine@MACHINE=\"@machine@\" \ +-D@force_machine_arch@MACHINE_ARCH=\"@machine_arch@\" \ -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\"" LDFLAGS="@LDFLAGS@" LIBS="@LIBS@" toUpper() { ${TR:-tr} abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ } do_compile2() { obj="$1"; shift src="$1"; shift echo ${CC} -c ${CFLAGS} "$@" -o "$obj" "$src" ${CC} -c ${CFLAGS} "$@" -o "$obj" "$src" } do_compile() { obj="$1"; shift case "$1" in *.c) src=$1; shift;; *) src=`basename "$obj" .o`.c;; esac for d in "$srcdir" "$srcdir/lst.lib" do test -s "$d/$src" || continue do_compile2 "$obj" "$d/$src" "$@" || exit 1 return done echo "Unknown object file '$obj'" >&2 exit 1 } do_link() { output="$1"; shift echo ${CC} ${LDSTATIC} ${LDFLAGS} -o "$output" "$@" ${LIBS} ${CC} ${LDSTATIC} ${LDFLAGS} -o "$output" "$@" ${LIBS} } BASE_OBJECTS="arch.o buf.o compat.o cond.o dir.o enum.o for.o getopt hash.o \ lst.o make.o make_malloc.o metachar.o parse.o sigcompat.o str.o \ suff.o targ.o trace.o var.o util.o" LIB_OBJECTS="@LIBOBJS@" do_compile main.o ${MDEFS} for o in ${BASE_OBJECTS} ${LIB_OBJECTS} do do_compile "$o" done case "@use_meta@" in yes) case "@use_filemon@" in no) MDEFS=;; *) MDEFS="-DUSE_FILEMON -DUSE_FILEMON_`echo @use_filemon@ | toUpper`" case "@use_filemon@,@filemon_h@" in dev,*/filemon.h) FDEFS="-DHAVE_FILEMON_H -I`dirname @filemon_h@`";; *) FDEFS=;; esac do_compile filemon_@use_filemon@.o filemon/filemon_@use_filemon@.c ${FDEFS} BASE_OBJECTS="filemon_@use_filemon@.o $BASE_OBJECTS" ;; esac do_compile meta.o ${MDEFS} BASE_OBJECTS="meta.o ${BASE_OBJECTS}" ;; esac do_compile job.o ${MDEFS} do_link bmake main.o job.o ${BASE_OBJECTS} ${LST_OBJECTS} ${LIB_OBJECTS} Index: head/contrib/bmake/make.1 =================================================================== --- head/contrib/bmake/make.1 (revision 367862) +++ head/contrib/bmake/make.1 (revision 367863) @@ -1,2462 +1,2475 @@ -.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $ +.\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig 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 November 1, 2020 +.Dd November 14, 2020 .Dt MAKE 1 .Os .Sh NAME .Nm make .Nd maintain program dependencies .Sh SYNOPSIS .Nm -.Op Fl BeikNnqrstWwX +.Op Fl BeikNnqrSstWwX .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 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 h Print debugging information about hash table operations. .It Ar j Print debugging information about running multiple shells. .It Ar L Turn on lint checks. This will throw errors for variable assignments that do not parse correctly, at the time of assignment so the file and line number are available. .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, overriding the default behavior set via .Va .MAKE.EXPAND_VARIABLES . .It Ar v Print debugging information about variable assignment. .It Ar x Run shell commands with .Fl x so the actual commands are printed as they are executed. .El .It Fl e Specify that environment variables override macro assignments within makefiles. .It Fl f Ar makefile Specify a makefile to read instead of the default .Ql Pa makefile . If .Ar makefile is .Ql Fl , standard input is read. Multiple makefiles may be specified, and are read in the order specified. .It Fl I Ar directory Specify a directory in which to search for makefiles and included makefiles. The system makefile directory (or directories, see the .Fl m option) is automatically included as part of this list. .It Fl i Ignore non-zero exit of shell commands in the makefile. Equivalent to specifying .Ql Fl before each command line in the makefile. .It Fl J Ar private This option should .Em not be specified by the user. .Pp When the .Ar j option is in use in a recursive build, this option is passed by a make to child makes to allow all the make processes in the build to cooperate to avoid overloading the system. .It Fl j Ar max_jobs Specify the maximum number of jobs that .Nm may have running at any one time. The value is saved in .Va .MAKE.JOBS . Turns compatibility mode off, unless the .Ar B flag is also specified. When compatibility mode is off, all commands associated with a target are executed in a single shell invocation as opposed to the traditional one shell invocation per line. This can break traditional scripts which change directories on each command invocation and then expect to start with a fresh environment on the next line. It is more efficient to correct the scripts rather than turn backwards compatibility on. .It Fl k Continue processing after errors are encountered, but only on those targets that do not depend on the target whose creation caused the error. .It Fl m Ar directory Specify a directory in which to search for sys.mk and makefiles included via the .Li \&< Ns Ar file Ns Li \&> 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 .Li \*q Ns Ar file Ns Li \*q 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 file system. 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) or the command is prefixed with .Ql Ic + . .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 +Stop processing if an error is encountered. +This is the default behavior and the opposite of +.Fl k . .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 the value of .Ar variable . 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. The value printed is extracted from the global context after all makefiles have been read. By default, the raw variable contents (which may include additional unexpanded variable references) are shown. If .Ar variable contains a .Ql \&$ then the value will be recursively expanded to its complete resultant text before printing. The expanded value will also be printed if .Va .MAKE.EXPAND_VARIABLES is set to true and the .Fl dV option has not been used to override it. Note that loop-local and target-local variables, as well as values taken temporarily by global variables during makefile processing, are not accessible via this option. The .Fl dv debug mode can be used to see these at the cost of generating substantial extraneous output. .It Fl v Ar variable Like .Fl V but the variable is always expanded to its complete value. .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 customarily created from them. A target is considered out-of-date if it does not exist, or if its modification time is less than that of any of its sources. An out-of-date target will be re-created, but not until all sources have been examined and themselves re-created as needed. Three operators may be used: .Bl -tag -width flag .It Ic \&: Many dependency lines may name this target but only one may have attached shell commands. All sources named in all dependency lines are considered together, and if needed the attached shell commands are run to create or re-create the target. If .Nm is interrupted, the target is removed. .It Ic \&! The same, but the target is always re-created whether or not it is out of date. .It Ic \&:: Any dependency line may have attached shell commands, but each one is handled independently: its sources are considered and the attached shell commands are run if the target is out of date with respect to (only) those sources. Thus, different groups of the attached shell commands may be run depending on the circumstances. Furthermore, unlike .Ic \&:, for dependency lines with no sources, the attached shell commands are always run. Also unlike .Ic \&:, the target will not be removed if .Nm is interrupted. .El All dependency lines mentioning a particular target must use the same operator. .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 one or more lines of shell commands, normally used to create the target. Each of the lines in this script .Em must 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 .Ql Ic \&:: operator is used, however, all rules may include scripts and the scripts are executed in the order found. .Pp 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. In compatibility (non-jobs) mode, each command is run in a separate process. If the command contains any shell meta characters .Pq Ql #=|^(){};&<>*?[]:$`\e\en it will be passed to the shell; otherwise .Nm will attempt direct execution. 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 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 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} $@) @echo Back in `pwd` ensure-one-shell-regardless-of-mode: @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, parentheses, 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" -offset indent .It Va .ALLSRC The list of all sources for this target; also known as .Ql Va \&> . .It Va .ARCHIVE 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 \&< . It is not defined in explicit rules. .It Va .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 @ . For compatibility with other makes this is an alias for .Ic .ARCHIVE in archive member rules. .El .Pp The shorter forms .Ql ( Va > , .Ql Va \&! , .Ql Va < , .Ql Va % , .Ql Va \&? , .Ql Va * , and .Ql Va @ ) are permitted for backward 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 :T modifiers. These forms are accepted for compatibility with .At V 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 . .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. If true, variable values printed with .Fl V are fully expanded; if false, the raw variable contents (which may include additional unexpanded variable references) are shown. .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 missing-filemon=bf. .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 missing-meta= Ar bf If .Va bf is True, then a missing .meta file makes the target out-of-date. .It Pa missing-filemon= Ar bf If .Va bf is True, then missing filemon data makes the target out-of-date. .It Pa nofilemon Do not use .Xr filemon 4 . .It Pa env For debugging, it can be useful to include 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.IGNORE_PATTERNS Provides a list of patterns to match against pathnames. Ignore any that match. .It Va .MAKE.META.IGNORE_FILTER Provides a list of variable modifiers to apply to each pathname. Ignore if the expansion is an empty string. .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 behavior 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.SAVE_DOLLARS value should be a boolean that controls whether .Ql $$ are preserved when doing .Ql := assignments. The default is false, for backwards compatibility. Set to true for compatability with other makes. If set to false, .Ql $$ becomes .Ql $ per normal evaluation rules. .It Va MAKE_PRINT_VAR_ON_ERROR When .Nm stops due to an error, it sets .Ql Va .ERROR_TARGET to the name of the target that failed, .Ql Va .ERROR_CMD to the commands of the failed target, and in "meta" mode, it also sets .Ql Va .ERROR_CWD to the .Xr getcwd 3 , and .Ql Va .ERROR_META_FILE to the path of the meta file (if any) describing the failed target. It then 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 via the special target .Ql Ic .OBJDIR . In all cases, .Nm will .Xr chdir 2 to the specified directory if it exists, and set .Ql Va .OBJDIR and .Ql Ev PWD to that directory before executing any targets. +.Pp +Except in the case of an explicit +.Ql Ic .OBJDIR +target, +.Nm +will check that the specified directory is writable and ignore it if not. +This check can be skipped by setting the environment variable +.Ql Ev MAKE_OBJDIR_CHECK_WRITABLE +to "no". . .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 behavior 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 .SHELL The pathname of the shell used to run target scripts. It is read-only. .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 Selects 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 normalize 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 Orders every word in variable alphabetically. .It Cm \&:Or Orders every word in variable in reverse alphabetical order. .It Cm \&:Ox Shuffles the 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 behavior. 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 to the shell. .It Cm \&:q Quotes every shell meta-character in the variable, and also doubles .Sq $ characters so that it can be passed safely through recursive invocations of .Nm . This is equivalent to: .Sq \&:S/\e\&$/&&/g:Q . .It Cm \&:R Replaces each word in the variable with everything but its suffix. .It Cm \&:range[=count] The value is an integer sequence representing the words of the original value, or the supplied .Va count . .It Cm \&:gmtime[=utc] The value is a format string for .Xr strftime 3 , using .Xr gmtime 3 . If a .Va utc value is not provided or is 0, the current time is used. .It Cm \&:hash Computes a 32-bit hash of the value and encode it as hex digits. .It Cm \&:localtime[=utc] The value is a format string for .Xr strftime 3 , using .Xr localtime 3 . If a .Va utc value is not provided or is 0, the current time is used. .It Cm \&:tA Attempts 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 Modifies the first occurrence of .Ar old_string in each word of the variable's value, replacing it with .Ar new_string . If a .Ql g is appended to the last delimiter of the pattern, all occurrences in each word are replaced. If a .Ql 1 is appended to the last delimiter of the pattern, only the first occurrence is affected. If a .Ql W is appended to the last delimiter 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 & 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). .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 path component. .It Cm \&:u Removes 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 if 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 . If only .Ar old_string contains the pattern matching character .Ar % , and .Ar old_string matches, then the result is the .Ar new_string . If only the .Ar new_string contains the pattern matching character .Ar % , then it is not treated specially and it is printed as a literal .Ar % on match. If there is more than one pattern matching character .Ar ( % ) in either the .Ar new_string or .Ar old_string , only the first instance is treated specially (as the pattern character); all subsequent instances are treated as regular characters. .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. Assigns .Ar temp to each word in the variable and evaluates .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 \&:_[=var] Saves the current variable value in .Ql $_ or the named .Va var for later reference. Example usage: .Bd -literal -offset indent M_cmpv.units = 1 1000 1000000 M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \&\\ \\* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh .Dv .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}} .Ed Here .Ql $_ is used to save the result of the .Ql :S modifier which is later referenced using the index values from .Ql :range . .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 behavior, 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. If the list is already ordered, then this effectively reverses the list, but it is more efficient to use .Ql Cm \&:Or instead of .Ql Cm \&:O:[-1..1] . .\" :[*] .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 \&< Ns Ar file Ns Cm \&> or .Cm \&.include \&\*q Ns Ar file Ns Cm \&\*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. .Pp If the include statement is written as .Cm .-include or as .Cm .sinclude then errors locating and/or opening include files are ignored. .Pp If the include statement is written as .Cm .dinclude not only are errors locating and/or opening include files ignored, but stale dependencies within the included file will be ignored just like .Va .MAKE.DEPENDFILE . .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 immediately. .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 .export-literal Ar variable ... The same as .Ql .export-env , except that variables in the value are not expanded. .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 \&&& 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 \&&& . .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 numerical 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 a numerical 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-lines .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-lines 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 .DELETE_ON_ERROR If this target is present in the makefile, it globally causes make to delete targets whose commands fail. (By default, only targets whose commands are interrupted during execution are deleted. This is the historical behavior.) This setting can be used to help prevent half-finished or malformed targets from being left around and corrupting future rebuilds. .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 .OBJDIR The source is a new value for .Ql Va .OBJDIR . If it exists, .Nm will .Xr chdir 2 to it and update the value of .Ql Va .OBJDIR . .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 built-in shell specs; .Ar sh , .Ar ksh , and .Ar csh . .It Ar path Specifies the path to the shell. .It Ar hasErrCtl Indicates whether the shell supports exit on error. .It Ar check The command to turn on error checking. .It Ar ignore The command to disable error checking. .It Ar echo The command to turn on echoing of commands executed. .It Ar quiet The command to turn off echoing of commands executed. .It Ar filter The output to filter after issuing the .Ar quiet command. It is typically identical to .Ar quiet . .It Ar errFlag The flag to pass the shell to enable error checking. .It Ar echoFlag The flag to pass the shell to enable command echoing. .It Ar newline The string literal to pass the shell that results in a single newline character when used outside of any quoting characters. .El Example: .Bd -literal \&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \e check="set \-e" ignore="set +e" \e echo="set \-v" quiet="set +v" filter="set +v" \e echoFlag=v errFlag=e newline="'\en'" .Ed .It Ic .SILENT Apply the .Ic .SILENT attribute to any specified sources. If no sources are specified, the .Ic .SILENT attribute is applied to every command in the file. .It Ic .STALE This target gets run when a dependency file contains stale entries, having .Va .ALLSRC set to the name of that dependency file. .It Ic .SUFFIXES Each source specifies a suffix to .Nm . If no sources are specified, any previously specified suffixes are deleted. It allows the creation of suffix-transformation rules. .Pp Example: .Bd -literal \&.SUFFIXES: .o \&.c.o: cc \-o ${.TARGET} \-c ${.IMPSRC} .Ed .El .Sh ENVIRONMENT .Nm uses the following environment variables, if they exist: .Ev MACHINE , .Ev MACHINE_ARCH , .Ev MAKE , .Ev MAKEFLAGS , .Ev MAKEOBJDIR , .Ev MAKEOBJDIRPREFIX , .Ev MAKESYSPATH , .Ev PWD , and .Ev TMPDIR . .Pp .Ev MAKEOBJDIRPREFIX and .Ev MAKEOBJDIR may only be set in the environment or on the command line to .Nm and not as makefile variables; see the description of .Ql Va .OBJDIR for more details. .Sh FILES .Bl -tag -width /usr/share/mk -compact .It .depend list of dependencies .It Makefile list of dependencies .It makefile list of dependencies .It sys.mk system makefile .It /usr/share/mk system makefile directory .El .Sh COMPATIBILITY The basic make syntax is compatible between different versions of make; however the special variables, variable modifiers and conditionals are not. .Ss Older versions An incomplete list of changes in older versions of .Nm : .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 , .Xr style.Makefile 5 .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 on the data. For instance, finding the end of a variable's use should involve scanning each of 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 367862) +++ head/contrib/bmake/make.c (revision 367863) @@ -1,1361 +1,1350 @@ -/* $NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $ */ +/* $NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig 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. */ -/*- - * make.c -- - * The functions which perform the examination of targets and - * their suitability for creation +/* 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_Run Initialize things for the module. Returns TRUE if + * work was (or would have been) done. * - * Make_Update Update all parents of a given child. Performs - * various bookkeeping chores like the updating + * Make_Update After a target is made, update all its parents. + * Perform various bookkeeping chores like the updating * of the youngestChild field of the parent, filling - * of the IMPSRC context variable, etc. It will - * place the parent on the toBeMade queue if it - * should be. + * of the IMPSRC context variable, etc. Place the parent + * on the toBeMade queue if it should be. * - * Make_TimeStamp Function to set the parent's youngestChild field - * based on a child's modification time. + * GNode_UpdateYoungestChild + * Update the node's youngestChild field based on the + * 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. + * GNode_IsOODate 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 "dir.h" #include "job.h" /* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $"); +MAKE_RCSID("$NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig Exp $"); /* Sequence # to detect recursion. */ -static unsigned int checked = 1; +static unsigned int checked_seqno = 1; /* 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 GNodeList *toBeMade; -static int MakeCheckOrder(void *, void *); static int MakeBuildParent(void *, void *); void debug_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(opts.debug_file, fmt, args); va_end(args); } MAKE_ATTR_DEAD static void make_abort(GNode *gn, int line) { debug_printf("make_abort from line %d\n", line); Targ_PrintNode(gn, 2); Targ_PrintNodes(toBeMade, 2); Targ_PrintGraph(3); abort(); } ENUM_VALUE_RTTI_8(GNodeMade, UNMADE, DEFERRED, REQUESTED, BEINGMADE, MADE, UPTODATE, ERROR, ABORTED); ENUM_FLAGS_RTTI_31(GNodeType, OP_DEPENDS, OP_FORCE, OP_DOUBLEDEP, /* OP_OPMASK is omitted since it combines other flags */ OP_OPTIONAL, OP_USE, OP_EXEC, OP_IGNORE, OP_PRECIOUS, OP_SILENT, OP_MAKE, OP_JOIN, OP_MADE, OP_SPECIAL, OP_USEBEFORE, OP_INVISIBLE, OP_NOTMAIN, OP_PHONY, OP_NOPATH, OP_WAIT, OP_NOMETA, OP_META, OP_NOMETA_CMP, OP_SUBMAKE, OP_TRANSFORM, OP_MEMBER, OP_LIB, OP_ARCHV, OP_HAS_COMMANDS, OP_SAVE_CMDS, OP_DEPS_FOUND, OP_MARK); ENUM_FLAGS_RTTI_10(GNodeFlags, REMAKE, CHILDMADE, FORCE, DONE_WAIT, DONE_ORDER, FROM_DEPEND, DONE_ALLSRC, CYCLE, DONECYCLE, INTERNAL); void GNode_FprintDetails(FILE *f, const char *prefix, const GNode *gn, const char *suffix) { char type_buf[GNodeType_ToStringSize]; char flags_buf[GNodeFlags_ToStringSize]; fprintf(f, "%smade %s, type %s, flags %s%s", prefix, Enum_ValueToString(gn->made, GNodeMade_ToStringSpecs), Enum_FlagsToString(type_buf, sizeof type_buf, gn->type, GNodeType_ToStringSpecs), Enum_FlagsToString(flags_buf, sizeof flags_buf, gn->flags, GNodeFlags_ToStringSpecs), suffix); } Boolean GNode_ShouldExecute(GNode *gn) { return !((gn->type & OP_MAKE) ? opts.noRecursiveExecute : opts.noExecute); } /* Update the youngest child of the node, according to the given child. */ void -Make_TimeStamp(GNode *pgn, GNode *cgn) +GNode_UpdateYoungestChild(GNode *gn, GNode *cgn) { - if (pgn->youngestChild == NULL || cgn->mtime > pgn->youngestChild->mtime) { - pgn->youngestChild = cgn; + if (gn->youngestChild == NULL || cgn->mtime > gn->youngestChild->mtime) + gn->youngestChild = cgn; +} + +static Boolean +IsOODateRegular(GNode *gn) +{ + /* These rules are inherited from the original Make. */ + + if (gn->youngestChild != NULL) { + if (gn->mtime < gn->youngestChild->mtime) { + DEBUG1(MAKE, "modified before source \"%s\"...", + GNode_Path(gn->youngestChild)); + return TRUE; + } + return FALSE; } + + if (gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) { + DEBUG0(MAKE, "non-existent and no sources..."); + return TRUE; + } + + if (gn->type & OP_DOUBLEDEP) { + DEBUG0(MAKE, ":: operator and no sources..."); + return TRUE; + } + + return FALSE; } /* See if the 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. * * The mtime field of the node and the youngestChild field of its parents * may be changed. */ Boolean -Make_OODate(GNode *gn) +GNode_IsOODate(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 (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { + Dir_UpdateMTime(gn, TRUE); if (DEBUG(MAKE)) { - if (gn->mtime != 0) { + if (gn->mtime != 0) debug_printf("modified %s...", Targ_FmtTime(gn->mtime)); - } else { + else debug_printf("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 is not GNode_IsTarget) * 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*. */ DEBUG0(MAKE, ".USE node..."); oodate = FALSE; - } else if ((gn->type & OP_LIB) && - ((gn->mtime==0) || Arch_IsLib(gn))) { + } else if ((gn->type & OP_LIB) && (gn->mtime == 0 || Arch_IsLib(gn))) { DEBUG0(MAKE, "library..."); /* * always out of date if no children and :: target * or non-existent. */ oodate = (gn->mtime == 0 || Arch_LibOODate(gn) || (gn->youngestChild == 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. */ DEBUG0(MAKE, ".JOIN node..."); DEBUG1(MAKE, "source %smade...", gn->flags & CHILDMADE ? "" : "not "); - oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE; + oodate = (gn->flags & CHILDMADE) != 0; } 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) { debug_printf("! operator..."); } else if (gn->type & OP_PHONY) { debug_printf(".PHONY node..."); } else { debug_printf(".EXEC node..."); } } oodate = TRUE; - } else if ((gn->youngestChild != NULL && - gn->mtime < gn->youngestChild->mtime) || - (gn->youngestChild == 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 (youngestChild == 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->youngestChild != NULL && - gn->mtime < gn->youngestChild->mtime) { - debug_printf("modified before source %s...", - GNode_Path(gn->youngestChild)); - } else if (gn->mtime == 0) { - debug_printf("non-existent and no sources..."); - } else { - debug_printf(":: operator and no sources..."); - } - } + } else if (IsOODateRegular(gn)) { 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) debug_printf("non existing child..."); } - oodate = (gn->flags & FORCE) ? TRUE : FALSE; + oodate = (gn->flags & FORCE) != 0; } #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 are GNode_IsTarget, * have their mtime stay below their children's mtime to keep parents from * thinking they're out-of-date. */ if (!oodate) { GNodeListNode *ln; for (ln = gn->parents->first; ln != NULL; ln = ln->next) - Make_TimeStamp(ln->datum, gn); + GNode_UpdateYoungestChild(ln->datum, gn); } return oodate; } -/* Add the node to the list if it needs to be examined. */ -static int -MakeAddChild(void *gnp, void *lp) +static void +PretendAllChildrenAreMade(GNode *pgn) { - GNode *gn = gnp; - GNodeList *l = lp; + GNodeListNode *ln; - if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) { - DEBUG2(MAKE, "MakeAddChild: need to examine %s%s\n", - gn->name, gn->cohort_num); - Lst_Enqueue(l, gn); + for (ln = pgn->children->first; ln != NULL; ln = ln->next) { + GNode *cgn = ln->datum; + + Dir_UpdateMTime(cgn, FALSE); /* cgn->path may get updated as well */ + GNode_UpdateYoungestChild(pgn, cgn); + pgn->unmade--; } - return 0; } -/* Find the pathname of a child that was already made. - * - * The path and mtime of the node and the youngestChild of the parent are - * updated; the unmade children count of the parent is decremented. - * - * Input: - * gnp the node to find - */ -static int -MakeFindChild(void *gnp, void *pgnp) -{ - GNode *gn = gnp; - GNode *pgn = pgnp; - - (void)Dir_MTime(gn, 0); - Make_TimeStamp(pgn, gn); - pgn->unmade--; - - return 0; -} - /* Called by Make_Run and SuffApplyTransform on the downward pass to handle * .USE and transformation nodes, by copying the child 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 source node, which is either a .USE/.USEBEFORE * node or a transformation node (OP_TRANSFORM). * pgn The target node */ void Make_HandleUse(GNode *cgn, GNode *pgn) { GNodeListNode *ln; /* An element in the children list */ #ifdef DEBUG_SRC - if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) { + if (!(cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM))) { debug_printf("Make_HandleUse: called for plain node %s\n", cgn->name); - return; + return; /* XXX: debug mode should not affect control flow */ } #endif if ((cgn->type & (OP_USE|OP_USEBEFORE)) || Lst_IsEmpty(pgn->commands)) { if (cgn->type & OP_USEBEFORE) { /* .USEBEFORE */ Lst_PrependAll(pgn->commands, cgn->commands); } else { /* .USE, or target has no commands */ Lst_AppendAll(pgn->commands, cgn->commands); } } for (ln = cgn->children->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; /* * 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 { free(gn->name); } (void)Var_Subst(gn->uname, pgn, VARE_WANTRES, &gn->name); /* TODO: handle errors */ if (gn->uname && strcmp(gn->name, gn->uname) != 0) { /* See if we have a target for this node. */ GNode *tgn = Targ_FindNode(gn->name); if (tgn != NULL) gn = tgn; } Lst_Append(pgn->children, gn); Lst_Append(gn->parents, pgn); pgn->unmade++; } pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_USEBEFORE|OP_TRANSFORM); } /* 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. * * For a .USE child, the commands, type flags and children are copied to the * parent node, and since the relation to the .USE node is then no longer * needed, that relation is removed. * * Input: * cgn the child, which may be a .USE node * pgn the current parent */ static void MakeHandleUse(GNode *cgn, GNode *pgn, GNodeListNode *ln) { Boolean unmarked; - unmarked = ((cgn->type & OP_MARK) == 0); + unmarked = !(cgn->type & OP_MARK); cgn->type |= OP_MARK; - if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0) + if (!(cgn->type & (OP_USE|OP_USEBEFORE))) return; 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... */ Lst_Remove(pgn->children, ln); pgn->unmade--; } static void HandleUseNodes(GNode *gn) { GNodeListNode *ln, *nln; for (ln = gn->children->first; ln != NULL; ln = nln) { nln = ln->next; MakeHandleUse(ln->datum, gn, ln); } } /* Check the modification time of a gnode, and update it if necessary. * Return 0 if the gnode does not exist, or its filesystem time if it does. */ time_t Make_Recheck(GNode *gn) { - time_t mtime = Dir_MTime(gn, 1); + time_t mtime; + Dir_UpdateMTime(gn, TRUE); + mtime = gn->mtime; + #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 + * something of a kludge, I admit, but it's a useful one. * - * FRC: - * - * To force things that depend on FRC to be made, so we have to - * check for gn->children being empty as well... + * 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 + * the Dir_UpdateMTime 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 + * Christos, 4/9/92: If we are saving commands, pretend that + * the target is made now. Otherwise archives with '...' rules * don't work! */ if (!GNode_ShouldExecute(gn) || (gn->type & OP_SAVE_CMDS) || (mtime == 0 && !(gn->type & OP_WAIT))) { DEBUG2(MAKE, " recheck(%s): update time from %s to now\n", gn->name, Targ_FmtTime(gn->mtime)); gn->mtime = now; - } - else { + } else { DEBUG2(MAKE, " recheck(%s): current update time: %s\n", gn->name, Targ_FmtTime(gn->mtime)); } #endif + + /* XXX: The returned mtime may differ from gn->mtime. + * Intentionally? */ return mtime; } /* * Set the .PREFIX and .IMPSRC variables for all the implied parents * of this node. */ static void UpdateImplicitParentsVars(GNode *cgn, const char *cname) { GNodeListNode *ln; const char *cpref = GNode_VarPrefix(cgn); for (ln = cgn->implicitParents->first; ln != NULL; ln = ln->next) { GNode *pgn = ln->datum; if (pgn->flags & REMAKE) { Var_Set(IMPSRC, cname, pgn); if (cpref != NULL) Var_Set(PREFIX, cpref, pgn); } } } +/* See if a .ORDER rule stops us from building this node. */ +static Boolean +IsWaitingForOrder(GNode *gn) +{ + GNodeListNode *ln; + + for (ln = gn->order_pred->first; ln != NULL; ln = ln->next) { + GNode *ogn = ln->datum; + + if (ogn->made >= MADE || !(ogn->flags & REMAKE)) + continue; + + DEBUG2(MAKE, "IsWaitingForOrder: Waiting for .ORDER node \"%s%s\"\n", + ogn->name, ogn->cohort_num); + return TRUE; + } + return FALSE; +} + /* 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. * * 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 youngestChild 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) { const char *cname; /* the child's name */ time_t mtime = -1; GNodeList *parents; GNodeListNode *ln; GNode *centurion; /* It is save to re-examine any nodes again */ - checked++; + checked_seqno++; cname = GNode_VarTarget(cgn); DEBUG2(MAKE, "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--; 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_ForEachUntil(centurion->order_succ, MakeBuildParent, toBeMade->first); /* Now mark all the parents as having one less unmade child */ for (ln = parents->first; ln != NULL; ln = ln->next) { GNode *pgn = ln->datum; - if (DEBUG(MAKE)) - debug_printf("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 (DEBUG(MAKE)) { + debug_printf("inspect parent %s%s: ", pgn->name, pgn->cohort_num); + GNode_FprintDetails(opts.debug_file, "", pgn, ""); + debug_printf(", unmade %d ", pgn->unmade - 1); + } if (!(pgn->flags & REMAKE)) { /* This parent isn't needed */ DEBUG0(MAKE, "- 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 unmade * 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) { DEBUG0(MAKE, "- .MADE\n"); continue; } - if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) { + if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE))) { if (cgn->made == MADE) pgn->flags |= CHILDMADE; - (void)Make_TimeStamp(pgn, cgn); + GNode_UpdateYoungestChild(pgn, cgn); } /* * A parent must wait for the completion of all instances * of a `::' dependency. */ if (centurion->unmade_cohorts != 0 || centurion->made < MADE) { DEBUG2(MAKE, "- centurion made %d, %d unmade cohorts\n", centurion->made, centurion->unmade_cohorts); continue; } /* One more child of this parent is now made */ pgn->unmade--; if (pgn->unmade < 0) { if (DEBUG(MAKE)) { debug_printf("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)) { DEBUG0(MAKE, "- 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. */ DEBUG0(MAKE, "- not deferred\n"); continue; } - assert(pgn->order_pred != NULL); - if (Lst_ForEachUntil(pgn->order_pred, MakeCheckOrder, 0)) { - /* A .ORDER rule stops us building this */ + + if (IsWaitingForOrder(pgn)) continue; - } + if (DEBUG(MAKE)) { debug_printf("- %s%s made, schedule %s%s (made %d)\n", cgn->name, cgn->cohort_num, pgn->name, pgn->cohort_num, pgn->made); Targ_PrintNode(pgn, 2); } /* Ok, we can schedule the parent again */ pgn->made = REQUESTED; Lst_Enqueue(toBeMade, pgn); } UpdateImplicitParentsVars(cgn, cname); } static void UnmarkChildren(GNode *gn) { GNodeListNode *ln; for (ln = gn->children->first; ln != NULL; ln = ln->next) { GNode *child = ln->datum; child->type &= ~OP_MARK; } } /* Add a child's name to the ALLSRC and OODATE variables of the given * node, but 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...).. * * Input: * cgn The child to add * pgn The parent to whose ALLSRC variable it should * be added */ static void MakeAddAllSrc(GNode *cgn, GNode *pgn) { if (cgn->type & OP_MARK) return; cgn->type |= OP_MARK; - if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) { + if (!(cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE))) { const char *child, *allsrc; if (cgn->type & OP_ARCHV) child = GNode_VarMember(cgn); else child = GNode_Path(cgn); if (cgn->type & OP_JOIN) { allsrc = GNode_VarAllsrc(cgn); } else { allsrc = child; } if (allsrc != NULL) Var_Append(ALLSRC, allsrc, pgn); 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 + * does). In such a case, if we've updated the child, the parent * is likely to have a modification time later than that of - * the kid and anything that relies on the OODATE variable will + * the child 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); } } } /* 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. * * If the node is a .JOIN node, its TARGET variable will be set to * match its ALLSRC variable. */ void Make_DoAllVar(GNode *gn) { GNodeListNode *ln; if (gn->flags & DONE_ALLSRC) return; UnmarkChildren(gn); for (ln = gn->children->first; ln != NULL; ln = ln->next) MakeAddAllSrc(ln->datum, gn); - if (!Var_Exists(OODATE, gn)) { + if (!Var_Exists(OODATE, gn)) Var_Set(OODATE, "", gn); - } - if (!Var_Exists(ALLSRC, gn)) { + if (!Var_Exists(ALLSRC, gn)) Var_Set(ALLSRC, "", gn); - } if (gn->type & OP_JOIN) Var_Set(TARGET, GNode_VarAllsrc(gn), gn); gn->flags |= DONE_ALLSRC; } static int -MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED) -{ - GNode *bn = v_bn; - - if (bn->made >= MADE || !(bn->flags & REMAKE)) - return 0; - - DEBUG2(MAKE, "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; - DEBUG4(MAKE, "MakeBuildChild: inspect %s%s, made %d, type %x\n", - cn->name, cn->cohort_num, cn->made, cn->type); + if (DEBUG(MAKE)) { + debug_printf("MakeBuildChild: inspect %s%s, ", + cn->name, cn->cohort_num); + GNode_FprintDetails(opts.debug_file, "", cn, "\n"); + } if (cn->made > DEFERRED) return 0; /* If this node is on the RHS of a .ORDER, check LHSs. */ - assert(cn->order_pred); - if (Lst_ForEachUntil(cn->order_pred, MakeCheckOrder, 0)) { + if (IsWaitingForOrder(cn)) { /* Can't build this (or anything else in this child list) yet */ cn->made = DEFERRED; return 0; /* but keep looking */ } DEBUG2(MAKE, "MakeBuildChild: schedule %s%s\n", cn->name, cn->cohort_num); cn->made = REQUESTED; if (toBeMade_next == NULL) Lst_Append(toBeMade, cn); else Lst_InsertBefore(toBeMade, toBeMade_next, cn); if (cn->unmade_cohorts != 0) Lst_ForEachUntil(cn->cohorts, MakeBuildChild, toBeMade_next); /* * If this node is a .WAIT node with unmade children * 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 */ +/* 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; } /* Start as many jobs as possible, taking them from the toBeMade queue. * - * If the query flag was given to pmake, no job will be started, + * If the -q option was given, 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. + * returns TRUE. In all other cases, this function returns FALSE. */ static Boolean MakeStartJobs(void) { - GNode *gn; - int have_token = 0; + GNode *gn; + Boolean have_token = FALSE; 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; + have_token = TRUE; gn = Lst_Dequeue(toBeMade); DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num); if (gn->made != REQUESTED) { DEBUG1(MAKE, "state %d\n", gn->made); make_abort(gn, __LINE__); } - if (gn->checked_seqno == checked) { + if (gn->checked_seqno == checked_seqno) { /* We've already looked at this node since a job finished... */ DEBUG2(MAKE, "already checked %s%s\n", gn->name, gn->cohort_num); gn->made = DEFERRED; continue; } - gn->checked_seqno = checked; + gn->checked_seqno = checked_seqno; 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_ForEachUntil(gn->children, MakeBuildChild, toBeMade->first); /* and drop this node on the floor */ DEBUG2(MAKE, "dropped %s%s\n", gn->name, gn->cohort_num); continue; } gn->made = BEINGMADE; - if (Make_OODate(gn)) { + if (GNode_IsOODate(gn)) { DEBUG0(MAKE, "out-of-date\n"); - if (opts.queryFlag) { + if (opts.queryFlag) return TRUE; - } Make_DoAllVar(gn); Job_Make(gn); - have_token = 0; + have_token = FALSE; } else { DEBUG0(MAKE, "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; } +/* Print the status of a .ORDER node. */ static void MakePrintStatusOrderNode(GNode *ogn, GNode *gn) { if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED) /* not waiting for this one */ return; printf(" `%s%s' has .ORDER dependency against %s%s ", gn->name, gn->cohort_num, ogn->name, ogn->cohort_num); GNode_FprintDetails(stdout, "(", ogn, ")\n"); if (DEBUG(MAKE) && opts.debug_file != stdout) { debug_printf(" `%s%s' has .ORDER dependency against %s%s ", gn->name, gn->cohort_num, ogn->name, ogn->cohort_num); GNode_FprintDetails(opts.debug_file, "(", ogn, ")\n"); } } static void MakePrintStatusOrder(GNode *gn) { GNodeListNode *ln; for (ln = gn->order_pred->first; ln != NULL; ln = ln->next) MakePrintStatusOrderNode(ln->datum, gn); } static void MakePrintStatusList(GNodeList *, int *); /* 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_ForEachUntil. */ static Boolean MakePrintStatus(GNode *gn, int *errors) { if (gn->flags & DONECYCLE) /* We've completely processed this node before, don't do it again. */ return FALSE; 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", gn->name, gn->cohort_num); GNode_FprintDetails(stdout, " (", gn, ")!\n"); if (DEBUG(MAKE) && opts.debug_file != stdout) { debug_printf("`%s%s' was not built", gn->name, gn->cohort_num); GNode_FprintDetails(opts.debug_file, " (", gn, ")!\n"); } /* Most likely problem is actually caused by .ORDER */ MakePrintStatusOrder(gn); break; default: /* Errors - already counted */ printf("`%s%s' not remade because of errors.\n", gn->name, gn->cohort_num); if (DEBUG(MAKE) && opts.debug_file != stdout) debug_printf("`%s%s' not remade because of errors.\n", gn->name, gn->cohort_num); break; } return FALSE; } DEBUG3(MAKE, "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 */ + /* First time we've seen this node, check all children */ gn->flags |= CYCLE; MakePrintStatusList(gn->children, errors); /* Mark that this node needn't be processed again */ gn->flags |= DONECYCLE; return FALSE; } /* 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 TRUE; /* Reporting for our children will give the rest of the loop */ MakePrintStatusList(gn->children, errors); return FALSE; } static void MakePrintStatusList(GNodeList *gnodes, int *errors) { GNodeListNode *ln; for (ln = gnodes->first; ln != NULL; ln = ln->next) if (MakePrintStatus(ln->datum, errors)) break; } +static void +ExamineLater(GNodeList *examine, GNodeList *toBeExamined) +{ + ListNode *ln; + + for (ln = toBeExamined->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + + if (gn->flags & REMAKE) + continue; + if (gn->type & (OP_USE | OP_USEBEFORE)) + continue; + + DEBUG2(MAKE, "ExamineLater: need to examine \"%s%s\"\n", + gn->name, gn->cohort_num); + Lst_Enqueue(examine, gn); + } +} + /* Expand .USE nodes and create a new targets list. * * Input: * targs the initial list of targets */ void Make_ExpandUse(GNodeList *targs) { - GNodeList *examine; /* List of targets to examine */ + GNodeList *examine = Lst_New(); /* Queue of targets to examine */ + Lst_AppendAll(examine, targs); - { - /* XXX: Why is it necessary to copy the list? There shouldn't be - * any modifications to the list, at least the function name - * ExpandUse doesn't suggest that. */ - GNodeListNode *ln; - examine = Lst_New(); - for (ln = targs->first; ln != NULL; ln = ln->next) - Lst_Append(examine, ln->datum); - } - /* * 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)) { GNode *gn = Lst_Dequeue(examine); if (gn->flags & REMAKE) /* We've looked at this one already */ continue; gn->flags |= REMAKE; DEBUG2(MAKE, "Make_ExpandUse: examine %s%s\n", gn->name, gn->cohort_num); if (gn->type & OP_DOUBLEDEP) Lst_PrependAll(examine, gn->cohorts); /* * 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, ')'); + char *eoa = strchr(gn->name, '('); + char *eon = strchr(gn->name, ')'); if (eoa == NULL || eon == NULL) continue; *eoa = '\0'; *eon = '\0'; Var_Set(MEMBER, eoa + 1, gn); Var_Set(ARCHIVE, gn->name, gn); *eoa = '('; *eon = ')'; } - (void)Dir_MTime(gn, 0); + Dir_UpdateMTime(gn, FALSE); Var_Set(TARGET, GNode_Path(gn), gn); UnmarkChildren(gn); HandleUseNodes(gn); - if ((gn->type & OP_MADE) == 0) + if (!(gn->type & OP_MADE)) Suff_FindDeps(gn); else { - /* Pretend we made all this node's children */ - Lst_ForEachUntil(gn->children, MakeFindChild, gn); + PretendAllChildrenAreMade(gn); if (gn->unmade != 0) - printf("Warning: %s%s still has %d unmade children\n", - gn->name, gn->cohort_num, gn->unmade); + printf("Warning: %s%s still has %d unmade children\n", + gn->name, gn->cohort_num, gn->unmade); } if (gn->unmade != 0) - Lst_ForEachUntil(gn->children, MakeAddChild, examine); + ExamineLater(examine, gn->children); } Lst_Free(examine); } /* Make the .WAIT node depend on the previous children */ static void add_wait_dependency(GNodeListNode *owln, GNode *wn) { GNodeListNode *cln; GNode *cn; for (cln = owln; (cn = cln->datum) != wn; cln = cln->next) { DEBUG3(MAKE, ".WAIT: add dependency %s%s -> %s\n", cn->name, cn->cohort_num, wn->name); /* XXX: This pattern should be factored out, it repeats often */ Lst_Append(wn->children, cn); wn->unmade++; Lst_Append(cn->parents, wn); } } /* Convert .WAIT nodes into dependencies. */ static void Make_ProcessWait(GNodeList *targs) { GNode *pgn; /* 'parent' node we are examining */ GNodeListNode *owln; /* Previous .WAIT node */ GNodeList *examine; /* List of targets to examine */ /* * 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 = GNode_New(".MAIN"); pgn->flags = REMAKE; pgn->type = OP_PHONY | OP_DEPENDS; /* Get it displayed in the diag dumps */ Lst_Prepend(Targ_List(), pgn); { GNodeListNode *ln; for (ln = targs->first; ln != NULL; ln = ln->next) { GNode *cgn = ln->datum; Lst_Append(pgn->children, cgn); Lst_Append(cgn->parents, pgn); pgn->unmade++; } } /* Start building with the 'dummy' .MAIN' node */ MakeBuildChild(pgn, NULL); examine = Lst_New(); Lst_Append(examine, pgn); while (!Lst_IsEmpty(examine)) { GNodeListNode *ln; pgn = Lst_Dequeue(examine); /* We only want to process each child-list once */ if (pgn->flags & DONE_WAIT) continue; pgn->flags |= DONE_WAIT; DEBUG1(MAKE, "Make_ProcessWait: examine %s\n", pgn->name); if (pgn->type & OP_DOUBLEDEP) Lst_PrependAll(examine, pgn->cohorts); owln = pgn->children->first; for (ln = pgn->children->first; ln != NULL; ln = ln->next) { GNode *cgn = ln->datum; if (cgn->type & OP_WAIT) { add_wait_dependency(owln, cgn); owln = ln; } else { Lst_Append(examine, cgn); } } } Lst_Free(examine); } /*- *----------------------------------------------------------------------- * 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(GNodeList *targs) { int errors; /* Number of errors the Job module reports */ /* Start trying to make the current targets... */ toBeMade = Lst_New(); Make_ExpandUse(targs); Make_ProcessWait(targs); if (DEBUG(MAKE)) { debug_printf("#***# full graph\n"); Targ_PrintGraph(1); } if (opts.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. */ DEBUG1(MAKE, "done: errors %d\n", errors); if (errors == 0) { MakePrintStatusList(targs, &errors); if (DEBUG(MAKE)) { debug_printf("done: errors %d\n", errors); - if (errors) + if (errors > 0) Targ_PrintGraph(4); } } - return errors != 0; + return errors > 0; } Index: head/contrib/bmake/make.h =================================================================== --- head/contrib/bmake/make.h (revision 367862) +++ head/contrib/bmake/make.h (revision 367863) @@ -1,744 +1,788 @@ -/* $NetBSD: make.h,v 1.179 2020/11/01 17:47:26 rillig Exp $ */ +/* $NetBSD: make.h,v 1.210 2020/11/16 21:53:10 rillig 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_MAKE_H #define MAKE_MAKE_H #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_STRING_H #include #else #include #endif #include #include #ifndef FD_CLOEXEC #define FD_CLOEXEC 1 #endif #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 +#define MAKE_INLINE static inline MAKE_ATTR_UNUSED + /* * A boolean type is defined as an integer, not an enum, for historic reasons. * The only allowed values are the constants TRUE and FALSE (1 and 0). */ #ifdef USE_DOUBLE_BOOLEAN /* During development, to find type mismatches in function declarations. */ typedef double Boolean; #define TRUE 1.0 #define FALSE 0.0 #elif defined(USE_UCHAR_BOOLEAN) /* During development, to find code that depends on the exact value of TRUE or * that stores other values in Boolean variables. */ typedef unsigned char Boolean; #define TRUE ((unsigned char)0xFF) #define FALSE ((unsigned char)0x00) #elif defined(USE_CHAR_BOOLEAN) /* During development, to find code that uses a boolean as array index, via * -Wchar-subscripts. */ typedef char Boolean; #define TRUE ((char)-1) #define FALSE ((char)0x00) #elif defined(USE_ENUM_BOOLEAN) typedef enum Boolean { FALSE, TRUE } Boolean; #else typedef int Boolean; #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #endif #include "lst.h" #include "enum.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 -typedef enum { +typedef enum GNodeMade { 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). */ } GNodeMade; /* 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. * * Some of the OP_ constants can be combined, others cannot. */ typedef enum GNodeType { + OP_NONE = 0, + /* The dependency operator ':' is the most common one. The commands of * this node are executed if any child is out-of-date. */ OP_DEPENDS = 1 << 0, /* The dependency operator '!' always executes its commands, even if * its children are up-to-date. */ OP_FORCE = 1 << 1, /* The dependency operator '::' behaves like ':', except that it allows * multiple dependency groups to be defined. Each of these groups is - * executed on its own, independently from the others. */ + * executed on its own, independently from the others. Each individual + * dependency group is called a cohort. */ OP_DOUBLEDEP = 1 << 2, /* Matches the dependency operators ':', '!' and '::'. */ OP_OPMASK = OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP, /* Don't care if the target doesn't exist and can't be created */ OP_OPTIONAL = 1 << 3, /* Use associated commands for parents */ OP_USE = 1 << 4, /* Target is never out of date, but always execute commands anyway. * Its time doesn't matter, so it has none...sort of */ OP_EXEC = 1 << 5, /* Ignore non-zero exit status from shell commands when creating the node */ OP_IGNORE = 1 << 6, /* Don't remove the target when interrupted */ OP_PRECIOUS = 1 << 7, /* Don't echo commands when executed */ OP_SILENT = 1 << 8, /* 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 */ OP_MAKE = 1 << 9, /* Target is out-of-date only if any of its children was out-of-date */ OP_JOIN = 1 << 10, /* Assume the children of the node have been already made */ OP_MADE = 1 << 11, /* Special .BEGIN, .END, .INTERRUPT */ OP_SPECIAL = 1 << 12, /* Like .USE, only prepend commands */ OP_USEBEFORE = 1 << 13, /* The node is invisible to its parents. I.e. it doesn't show up in the - * parents' local variables. */ + * parents' local variables (.IMPSRC, .ALLSRC). */ OP_INVISIBLE = 1 << 14, /* The node is exempt from normal 'main target' processing in parse.c */ OP_NOTMAIN = 1 << 15, /* Not a file target; run always */ OP_PHONY = 1 << 16, /* Don't search for file in the path */ OP_NOPATH = 1 << 17, - /* .WAIT phony node */ + /* In a dependency line "target: source1 .WAIT source2", source1 is made + * first, including its children. Once that is finished, source2 is made, + * including its children. The .WAIT keyword may appear more than once in + * a single dependency declaration. */ OP_WAIT = 1 << 18, /* .NOMETA do not create a .meta file */ OP_NOMETA = 1 << 19, /* .META we _do_ want a .meta file */ OP_META = 1 << 20, /* Do not compare commands in .meta file */ OP_NOMETA_CMP = 1 << 21, /* Possibly a submake node */ OP_SUBMAKE = 1 << 22, /* Attributes applied by PMake */ - /* The node is a transformation rule */ + /* The node is a transformation rule, such as ".c.o". */ OP_TRANSFORM = 1 << 31, /* Target is a member of an archive */ /* XXX: How does this differ from OP_ARCHV? */ OP_MEMBER = 1 << 30, /* The node is a library, * its name has the form "-l" */ OP_LIB = 1 << 29, /* The node is an archive member, * its name has the form "archive(member)" */ /* XXX: How does this differ from OP_MEMBER? */ OP_ARCHV = 1 << 28, /* Target has all the commands it should. Used when parsing to catch * multiple command groups for a target. Only applies to the dependency * operators ':' and '!', but not to '::'. */ OP_HAS_COMMANDS = 1 << 27, /* The special command "..." has been seen. All further commands from * this node will be saved on the .END node instead, to be executed at * the very end. */ OP_SAVE_CMDS = 1 << 26, /* Already processed by Suff_FindDeps */ OP_DEPS_FOUND = 1 << 25, /* Node found while expanding .ALLSRC */ OP_MARK = 1 << 24, OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM } GNodeType; typedef enum GNodeFlags { REMAKE = 0x0001, /* this target needs to be (re)made */ CHILDMADE = 0x0002, /* children of this target were made */ FORCE = 0x0004, /* children don't exist, and we pretend made */ DONE_WAIT = 0x0008, /* Set by Make_ProcessWait() */ DONE_ORDER = 0x0010, /* Build requested by .ORDER processing */ FROM_DEPEND = 0x0020, /* Node created from .depend */ DONE_ALLSRC = 0x0040, /* We do it once only */ CYCLE = 0x1000, /* Used by MakePrintStatus */ DONECYCLE = 0x2000, /* Used by MakePrintStatus */ INTERNAL = 0x4000 /* Internal use only */ } GNodeFlags; typedef struct List StringList; typedef struct ListNode StringListNode; typedef struct List GNodeList; typedef struct ListNode GNodeListNode; typedef struct List /* of CachedDir */ SearchPath; /* A graph node represents a target that can possibly be made, including its * relation to other targets and a lot of other details. */ typedef struct GNode { /* The target's name, such as "clean" or "make.c" */ char *name; /* The unexpanded name of a .USE node */ char *uname; /* The full pathname of the file belonging to the target. * XXX: What about .PHONY targets? These don't have an associated path. */ char *path; /* The type of operator used to define the sources (see the OP flags below). * XXX: This looks like a wild mixture of type and flags. */ GNodeType type; GNodeFlags flags; /* The state of processing on this node */ GNodeMade made; int unmade; /* The number of unmade children */ /* The modification time; 0 means the node does not have a corresponding - * file; see Make_OODate. */ + * file; see GNode_IsOODate. */ time_t mtime; struct GNode *youngestChild; /* The GNodes for which this node is an implied source. May be empty. * For example, when there is an inference rule for .c.o, the node for * file.c has the node for file.o in this list. */ GNodeList *implicitParents; - /* Other nodes of the same name, for the '::' operator. */ - GNodeList *cohorts; - /* The nodes that depend on this one, or in other words, the nodes for * which this is a source. */ GNodeList *parents; /* The nodes on which this one depends. */ GNodeList *children; /* .ORDER nodes we need made. The nodes that must be made (if they're * made) before this node can be made, but that do not enter into the * datedness of this node. */ GNodeList *order_pred; /* .ORDER nodes who need us. The nodes that must be made (if they're made * at all) after this node is made, but that do not depend on this node, * in the normal sense. */ GNodeList *order_succ; + /* Other nodes of the same name, for the '::' dependency operator. */ + GNodeList *cohorts; /* The "#n" suffix for this cohort, or "" for other nodes */ char cohort_num[8]; /* The number of unmade instances on the cohorts list */ int unmade_cohorts; /* Pointer to the first instance of a '::' node; only set when on a * cohorts list */ struct GNode *centurion; /* Last time (sequence number) we tried to make this node */ unsigned int checked_seqno; /* The "local" variables that are specific to this target and this target * only, such as $@, $<, $?. * * Also used for the global variable scopes VAR_GLOBAL, VAR_CMDLINE, * VAR_INTERNAL, which contain variables with arbitrary names. */ HashTable /* of Var pointer */ context; /* The commands to be given to a shell to create this target. */ StringList *commands; /* Suffix for the node (determined by Suff_FindDeps and opaque to everyone * but the Suff module) */ struct Suff *suffix; - /* filename where the GNode got defined */ + /* Filename where the GNode got defined */ + /* XXX: What is the lifetime of this string? */ const char *fname; - /* line number where the GNode got defined */ + /* Line number where the GNode got defined */ int lineno; } GNode; -/* - * Error levels for parsing. PARSE_FATAL means the process cannot continue - * once the top-level makefile has been parsed. PARSE_WARNING and PARSE_INFO - * mean it can. - */ +/* Error levels for diagnostics during parsing. */ typedef enum ParseErrorLevel { + /* Exit when the current top-level makefile has been parsed completely. */ PARSE_FATAL = 1, + /* Print "warning"; may be upgraded to fatal by the -w option. */ PARSE_WARNING, + /* Informational, mainly used during development of makefiles. */ PARSE_INFO } ParseErrorLevel; /* * Values returned by Cond_EvalLine and Cond_EvalCondition. */ typedef enum CondEvalResult { COND_PARSE, /* Parse the next lines */ COND_SKIP, /* Skip the next lines */ COND_INVALID /* Not a conditional statement */ } CondEvalResult; -/* - * Definitions for the "local" variables. Used only for clarity. - */ +/* Names of the variables that are "local" to a specific target. */ #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 "type & OP_OPMASK) != 0; } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_Path(const GNode *gn) { return gn->path != NULL ? gn->path : gn->name; } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarTarget(GNode *gn) { return Var_ValueDirect(TARGET, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarOodate(GNode *gn) { return Var_ValueDirect(OODATE, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarAllsrc(GNode *gn) { return Var_ValueDirect(ALLSRC, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarImpsrc(GNode *gn) { return Var_ValueDirect(IMPSRC, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarPrefix(GNode *gn) { return Var_ValueDirect(PREFIX, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarArchive(GNode *gn) { return Var_ValueDirect(ARCHIVE, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); } #ifdef __GNUC__ #define UNCONST(ptr) ({ \ union __unconst { \ const void *__cp; \ void *__p; \ } __d; \ __d.__cp = ptr, __d.__p; }) #else #define UNCONST(ptr) (void *)(ptr) #endif /* At least GNU/Hurd systems lack hardcoded MAXPATHLEN/PATH_MAX */ #ifdef HAVE_LIMITS_H #include #endif #ifndef MAXPATHLEN #define MAXPATHLEN BMAKE_PATH_MAX #endif #ifndef PATH_MAX #define PATH_MAX MAXPATHLEN #endif #if defined(SYSV) #define KILLPG(pid, sig) kill(-(pid), (sig)) #else #define KILLPG(pid, sig) killpg((pid), (sig)) #endif -static inline MAKE_ATTR_UNUSED Boolean ch_isalnum(char ch) -{ return isalnum((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED Boolean ch_isalpha(char ch) -{ return isalpha((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED Boolean ch_isdigit(char ch) -{ return isdigit((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED Boolean ch_isspace(char ch) -{ return isspace((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED Boolean ch_isupper(char ch) -{ return isupper((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED char ch_tolower(char ch) -{ return (char)tolower((unsigned char)ch); } -static inline MAKE_ATTR_UNUSED char ch_toupper(char ch) -{ return (char)toupper((unsigned char)ch); } +MAKE_INLINE Boolean +ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; } +MAKE_INLINE char +ch_tolower(char ch) { return (char)tolower((unsigned char)ch); } +MAKE_INLINE char +ch_toupper(char ch) { return (char)toupper((unsigned char)ch); } -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void cpp_skip_whitespace(const char **pp) { while (ch_isspace(**pp)) (*pp)++; } -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void +cpp_skip_hspace(const char **pp) +{ + while (**pp == ' ' || **pp == '\t') + (*pp)++; +} + +MAKE_INLINE void pp_skip_whitespace(char **pp) { while (ch_isspace(**pp)) + (*pp)++; +} + +MAKE_INLINE void +pp_skip_hspace(char **pp) +{ + while (**pp == ' ' || **pp == '\t') (*pp)++; } #ifdef MAKE_NATIVE # include # ifndef lint # define MAKE_RCSID(id) __RCSID(id) # endif #else # define MAKE_RCSID(id) static volatile char rcsid[] = id #endif #endif /* MAKE_MAKE_H */ Index: head/contrib/bmake/make_malloc.h =================================================================== --- head/contrib/bmake/make_malloc.h (revision 367862) +++ head/contrib/bmake/make_malloc.h (revision 367863) @@ -1,54 +1,54 @@ -/* $NetBSD: make_malloc.h,v 1.12 2020/10/19 23:43:55 rillig Exp $ */ +/* $NetBSD: make_malloc.h,v 1.13 2020/11/10 00:32:12 rillig Exp $ */ /*- * Copyright (c) 2009 The NetBSD Foundation, 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef USE_EMALLOC void *bmake_malloc(size_t); void *bmake_realloc(void *, size_t); char *bmake_strdup(const char *); char *bmake_strldup(const char *, size_t); #else #include #define bmake_malloc(n) emalloc(n) #define bmake_realloc(p, n) erealloc(p, n) #define bmake_strdup(s) estrdup(s) #define bmake_strldup(s, n) estrndup(s, n) #endif char *bmake_strsedup(const char *, const char *); /* Thin wrapper around free(3) to avoid the extra function call in case * p is NULL, to save a few machine instructions. * * The case of a NULL pointer happens especially often after Var_Value, * since only environment variables need to be freed, but not others. */ -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void bmake_free(void *p) { if (p != NULL) free(p); } Index: head/contrib/bmake/meta.c =================================================================== --- head/contrib/bmake/meta.c (revision 367862) +++ head/contrib/bmake/meta.c (revision 367863) @@ -1,1709 +1,1711 @@ -/* $NetBSD: meta.c,v 1.136 2020/10/31 12:04:24 rillig Exp $ */ +/* $NetBSD: meta.c,v 1.144 2020/11/15 12:02:44 rillig Exp $ */ /* * Implement 'meta' mode. * Adapted from John Birrell's patches to FreeBSD make. * --sjg */ /* * Copyright (c) 2009-2016, 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 #ifdef HAVE_LIBGEN_H #include #elif !defined(HAVE_DIRNAME) char * dirname(char *); #endif #include #if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H) #include #endif #include "make.h" #include "dir.h" #include "job.h" #ifdef USE_FILEMON #include "filemon/filemon.h" #endif static BuildMon Mybm; /* for compat */ static StringList *metaBailiwick; /* our scope of control */ static char *metaBailiwickStr; /* string storage for the list */ static StringList *metaIgnorePaths; /* paths we deliberately ignore */ static char *metaIgnorePathsStr; /* string storage for the list */ #ifndef MAKE_META_IGNORE_PATHS #define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS" #endif #ifndef MAKE_META_IGNORE_PATTERNS #define MAKE_META_IGNORE_PATTERNS ".MAKE.META.IGNORE_PATTERNS" #endif #ifndef MAKE_META_IGNORE_FILTER #define MAKE_META_IGNORE_FILTER ".MAKE.META.IGNORE_FILTER" #endif Boolean useMeta = FALSE; static Boolean useFilemon = FALSE; static Boolean writeMeta = FALSE; static Boolean metaMissing = FALSE; /* oodate if missing */ static Boolean filemonMissing = FALSE; /* oodate if missing */ 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 metaIgnorePatterns = FALSE; /* do we need to do pattern matches */ static Boolean metaIgnoreFilter = FALSE; /* do we have more complex filtering? */ 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) +# 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 /* * Open the filemon device. */ static void meta_open_filemon(BuildMon *pbm) { int dupfd; pbm->mon_fd = -1; pbm->filemon = NULL; if (!useFilemon || !pbm->mfp) return; pbm->filemon = filemon_open(); if (pbm->filemon == NULL) { useFilemon = FALSE; warn("Could not open filemon %s", filemon_path()); 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 ((dupfd = dup(pbm->mon_fd)) == -1) { err(1, "Could not dup filemon output!"); } (void)fcntl(dupfd, F_SETFD, FD_CLOEXEC); if (filemon_setfd(pbm->filemon, dupfd) == -1) { err(1, "Could not set filemon file descriptor!"); } /* we don't need these once we exec */ (void)fcntl(pbm->mon_fd, F_SETFD, FD_CLOEXEC); } /* * Read the build monitor output file and write records to the target's * metadata file. */ static int filemon_read(FILE *mfp, int fd) { char buf[BUFSIZ]; int error; /* Check if we're not writing to a meta data file.*/ if (mfp == NULL) { if (fd >= 0) close(fd); /* not interested */ return 0; } /* rewind */ if (lseek(fd, (off_t)0, SEEK_SET) < 0) { error = errno; warn("Could not rewind filemon"); fprintf(mfp, "\n"); } else { ssize_t n; error = 0; fprintf(mfp, "\n-- filemon acquired metadata --\n"); - while ((n = read(fd, buf, sizeof(buf))) > 0) { + while ((n = read(fd, buf, sizeof buf)) > 0) { if ((ssize_t)fwrite(buf, 1, (size_t)n, mfp) < n) error = EIO; } } fflush(mfp); if (close(fd) < 0) error = errno; return error; } #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 - (size_t)(cp - buf)); } else { return; /* can't happen? */ } } } while (cp); } static char * meta_name(char *mname, size_t mnamelen, const char *dname, const char *tname, const char *cwd) { char buf[MAXPATHLEN]; char *rp; char *cp; char *tp; char *dtp; size_t ldname; /* * 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 (cached_realpath(tname, buf)) { if ((rp = strrchr(buf, '/'))) { rp++; cp++; if (strcmp(cp, rp) != 0) strlcpy(rp, cp, sizeof buf - (size_t)(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)); + strlcpy(buf, tname, sizeof buf); } else { - snprintf(buf, sizeof(buf), "%s/%s", cwd, tname); + snprintf(buf, sizeof buf, "%s/%s", cwd, tname); } - eat_dots(buf, sizeof(buf), 1); /* ./ */ - eat_dots(buf, sizeof(buf), 2); /* ../ */ + 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); dtp = dirname(tp); if (strcmp(dname, dtp) == 0) snprintf(mname, mnamelen, "%s.meta", tname); else { ldname = strlen(dname); if (strncmp(dname, dtp, ldname) == 0 && dtp[ldname] == '/') snprintf(mname, mnamelen, "%s/%s.meta", dname, &tname[ldname+1]); 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); return mname; } /* * Return true if running ${.MAKE} * Bypassed if target is flagged .MAKE */ static int is_submake(void *cmdp, void *gnp) { static const char *p_make = NULL; static size_t p_len; char *cmd = cmdp; GNode *gn = gnp; char *mp = NULL; char *cp; char *cp2; int rc = 0; /* keep looking */ - if (!p_make) { + if (p_make == NULL) { void *dontFreeIt; p_make = Var_Value(".MAKE", gn, &dontFreeIt); p_len = strlen(p_make); } cp = strchr(cmd, '$'); if ((cp)) { (void)Var_Subst(cmd, gn, VARE_WANTRES, &mp); /* TODO: handle errors */ cmd = mp; } cp2 = strstr(cmd, p_make); - if ((cp2)) { + if (cp2 != NULL) { 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; } } } free(mp); return rc; } typedef struct meta_file_s { FILE *fp; GNode *gn; } meta_file_t; static void printCMD(const char *cmd, meta_file_t *mfp) { char *cmd_freeIt = NULL; if (strchr(cmd, '$')) { (void)Var_Subst(cmd, mfp->gn, VARE_WANTRES, &cmd_freeIt); /* TODO: handle errors */ cmd = cmd_freeIt; } fprintf(mfp->fp, "CMD %s\n", cmd); free(cmd_freeIt); } static void printCMDs(GNode *gn, meta_file_t *mf) { GNodeListNode *ln; for (ln = gn->commands->first; ln != NULL; ln = ln->next) printCMD(ln->datum, mf); } /* * Certain node types never get a .meta file */ #define SKIP_META_TYPE(_type) do { \ if ((gn->type & __CONCAT(OP_, _type))) { \ if (verbose) { \ debug_printf("Skipping meta for %s: .%s\n", \ gn->name, __STRING(_type)); \ } \ return FALSE; \ } \ } while (0) /* * Do we need/want a .meta file ? */ static Boolean meta_needed(GNode *gn, const char *dname, char *objdir, int verbose) { - struct make_stat mst; + struct cached_stat cst; if (verbose) verbose = DEBUG(META); /* 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); } /* Check if there are no commands to execute. */ if (Lst_IsEmpty(gn->commands)) { if (verbose) debug_printf("Skipping meta for %s: no commands\n", gn->name); return FALSE; } if ((gn->type & (OP_META|OP_SUBMAKE)) == OP_SUBMAKE) { /* OP_SUBMAKE is a bit too aggressive */ if (Lst_ForEachUntil(gn->commands, is_submake, gn)) { DEBUG1(META, "Skipping meta for %s: .SUBMAKE\n", gn->name); return FALSE; } } /* The object directory may not exist. Check it.. */ - if (cached_stat(dname, &mst) != 0) { + if (cached_stat(dname, &cst) != 0) { if (verbose) debug_printf("Skipping meta for %s: no .OBJDIR\n", gn->name); return FALSE; } /* make sure these are canonical */ if (cached_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 (verbose) debug_printf("Skipping meta for %s: .OBJDIR == .CURDIR\n", gn->name); return FALSE; } return TRUE; } 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; void *objdir_freeIt; mf.fp = NULL; dname = Var_Value(".OBJDIR", gn, &objdir_freeIt); tname = GNode_VarTarget(gn); /* if this succeeds objdir is realpath of dname */ if (!meta_needed(gn, dname, objdir, TRUE)) goto out; dname = objdir; if (metaVerbose) { char *mp; /* Describe the target we are building */ (void)Var_Subst("${" MAKE_META_PREFIX "}", gn, VARE_WANTRES, &mp); /* TODO: handle errors */ 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 (!writeMeta) /* Don't create meta data. */ goto out; - fname = meta_name(pbm->meta_fname, sizeof(pbm->meta_fname), + fname = meta_name(pbm->meta_fname, sizeof pbm->meta_fname, dname, tname, objdir); #ifdef DEBUG_META_MODE DEBUG1(META, "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; printCMDs(gn, &mf); - fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf))); + fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof buf)); fprintf(mf.fp, "TARGET %s\n", tname); cp = GNode_VarOodate(gn); if (cp && *cp) { fprintf(mf.fp, "OODATE %s\n", cp); } 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: bmake_free(objdir_freeIt); 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", filemon_path(), VAR_GLOBAL); #endif } #define get_mode_bf(bf, token) \ if ((cp = strstr(make_mode, token))) \ - bf = boolValue(&cp[sizeof(token) - 1]) + bf = boolValue(cp + sizeof (token) - 1) /* * Initialization we need after reading makefiles. */ void meta_mode_init(const char *make_mode) { static int once = 0; char *cp; void *freeIt; 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 (strstr(make_mode, "ignore-cmd")) metaIgnoreCMDs = TRUE; if (useFilemon) get_mode_bf(filemonMissing, "missing-filemon="); get_mode_bf(metaCurdirOk, "curdirok="); get_mode_bf(metaMissing, "missing-meta="); get_mode_bf(metaSilent, "silent="); } 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); } if (once) return; once = 1; - memset(&Mybm, 0, sizeof(Mybm)); + memset(&Mybm, 0, sizeof Mybm); /* * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} */ metaBailiwick = Lst_New(); (void)Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", VAR_GLOBAL, VARE_WANTRES, &metaBailiwickStr); /* TODO: handle errors */ - str2Lst_Append(metaBailiwick, metaBailiwickStr, NULL); + str2Lst_Append(metaBailiwick, metaBailiwickStr); /* * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} */ metaIgnorePaths = Lst_New(); Var_Append(MAKE_META_IGNORE_PATHS, "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL); (void)Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL, VARE_WANTRES, &metaIgnorePathsStr); /* TODO: handle errors */ - str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr, NULL); + str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr); /* * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS} */ freeIt = NULL; if (Var_Value(MAKE_META_IGNORE_PATTERNS, VAR_GLOBAL, &freeIt)) { metaIgnorePatterns = TRUE; bmake_free(freeIt); } freeIt = NULL; if (Var_Value(MAKE_META_IGNORE_FILTER, VAR_GLOBAL, &freeIt)) { metaIgnoreFilter = TRUE; bmake_free(freeIt); } } /* * 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) { meta_open_filemon(pbm); } else { pbm->mon_fd = -1; pbm->filemon = NULL; } #endif } /* * The child calls this before doing anything. * It does not disturb our state. */ void meta_job_child(Job *job) { #ifdef USE_FILEMON BuildMon *pbm; if (job != NULL) { pbm = &job->bm; } else { pbm = &Mybm; } if (pbm->mfp != NULL) { close(fileno(pbm->mfp)); if (useFilemon && pbm->filemon) { pid_t pid; pid = getpid(); if (filemon_setpid_child(pbm->filemon, pid) == -1) { err(1, "Could not set filemon pid!"); } } } #endif } void meta_job_parent(Job *job, pid_t pid) { #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) BuildMon *pbm; if (job != NULL) { pbm = &job->bm; } else { pbm = &Mybm; } if (useFilemon && pbm->filemon) { filemon_setpid_parent(pbm->filemon, pid); } #endif } int meta_job_fd(Job *job) { #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) BuildMon *pbm; if (job != NULL) { pbm = &job->bm; } else { pbm = &Mybm; } if (useFilemon && pbm->filemon) { return filemon_readfd(pbm->filemon); } #endif return -1; } int meta_job_event(Job *job) { #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) BuildMon *pbm; if (job != NULL) { pbm = &job->bm; } else { pbm = &Mybm; } if (useFilemon && pbm->filemon) { return filemon_process(pbm->filemon); } #endif return 0; } void meta_job_error(Job *job, GNode *gn, int flags, int status) { char cwd[MAXPATHLEN]; BuildMon *pbm; if (job != NULL) { pbm = &job->bm; - if (!gn) + if (gn == NULL) gn = job->node; } else { pbm = &Mybm; } if (pbm->mfp != NULL) { fprintf(pbm->mfp, "\n*** Error code %d%s\n", status, (flags & JOB_IGNERR) ? "(ignored)" : ""); } if (gn) { Var_Set(".ERROR_TARGET", GNode_Path(gn), VAR_GLOBAL); } - getcwd(cwd, sizeof(cwd)); + getcwd(cwd, sizeof cwd); Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL); - if (pbm->meta_fname[0]) { + if (pbm->meta_fname[0] != '\0') { Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL); } 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 size_t meta_prefix_len; - if (!meta_prefix) { + if (meta_prefix == NULL) { char *cp2; (void)Var_Subst("${" MAKE_META_PREFIX "}", VAR_GLOBAL, VARE_WANTRES, &meta_prefix); /* TODO: handle errors */ if ((cp2 = strchr(meta_prefix, '$'))) meta_prefix_len = (size_t)(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); } } int meta_cmd_finish(void *pbmp) { int error = 0; BuildMon *pbm = pbmp; #ifdef USE_FILEMON int x; #endif - if (!pbm) + if (pbm == NULL) pbm = &Mybm; #ifdef USE_FILEMON if (pbm->filemon) { while (filemon_process(pbm->filemon) > 0) continue; if (filemon_close(pbm->filemon) == -1) error = errno; x = filemon_read(pbm->mfp, pbm->mon_fd); if (error == 0 && x != 0) error = x; pbm->mon_fd = -1; pbm->filemon = NULL; - } else + return error; + } #endif - fprintf(pbm->mfp, "\n"); /* ensure end with newline */ + + fprintf(pbm->mfp, "\n"); /* ensure end with newline */ return error; } int meta_job_finish(Job *job) { BuildMon *pbm; int error = 0; int x; if (job != NULL) { pbm = &job->bm; } else { pbm = &Mybm; } if (pbm->mfp != NULL) { error = meta_cmd_finish(pbm); x = fclose(pbm->mfp); if (error == 0 && x != 0) error = errno; pbm->mfp = NULL; pbm->meta_fname[0] = '\0'; } return error; } void meta_finish(void) { if (metaBailiwick != NULL) Lst_Free(metaBailiwick); free(metaBailiwickStr); if (metaIgnorePaths != NULL) Lst_Free(metaIgnorePaths); free(metaIgnorePathsStr); } /* * 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], (int)bufsz - o, fp) != NULL) { check_newline: x = o + (int)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(((size_t)fs.st_size / 2), BUFSIZ); if (newsz <= bufsz) newsz = ROUNDUP((size_t)fs.st_size, BUFSIZ); if (newsz <= bufsz) return x; /* truncated */ DEBUG2(META, "growing buffer %zu -> %zu\n", bufsz, newsz); p = bmake_realloc(buf, newsz); if (p) { *bufp = buf = p; *szp = bufsz = newsz; /* fetch the rest */ - if (!fgets(&buf[x], (int)bufsz - x, fp)) + if (fgets(&buf[x], (int)bufsz - x, fp) == NULL) return x; /* truncated! */ goto check_newline; } } } return 0; } /* Lst_ForEachUntil wants 1 to stop search */ static int prefix_match(void *p, void *q) { const char *prefix = p; const char *path = q; size_t n = strlen(prefix); return strncmp(path, prefix, n) == 0; } /* See if the path equals prefix or starts with "prefix/". */ static Boolean path_starts_with(const char *path, const char *prefix) { size_t n = strlen(prefix); if (strncmp(path, prefix, n) != 0) return FALSE; return path[n] == '\0' || path[n] == '/'; } static int meta_ignore(GNode *gn, const char *p) { char fname[MAXPATHLEN]; if (p == NULL) return TRUE; if (*p == '/') { cached_realpath(p, fname); /* clean it up */ if (Lst_ForEachUntil(metaIgnorePaths, prefix_match, fname)) { #ifdef DEBUG_META_MODE DEBUG1(META, "meta_oodate: ignoring path: %s\n", p); #endif return TRUE; } } if (metaIgnorePatterns) { const char *expr; char *pm; Var_Set(".p.", p, gn); expr = "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}"; (void)Var_Subst(expr, gn, VARE_WANTRES, &pm); /* TODO: handle errors */ if (*pm) { #ifdef DEBUG_META_MODE DEBUG1(META, "meta_oodate: ignoring pattern: %s\n", p); #endif free(pm); return TRUE; } free(pm); } if (metaIgnoreFilter) { char *fm; /* skip if filter result is empty */ - snprintf(fname, sizeof(fname), + snprintf(fname, sizeof fname, "${%s:L:${%s:ts:}}", p, MAKE_META_IGNORE_FILTER); (void)Var_Subst(fname, gn, VARE_WANTRES, &fm); /* TODO: handle errors */ if (*fm == '\0') { #ifdef DEBUG_META_MODE DEBUG1(META, "meta_oodate: ignoring filtered: %s\n", p); #endif free(fm); return TRUE; } free(fm); } return FALSE; } /* * When running with 'meta' functionality, a target can be out-of-date * if any of the references in its meta data file is more recent. * We have to track the latestdir on a per-process basis. */ #define LCWD_VNAME_FMT ".meta.%d.lcwd" #define LDIR_VNAME_FMT ".meta.%d.ldir" /* * It is possible that a .meta file is corrupted, * if we detect this we want to reproduce it. * Setting oodate TRUE will have that effect. */ #define CHECK_VALID_META(p) if (!(p && *p)) { \ warnx("%s: %d: malformed", fname, lineno); \ oodate = TRUE; \ continue; \ } #define DEQUOTE(p) if (*p == '\'') { \ char *ep; \ p++; \ if ((ep = strchr(p, '\''))) \ *ep = '\0'; \ } static void append_if_new(StringList *list, const char *str) { StringListNode *ln; for (ln = list->first; ln != NULL; ln = ln->next) if (strcmp(ln->datum, str) == 0) return; Lst_Append(list, bmake_strdup(str)); } 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]; const char *dname; const char *tname; 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; StringList *missingFiles; - int have_filemon = FALSE; + Boolean have_filemon = FALSE; void *objdir_freeIt; if (oodate) return oodate; /* we're done */ dname = Var_Value(".OBJDIR", gn, &objdir_freeIt); tname = GNode_VarTarget(gn); /* if this succeeds fname3 is realpath of dname */ if (!meta_needed(gn, dname, fname3, FALSE)) goto oodate_out; dname = fname3; missingFiles = Lst_New(); /* * 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(fname, sizeof(fname), dname, tname, dname); + meta_name(fname, sizeof fname, dname, tname, dname); #ifdef DEBUG_META_MODE DEBUG1(META, "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 x; StringListNode *cmdNode; - struct make_stat mst; + struct cached_stat cst; - if (!buf) { + if (buf == NULL) { bufsz = 8 * BUFSIZ; buf = bmake_malloc(bufsz); } - if (!cwdlen) { - if (getcwd(cwd, sizeof(cwd)) == NULL) + if (cwdlen == 0) { + 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)); + strlcpy(lcwd, cwd, sizeof lcwd); + strlcpy(latestdir, cwd, sizeof latestdir); - if (!tmpdir) { + if (tmpdir == NULL) { tmpdir = getTmpdir(); tmplen = strlen(tmpdir); } /* we want to track all the .meta we read */ Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); cmdNode = gn->commands->first; 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 (!have_filemon) { if (strncmp(buf, "-- filemon", 10) == 0) { have_filemon = TRUE; continue; } if (strncmp(buf, "# buildmon", 10) == 0) { have_filemon = TRUE; continue; } } /* Delimit the record type. */ p = buf; #ifdef DEBUG_META_MODE DEBUG3(META, "%s: %d: %s\n", fname, lineno, buf); #endif strsep(&p, " "); if (have_filemon) { /* * 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) { const char *ldir; void *tp; if (lastpid > 0) { /* We need to remember these. */ Var_Set(lcwd_vname, lcwd, VAR_GLOBAL); Var_Set(ldir_vname, latestdir, VAR_GLOBAL); } - snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid); - snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid); + 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)); + strlcpy(latestdir, ldir, sizeof latestdir); bmake_free(tp); } ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp); if (ldir) { - strlcpy(lcwd, ldir, sizeof(lcwd)); + strlcpy(lcwd, ldir, sizeof lcwd); bmake_free(tp); } } /* Skip past the pid. */ if (strsep(&p, " ") == NULL) continue; #ifdef DEBUG_META_MODE if (DEBUG(META)) debug_printf("%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); + snprintf(cldir, sizeof cldir, LCWD_VNAME_FMT, child); Var_Set(cldir, lcwd, VAR_GLOBAL); - snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child); + snprintf(cldir, sizeof cldir, LDIR_VNAME_FMT, child); Var_Set(cldir, latestdir, VAR_GLOBAL); #ifdef DEBUG_META_MODE if (DEBUG(META)) debug_printf( "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", fname, lineno, child, cwd, lcwd, latestdir); #endif } } break; case 'C': /* Chdir */ /* Update lcwd and latest directory. */ - strlcpy(latestdir, p, sizeof(latestdir)); - strlcpy(lcwd, p, sizeof(lcwd)); + strlcpy(latestdir, p, sizeof latestdir); + strlcpy(lcwd, p, sizeof lcwd); Var_Set(lcwd_vname, lcwd, VAR_GLOBAL); Var_Set(ldir_vname, lcwd, VAR_GLOBAL); #ifdef DEBUG_META_MODE DEBUG4(META, "%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 == '/') { /* remove any missingFiles entries that match p */ StringListNode *ln = missingFiles->first; while (ln != NULL) { StringListNode *next = ln->next; if (path_starts_with(ln->datum, p)) { free(ln->datum); Lst_Remove(missingFiles, ln); } ln = next; } } if (buf[0] == 'M') { /* the target of the mv is a file 'W'ritten */ #ifdef DEBUG_META_MODE DEBUG2(META, "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 DEBUG2(META, "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_ForEachUntil(metaBailiwick, prefix_match, p)) break; /* tmpdir might be within */ if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0) break; /* ignore anything containing the string "tmp" */ + /* XXX: The arguments to strstr must be swapped. */ if ((strstr("tmp", p))) break; - if ((link_src != NULL && cached_lstat(p, &mst) < 0) || - (link_src == NULL && cached_stat(p, &mst) < 0)) { + if ((link_src != NULL && cached_lstat(p, &cst) < 0) || + (link_src == NULL && cached_stat(p, &cst) < 0)) { if (!meta_ignore(gn, p)) append_if_new(missingFiles, p); } break; check_link_src: p = link_src; link_src = NULL; #ifdef DEBUG_META_MODE DEBUG1(META, "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 (meta_ignore(gn, p)) 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); + snprintf(fname1, sizeof fname1, "%s/%s", latestdir, p); sdirs[sdx++] = fname1; if (strcmp(latestdir, lcwd) != 0) { /* Check vs lcwd */ - snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p); + 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); + snprintf(fname3, sizeof fname3, "%s/%s", cwd, p); sdirs[sdx++] = fname3; } } sdirs[sdx++] = NULL; for (sdp = sdirs; *sdp && !found; sdp++) { #ifdef DEBUG_META_MODE DEBUG3(META, "%s: %d: looking for: %s\n", fname, lineno, *sdp); #endif - if (cached_stat(*sdp, &mst) == 0) { + if (cached_stat(*sdp, &cst) == 0) { found = 1; p = *sdp; } } if (found) { #ifdef DEBUG_META_MODE DEBUG3(META, "%s: %d: found: %s\n", fname, lineno, p); #endif - if (!S_ISDIR(mst.mst_mode) && - mst.mst_mtime > gn->mtime) { + if (!S_ISDIR(cst.cst_mode) && + cst.cst_mtime > gn->mtime) { DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); oodate = TRUE; - } else if (S_ISDIR(mst.mst_mode)) { + } else if (S_ISDIR(cst.cst_mode)) { /* Update the latest directory. */ cached_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... */ append_if_new(missingFiles, p); } } if (buf[0] == 'E') { /* previous latestdir is no longer relevant */ - strlcpy(latestdir, lcwd, sizeof(latestdir)); + 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 (cmdNode == NULL) { DEBUG2(META, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno); oodate = TRUE; } else { char *cmd = cmdNode->datum; 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; DEBUG2(META, "%s: %d: cannot compare command using .OODATE\n", fname, lineno); } (void)Var_Subst(cmd, gn, VARE_WANTRES|VARE_UNDEFERR, &cmd); /* TODO: handle errors */ 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 (p && + if (p != NULL && !hasOODATE && !(gn->type & OP_NOMETA_CMP) && - strcmp(p, cmd) != 0) { + (strcmp(p, cmd) != 0)) { DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); if (!metaIgnoreCMDs) oodate = TRUE; } free(cmd); cmdNode = cmdNode->next; } } else if (strcmp(buf, "CWD") == 0) { /* * Check if there are extra commands now * that weren't in the meta data file. */ if (!oodate && cmdNode != NULL) { DEBUG2(META, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno); oodate = TRUE; } CHECK_VALID_META(p); if (strcmp(p, cwd) != 0) { DEBUG4(META, "%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)) { DEBUG2(META, "%s: missing files: %s...\n", fname, (char *)missingFiles->first->datum); oodate = TRUE; } if (!oodate && !have_filemon && filemonMissing) { DEBUG1(META, "%s: missing filemon data\n", fname); oodate = TRUE; } } else { if (writeMeta && (metaMissing || (gn->type & OP_META))) { cp = NULL; /* if target is in .CURDIR we do not need a meta file */ if (gn->path && (cp = strrchr(gn->path, '/')) && cp > gn->path) { if (strncmp(curdir, gn->path, (size_t)(cp - gn->path)) != 0) { cp = NULL; /* not in .CURDIR */ } } - if (!cp) { + if (cp == NULL) { DEBUG1(META, "%s: required but missing\n", fname); oodate = TRUE; needOODATE = TRUE; /* assume the worst */ } } } Lst_Destroy(missingFiles, free); 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, GNode_VarAllsrc(gn), gn); } oodate_out: bmake_free(objdir_freeIt); 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) { meta_open_filemon(pbm); } else { pbm->mon_fd = -1; pbm->filemon = NULL; } #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, FD_CLOEXEC); (void)fcntl(childPipe[1], F_SETFD, FD_CLOEXEC); } void meta_compat_child(void) { meta_job_child(NULL); if (dup2(childPipe[1], 1) < 0 || dup2(1, 2) < 0) execDie("dup2", "pipe"); } void meta_compat_parent(pid_t child) { int outfd, metafd, maxfd, nfds; char buf[BUFSIZ+1]; fd_set readfds; meta_job_parent(NULL, child); close(childPipe[1]); /* child side */ outfd = childPipe[0]; #ifdef USE_FILEMON metafd = Mybm.filemon ? filemon_readfd(Mybm.filemon) : -1; #else metafd = -1; #endif maxfd = -1; if (outfd > maxfd) maxfd = outfd; if (metafd > maxfd) maxfd = metafd; while (outfd != -1 || metafd != -1) { FD_ZERO(&readfds); if (outfd != -1) { FD_SET(outfd, &readfds); } if (metafd != -1) { FD_SET(metafd, &readfds); } nfds = select(maxfd + 1, &readfds, NULL, NULL, NULL); if (nfds == -1) { if (errno == EINTR) continue; err(1, "select"); } if (outfd != -1 && FD_ISSET(outfd, &readfds)) do { /* XXX this is not line-buffered */ - ssize_t nread = read(outfd, buf, sizeof(buf) - 1); + ssize_t nread = read(outfd, buf, sizeof buf - 1); if (nread == -1) err(1, "read"); if (nread == 0) { close(outfd); outfd = -1; break; } fwrite(buf, 1, (size_t)nread, stdout); fflush(stdout); buf[nread] = '\0'; meta_job_output(NULL, buf, ""); } while (0); if (metafd != -1 && FD_ISSET(metafd, &readfds)) { if (meta_job_event(NULL) <= 0) metafd = -1; } } } #endif /* USE_META */ Index: head/contrib/bmake/metachar.h =================================================================== --- head/contrib/bmake/metachar.h (revision 367862) +++ head/contrib/bmake/metachar.h (revision 367863) @@ -1,48 +1,48 @@ -/* $NetBSD: metachar.h,v 1.11 2020/10/31 18:20:00 rillig Exp $ */ +/* $NetBSD: metachar.h,v 1.12 2020/11/10 00:32:12 rillig Exp $ */ /*- * Copyright (c) 2015 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Christos Zoulas. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef MAKE_METACHAR_H #define MAKE_METACHAR_H #include "make.h" extern unsigned char _metachar[]; #define is_shell_metachar(c) _metachar[(c) & 0x7f] -static inline MAKE_ATTR_UNUSED int +MAKE_INLINE int needshell(const char *cmd) { while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=') cmd++; return *cmd != '\0'; } #endif /* MAKE_METACHAR_H */ Index: head/contrib/bmake/missing/sys/cdefs.h =================================================================== --- head/contrib/bmake/missing/sys/cdefs.h (nonexistent) +++ head/contrib/bmake/missing/sys/cdefs.h (revision 367863) @@ -0,0 +1,186 @@ +/* $NetBSD: cdefs.h,v 1.18 1997/06/18 19:09:50 christos Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Berkeley Software Design, Inc. + * + * 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. + * + * @(#)cdefs.h 8.7 (Berkeley) 1/21/94 + */ + +#ifndef _SYS_CDEFS_H_ + +#if defined(NEED_HOST_CDEFS_H) +/* + * make sure we don't come past here again. + */ +#undef NEED_HOST_CDEFS_H +/* + * Some systems - notably linux, have sys/cdefs.h + * which is not really compatible with our's. + */ +#ifdef __GNUC__ +# include_next +#else +/* + * It sucks that we have to hard code a path like this. + * But systems that have a sys/cdefs.h that don't use gcc + * should be few. + */ +# include "/usr/include/sys/cdefs.h" +#endif +/* + * We are about to [re]define these + */ +#undef __P +#undef _SYS_CDEFS_H_ +#endif + +#define _SYS_CDEFS_H_ + +#ifdef NetBSD +#include +#endif + +#if defined(__cplusplus) +# ifndef __BEGIN_DECLS +# define __BEGIN_DECLS extern "C" { +# endif +# ifndef __END_DECLS +# define __END_DECLS }; +# endif +#else +# ifndef __BEGIN_DECLS +# define __BEGIN_DECLS +# endif +# ifndef __END_DECLS +# define __END_DECLS +# endif +#endif + +/* + * The __CONCAT macro is used to concatenate parts of symbol names, e.g. + * with "#define OLD(foo) __CONCAT(old,foo)", OLD(foo) produces oldfoo. + * The __CONCAT macro is a bit tricky -- make sure you don't put spaces + * in between its arguments. __CONCAT can also concatenate double-quoted + * strings produced by the __STRING macro, but this only works with ANSI C. + */ +#if defined(__STDC__) || defined(__cplusplus) +#define __P(protos) protos /* full-blown ANSI C */ +#ifndef __CONCAT +#define __CONCAT(x,y) x ## y +#endif +#define __STRING(x) #x + +#define __const const /* define reserved names to standard */ +#define __signed signed +#define __volatile volatile +#if defined(__cplusplus) +#define __inline inline /* convert to C++ keyword */ +#else +#ifndef __GNUC__ +#define __inline /* delete GCC keyword */ +#endif /* !__GNUC__ */ +#endif /* !__cplusplus */ + +#else /* !(__STDC__ || __cplusplus) */ +#define __P(protos) () /* traditional C preprocessor */ +#define __CONCAT(x,y) x/**/y +#define __STRING(x) "x" + +#ifndef __GNUC__ +#define __const /* delete pseudo-ANSI C keywords */ +#define __inline +#define __signed +#define __volatile +#endif /* !__GNUC__ */ + +/* + * In non-ANSI C environments, new programs will want ANSI-only C keywords + * deleted from the program and old programs will want them left alone. + * Programs using the ANSI C keywords const, inline etc. as normal + * identifiers should define -DNO_ANSI_KEYWORDS. + */ +#ifndef NO_ANSI_KEYWORDS +#define const __const /* convert ANSI C keywords */ +#define inline __inline +#define signed __signed +#define volatile __volatile +#endif /* !NO_ANSI_KEYWORDS */ +#endif /* !(__STDC__ || __cplusplus) */ + +/* + * GCC1 and some versions of GCC2 declare dead (non-returning) and + * pure (no side effects) functions using "volatile" and "const"; + * unfortunately, these then cause warnings under "-ansi -pedantic". + * GCC2 uses a new, peculiar __attribute__((attrs)) style. All of + * these work for GNU C++ (modulo a slight glitch in the C++ grammar + * in the distribution version of 2.5.5). + */ +#if !defined(__GNUC__) || __GNUC__ < 2 || \ + (__GNUC__ == 2 && __GNUC_MINOR__ < 5) +#define __attribute__(x) /* delete __attribute__ if non-gcc or gcc1 */ +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) +#define __dead __volatile +#define __pure __const +#endif +#endif + +#ifdef sun386 +# define __attribute__(x) +#endif + +#ifdef __KPRINTF_ATTRIBUTE__ +#define __kprintf_attribute__(a) __attribute__(a) +#else +#define __kprintf_attribute__(a) +#endif + +/* Delete pseudo-keywords wherever they are not available or needed. */ +#ifndef __dead +#define __dead +#define __pure +#endif + +#define __IDSTRING(name,string) \ + static const char name[] __attribute__((__unused__)) = string + +#ifndef __RCSID +#define __RCSID(s) __IDSTRING(rcsid,s) +#endif + +#ifndef __COPYRIGHT +#define __COPYRIGHT(s) __IDSTRING(copyright,s) +#endif + +#endif /* !_SYS_CDEFS_H_ */ Property changes on: head/contrib/bmake/missing/sys/cdefs.h ___________________________________________________________________ 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/ChangeLog =================================================================== --- head/contrib/bmake/mk/ChangeLog (revision 367862) +++ head/contrib/bmake/mk/ChangeLog (revision 367863) @@ -1,1709 +1,1716 @@ +2020-11-06 Simon J Gerraty + + * install-mk (MK_VERSION): 20201106 + + * meta.autodep.mk: use OBJ_EXTENSIONS rather than hardcode sed + args to tweak extensions for local deps. + 2020-11-01 Simon J Gerraty * install-mk (MK_VERSION): 20201101 * dirdeps.mk: most leaf makefiles are not suitable for building dirdeps.cache so if RELDIR is not "." use dirdeps.mk 2020-10-28 Simon J Gerraty * install-mk (MK_VERSION): 20201028 * dirdeps.mk: if we don't have :range use equivalent of M_RANGE when building dirdeps.cache for leaf directory use -f dirdeps.mk * sys.vars.mk: add M_JOT and M_RANGE 2020-10-01 Simon J Gerraty * install-mk (MK_VERSION): 20201001 * meta2deps.{py,sh}: throw an error if we don't see filemon version 2020-09-09 Simon J Gerraty * install-mk (MK_VERSION): 20200909 * dirdeps-cache-update.mk: use cache_update_dirdep as guard target 2020-08-26 Simon J Gerraty * dirdeps.mk: ensure we cannot confuse a static cache for dynamic (even more rare that use of static cache is playing clever tricks with it) 2020-08-16 Simon J Gerraty * dirdeps-cache-update.mk: allow MK_STATIC_DIRDEPS_CACHE_UPDATE_IMMEDIATE to control when we actually update STATIC_DIRDEPS_CACHE. * stage-install.sh: create dest directory if needed before running install(1) 2020-08-10 Simon J Gerraty * dirdeps-targets.mk: include Makefile.dirdeps.options * dirdeps.mk: use _TARGETS if defined for DIRDEPS_CACHE 2020-08-09 Simon J Gerraty * dirdeps.mk: default BUILD_DIRDEPS_MAKEFILE to empty * dirdeps-cache-update.mk: building parallel cache update under the context of dirdeps-cached would be ideal, but is problematic, so it runs as a sibling. Use cache-built target to ensure we wait for it to complete if necessary. 2020-08-06 Simon J Gerraty * install-mk (MK_VERSION): 20200806 * dirdeps-options: allow TARGET_SPEC to affect option values. Use DIRDEPS_OPTIONS_QUALIFIER_LIST before using bare MK_* * dirdeps-targets.mk: check for MK_STATIC_DIRDEPS_CACHE defined before looking for STATIC_DIRDEPS_CACHE 2020-08-05 Simon J Gerraty * host-target.mk: Darwin use MACHINE for HOST_ARCH too * dirdeps-options.mk: improve debug output 2020-07-22 Simon J Gerraty * dirdeps.mk: set and export DYNAMIC_DIRDEPS_CACHE for use by dirdeps-cache-update.mk * dirdeps-targets.mk: set and export STATIC_DIRDEPS_CACHE for use by dirdeps-cache-update.mk even if we don't use it. * dirdeps-cache-update.mk: we only need worry about the background update case, with the above, the update from DIRDEPS_CACHE is simple. * meta2deps.py: R 1234 . is not interesting 2020-07-20 Simon J Gerraty * sys.mk: default MK_STATIC_DIRDEPS_CACHE from MK_DIRDEPS_CACHE * dirdeps-options.mk: do not :tu DIRDEPS_OPTIONS allows use of lower case for pseudo options. * dirdeps-cache-update.mk: magic to deal with STATIC_DIRDEPS_CACHE 2020-07-18 Simon J Gerraty * dirdeps-targets.mk: Look for Makefile.dirdeps.cache which allows us to have a static cache for expensive targets. Use -DWITHOUT_STATIC_DIRDEPS_CACHE -DWITH_DIRDEPS_CACHE to regenerate the dirdeps.cache it is a copy of. 2020-07-17 Simon J Gerraty * Get rid of BUILD_AT_LEVEL0, MK_DIRDEPS_BUILD makes more sense. 2020-07-16 Simon J Gerraty * dirdeps.mk (DIRDEP_LOADAVG_REPORT): make it easy to record load averages at intervals during build. 2020-07-15 Simon J Gerraty * install-mk (MK_VERSION): 20200715 * dirdeps.mk: tweak Checking line to make matching Finished lines for post-build analysis easier. * meta.autodep.mk: use !defined(WITHOUT_META_STATS) * progs.mk: avoid prog.mk outputting multiple Finished lines 2020-07-11 Simon J Gerraty * dirdeps.mk: further optimize dirdeps.cache generate a DIRDEPS.${.TARGET} list for other purposes and improve the layout. 2020-07-10 Simon J Gerraty * dirdeps.mk: optimize content of dirdeps.cache 2020-06-28 Simon J Gerraty * sys/*.mk: make it easier for local*sys.mk to customize by using ?= 2020-06-22 Simon J Gerraty * gendirdeps.mk (LOCAL_DEPENDS_GUARD): if we don't build at level 0 it is much safer to guard local depends with a simple check for .MAKE.LEVEL > 0 2020-06-10 Simon J Gerraty * install-mk (MK_VERSION): 20200610 * mkopt.sh: this needs posix shell so #!/bin/sh should be ok 2020-06-06 Simon J Gerraty * install-mk (MK_VERSION): 20200606 * dirdeps-targets.mk: allow for filtering of .TARGETS * meta2deps.py: fix bug in processing 'L'ink and 'M'ove entries - and we don't care about 'W'rite entries. Also ignore absolute paths that do not exist. 2020-05-25 Simon J Gerraty * install-mk (MK_VERSION): 20200525 * init.mk: expand and simplify handling of qualified vars like CPPFLAGS.${.TARGET:T} 2020-05-15 Simon J Gerraty * install-mk (MK_VERSION): 20200515 * dirdeps.mk: set _debug_* earlier and allow passing -d* flags to submake when building DIRDEPS_CACHE 2020-05-09 Simon J Gerraty * whats.mk: more easily extensible 2020-05-02 Simon J Gerraty * whats.mk: greatly simplify by adding what.c to SRCS 2020-05-01 Simon J Gerraty * whats.mk: for libs take care how we add to *OBJS * lib.mk: : works better with whats.mk 2020-04-25 Simon J Gerraty * install-mk (MK_VERSION): 20200420 * meta.stage.mk: it is not a STAGE_CONFLICT if some-target.dirdep contains the same ${RELDIR} and a prefix match for our ${TARGET_SPEC} 2020-04-16 Simon J Gerraty * install-mk (MK_VERSION): 20200416 * sys/*.mk: set MAKE_SHELL rather than SHELL so as not to interfere with user env. * sys.mk: default MAKE_SHELL to sh and SHELL to MAKE_SHELL * autodep.mk: use MAKE_SHELL. 2019-11-21 Simon J Gerraty * gendirdeps.mk: clear .SUFFIXES to avoid a lot of wasted effort, and unexport _meta_files when no longer needed as it consumes space we need for command line. 2019-11-11 Simon J Gerraty * dirdeps.mk _DIRDEP_USE: use DIRDEP_DIR and add DIRDEP_USE_PRELUDE at start - facilitates job distribution 2019-10-04 Simon J Gerraty * dirdeps-targets.mk: Use TARGET_SPEC_LAST_LIST defaults to ${${TARGET_SPEC_VARS:[-1]}_LIST} to match valid TARGET_SPEC qualified depend files. 2019-10-02 Simon J Gerraty * dirdeps-targets.mk: encapsulate logic for finding top-level targets to set initial DIRDEPS for DIRDEPS_BUILD 2019-09-27 Simon J Gerraty * install-mk (MK_VERSION): 20190911 * compiler.mk: set COMPILER_TYPE 2019-07-17 Simon J Gerraty * install-mk (MK_VERSION): 20190704 * sys/Darwin.mk: support for Objective-C and clang 2019-05-30 Simon J Gerraty * dirdeps.mk: avoid insanely long command line when generating cache 2019-05-23 Simon J Gerraty * install-mk (MK_VERSION): 20190505 * whats.mk: handle corner case SHLIB defined but not LIB 2018-09-19 Simon J Gerraty * install-mk (MK_VERSION): 20180919 * dirdeps-options.mk: .undef cannot handle var that expands to more than one var. 2018-07-08 Simon J Gerraty * meta.stage.mk: allow wildcards in STAGE_FILES.* etc. 2018-06-01 Simon J Gerraty * meta.autodep.mk: export META_FILES to avoid command line limit * gendirdeps.mk: if we have lots of .meta files put them in an @list 2018-05-28 Simon J Gerraty * dirdeps-options.mk: use local.dirdeps-options.mk not local.dirdeps-option.mk 2018-04-20 Simon J Gerraty * install-mk (MK_VERSION): 20180420 * dirdeps.mk: include local.dirdeps-build.mk when .MAKE.LEVEL > 0 ie. we are building something. 2018-04-14 Simon J Gerraty * FILES: add dirdeps-options.mk to deal with optional DIRDEPS. 2018-04-05 Simon J Gerraty * install-mk (MK_VERSION): 20180405 * ldorder.mk: describe how to use LDORDER_EXTERN_BARRIER if needed. 2018-01-18 Simon J Gerraty * install-mk (MK_VERSION): 20180118 * ldorder.mk: let make compute correct link order 2017-12-12 Simon J Gerraty * install-mk (MK_VERSION): 20171212 * gendirdeps.mk: guard against bogus entries in GENDIRDEPS_FILTER 2017-11-14 Simon J. Gerraty * install-mk (MK_VERSION): 20171111 * lib.mk: ensure META_NOECHO is set 2017-10-25 Simon J. Gerraty * Allow for host32 on rare occasions. 2017-10-18 Simon J. Gerraty * install-mk (MK_VERSION): 20171018 * whats.mk: include what_thing in what_uuid to avoid problem when building multiple apps in the same directory. 2017-08-12 Simon J. Gerraty * install-mk (MK_VERSION): 20170812 * autoconf.mk: Use CONFIGURE_DEPS so Makefile can add dependencies for config.recheck and config.gen 2017-06-30 Simon J. Gerraty * install-mk (MK_VERSION): 20170630 * meta.stage.mk: avoid triggering stage_* targets with nothing to do. 2017-05-23 Simon J. Gerraty * meta2deps.py: take special care of '..' 2017-05-15 Simon J. Gerraty * install-mk (MK_VERSION): 20170515 * dirdeps.mk (DEP_EXPORT_VARS): on rare occasions it is useful/necessary for a Makefile.depend file to export some knobs. This is complicated when we are doing DIRDEPS_CACHE, so we will handle export of any variables listed in DEP_EXPORT_VARS. 2017-05-08 Simon J. Gerraty * install-mk (MK_VERSION): 20170505 * meta2deps.py: fix botched indenation. 2017-05-05 Simon J. Gerraty * sys/*.mk: Remove setting of MAKE it is unnecessary and in many cases wrong (basname rather than full path) * scripts.mk (SCRIPTSGROUPS): make this more like files.mk and inc.mk * init.mk: define realbuild to simplify logic in {lib,prog}.mk etc 2017-05-01 Simon J. Gerraty * install-mk (MK_VERSION): 20170501 * doc.mk: fix typo in DOC_INSTALL_OWN * inc.mk: handle INCGROUPS similar to freebsd * files.mk: add something for files too * add staging logic to lib.mk prog.mk etc. 2017-04-24 Simon J. Gerraty * install-mk (MK_VERSION): 20170424 * dirdeps.mk: set NO_DIRDEPS when bootstrapping. also target of bootstrap-this when sed is needed should be ${_want:T} 2017-04-18 Simon J. Gerraty * install-mk (MK_VERSION): 20170418 * auto.obj.mk: if using MAKEOBJDIRPREFIX check if it is a prefix match for .CURDIR - in which case .CURDIR *is* __objdir. 2017-04-01 Simon J. Gerraty * install-mk (MK_VERSION): 20170401 * meta2deps.py: add is_src so we can check if obj dependency is also a src dependency. 2017-03-26 Simon J. Gerraty * install-mk (MK_VERSION): 20170326 * meta.stage.mk: do nothing if NO_STAGING is defined. 2017-03-24 Simon J. Gerraty * auto.obj.mk: handle the case of __objdir=obj or obj.${MACHINE} etc. 2017-03-18 Simon J. Gerraty * mkopt.sh: treat WITH_*=NO like no; ie. WITHOUT_* 2017-03-01 Simon J. Gerraty * install-mk (MK_VERSION): 20170301 * dirdeps.mk (_build_all_dirs): update this outside test for empty DIRDEPS. * meta.stage.mk: allow multiple inclusion to the extent it makes sense. 2017-02-14 Simon J. Gerraty * prog.mk (install_links): depends on realinstall 2017-02-12 Simon J. Gerraty * install-mk (MK_VERSION): 20170212 * dpadd.mk: avoid applying :T:R twice to DPLIBS entries 2017-01-30 Simon J. Gerraty * install-mk (MK_VERSION): 20170130 * dirdeps.mk: use :range if we can. * sys.vars.mk: provide M_cmpv if MAKE_VERSION >= 20170130 * meta2deps.py: clean paths without using realpath() where possible. fix sort_unique. 2016-12-12 Simon J. Gerraty * install-mk (MK_VERSION): 20161212 * meta2deps.py: set pid_cwd[pid] when we process 'C'hdir, rather than when we detect pid change. 2016-12-07 Simon J. Gerraty * install-mk (MK_VERSION): 20161207 * meta.stage.mk: add stage_as_and_symlink for staging packages. We build foo.tgz stage_as foo-${VERSION}.tgz but want to be able to use foo.tgz to reference the latest staged version - so we make foo.tgz a symlink to it. Using a target to do both operations ensures we stay in sync. 2016-11-26 Simon J. Gerraty * install-mk (MK_VERSION): 20161126 * dirdeps.mk: set DIRDEPS_CACHE before we include local.dirdeps.mk so it can add dependencies. 2016-10-10 Simon J. Gerraty * dirdeps.mk: set DEP_* before we expand .MAKE.DEPENDFILE_PREFERENCE do that they can influence the result correctly. * dirdeps.mk (${DIRDEPS_CACHE}): make sure we pass on TARGET_SPEC * dirdeps.mk: Add ONLY_TARGET_SPEC_LIST and NOT_TARGET_SPEC_LIST similar to ONLY_MACHINE_LIST and NOT_MACHINE_LIST 2016-10-05 Simon J. Gerraty * dirdeps.mk: remove dependence on jot (normal situations anyway). Before we read another Makefile.depend* set DEP_* vars from _DEP_TARGET_SPEC in case it uses any of them with := When bootstrapping, trim any ,* from extention of chosen _src Makefile.depend* to get the machine value we subst for. 2016-09-30 Simon J. Gerraty * dirdeps.mk: use TARGET_SPEC_VARS to qualify components added to DEP_SKIP_DIR and DEP_DIRDEPS_FILTER * sys.mk: extract some bits to sys.{debug,vars}.mk for easier re-use by others. 2016-09-23 Simon Gerraty * lib.mk: Use ${PICO} for extension for PIC objects. default to .pico (like NetBSD) safe on case insensitive filesystem. 2016-08-19 Simon J. Gerraty * meta.sys.mk (META_COOKIE_TOUCH): use ${.OBJDIR}/${.TARGET:T} as default 2016-08-15 Simon J. Gerraty * install-mk (MK_VERSION): 20160815 * dirdeps.mk (.MAKE.META.IGNORE_FILTER): set filter to only consider Makefile.depend* when checking if DIRDEPS_CACHE is up-to-date. 2016-08-13 Simon J. Gerraty * meta.sys.mk (.MAKE.META.IGNORE_PATHS): in meta mode we can ignore the mtime of makefiles 2016-08-02 Simon J. Gerraty * install-mk (MK_VERSION): 20160802 * lib.mk (libinstall): depends on beforinstall * prog.mk (proginstall): depends on beforinstall patch from Lauri Tirkkonen * dirdeps.mk (bootstrap): When bootstrapping; creat .MAKE.DEPENDFILE_DEFAULT and allow additional filtering via .MAKE.DEPENDFILE_BOOTSTRAP_SED * dirdeps.mk: move some comments to where they make sense. 2016-07-27 Simon J. Gerraty * dirdeps.mk (DIRDEPS_CACHE): no dirname. 2016-06-02 Simon J. Gerraty * install-mk (MK_VERSION): 20160602 * meta.autodep.mk: when passing META_FILES to gendirdeps.mk do not apply :T to META_XTRAS patch from Bryan Drewery at FreeBSD.org. 2016-05-30 Simon J. Gerraty * install-mk (MK_VERSION): 20160530 * meta.stage.mk: we assume ${CLEANFILES} gets .NOPATH make it so. 2016-05-12 Simon J. Gerraty * install-mk (MK_VERSION): 20160512 * dpadd.mk: always include local.dpadd.mk if it exists remove some things that better belong in local.dpadd.mk skip INCLUDES_* for staged libs unless SRC_* defined. * own.mk: add INCLUDEDIR 2016-04-18 Simon J. Gerraty * dirdeps.mk: when doing -f dirdeps.mk if target suppies no TARGET_MACHINE - :E will be empty or match part of path, use ${MACHINE} 2016-04-07 Simon J. Gerraty * meta.autodep.mk: issue a warning if UPDATE_DEPENDFILE=NO due to NO_FILEMON_COOKIE * dirdeps.mk: move the logic that allows for make -f dirdeps.mk some/dir.${TARGET_SPEC} inside the check for !target(_DIRDEP_USE) 2016-04-04 Simon J. Gerraty * Use <> when including local*.mk and others which may exist elsewhere so that user can better control what they get. * meta.autodep.mk (NO_FILEMON_COOKIE): create a cookie if we ever build dir with nofilemon so that UPDATE_DEPENDFILE will be forced to NO until cleaned. 2016-04-01 Simon J. Gerraty * install-mk (MK_VERSION): 20160401 * meta2deps.py: fix old print statement when debugging. * gendirdeps.mk: META2DEPS_CMD append M2D_EXCLUDES with -X patch from Bryan Drewery 2016-03-22 Simon J. Gerraty * install-mk (MK_VERSION): 20160317 (St. Pats) * warnings.mk: g++ does not like -Wimplicit * sys.mk sys/*.mk lib.mk prog.mk: use CXX_SUFFIXES to handle the pelthora of common suffixes for C++ * lib.mk: use .So for shared objects 2016-03-15 Simon J. Gerraty * install-mk (MK_VERSION): 20160315 * meta.stage.mk (LN_CP_SCRIPT): do not ln(1) if we have to chmod(1) normally only applies to scripts. * dirdeps.mk: NO_DIRDEPS_BELOW to supress DIRDEPS below RELDIR as well as outside it. 2016-03-10 Simon J. Gerraty * install-mk (MK_VERSION): 20160310 * dirdeps.mk: use targets rather than a list to track DIRDEPS that we have processed; the list gets very inefficient as number of DIRDEPS gets large. * sys.dependfile.mk: fix comment wrt MACHINE * meta.autodep.mk: ignore staged DPADDs when bootstrapping. patch from Bryan Drewery 2016-03-02 Simon J. Gerraty * meta2deps.sh: don't ignore subdirs. patch from Bryan Drewery 2016-02-26 Simon J. Gerraty * install-mk (MK_VERSION): 20160226 * gendirdeps.mk: mark _DEPENDFILE .NOMETA 2016-02-20 Simon J. Gerraty * dirdeps.mk: we shouldn't normally include .depend but if we do use .dinclude if we can. 2016-02-18 Simon J. Gerraty * install-mk (MK_VERSION): 20160218 * sys.clean-env.mk: with recent change to Var_Subst() we cannot use the '$$' trick, but .export-literal does the job we need. * auto.dep.mk: make use .dinclude if we can. 2016-02-05 Simon J. Gerraty * dirdeps.mk: Add _build_all_dirs such that local.dirdeps.mk can add fully qualified dirs to it. These will be built normally but the current DEP_RELDIR will not depend on then (to avoid cycles). This makes it easy to hook things like unit-tests into build. 2016-01-21 Simon J. Gerraty * dirdeps.mk: add bootstrap-empty 2015-12-12 Simon J. Gerraty * install-mk (MK_VERSION): 20151212 * auto.obj.mk: do not require MAKEOBJDIRPREFIX to exist. only apply :tA to __objdir when comparing to .OBJDIR 2015-11-14 Simon J. Gerraty * install-mk (MK_VERSION): 20151111 * meta.sys.mk: include sys.dependfile.mk * sys.mk (OPTIONS_DEFAULT_NO): use options.mk to set MK_AUTO_OBJ and MK_DIRDEPS_BUILD include local.sys.env.mk early include local.sys.mk later * own.mk (OPTIONS_DEFAULT_NO): AUTO_OBJ etc moved to sys.mk 2015-11-13 Simon J. Gerraty * meta.sys.mk (META_COOKIE_TOUCH): add ${META_COOKIE_TOUCH} to the end of scripts to touch cookie * meta.stage.mk: stage_libs should ignore SYMLINKS. 2015-10-23 Simon J. Gerraty * install-mk (MK_VERSION): 20151022 * sys.mk: BSD/OS does not have 'type' as a shell builtin. 2015-10-20 Simon J. Gerraty * install-mk (MK_VERSION): 20151020 * dirdeps.mk: Add logic for make -f dirdeps.mk some/dir.${TARGET_SPEC} 2015-10-14 Simon J. Gerraty * install-mk (MK_VERSION): 20151010 2015-10-02 Simon J. Gerraty * meta.stage.mk: use staging: ${STAGE_TARGETS:... to have stage_lins run last in non-jobs mode. Use .ORDER only for jobs mode. 2015-09-02 Simon J. Gerraty * rst2htm.mk: allow for per target flags etc. 2015-09-01 Simon J. Gerraty * install-mk (MK_VERSION): 20150901 * doc.mk: create dir if needed use DOC_INSTALL_OWN 2015-06-15 Simon J. Gerraty * install-mk (MK_VERSION): 20150615 * auto.obj.mk: allow use of MAKEOBJDIRPREFIX too. Follow make's normal precedence rules. * gendirdeps.mk: allow customization of the header. eg. for FreeBSD: GENDIRDEPS_HEADER= echo '\# ${FreeBSD:L:@v@$$$v$$ @:M*F*}'; * meta.autodep.mk: ignore dirdeps.cache* * meta.stage.mk: when bootstrapping options it can be handy to throw warnings rather than errors for staging conflicts. * meta.sys.mk: include local.meta.sys.mk for customization 2015-06-06 Simon J. Gerraty * install-mk (MK_VERSION): 20150606 * dirdeps.mk: don't rely on manually maintained Makefile.depend to set DEP_RELDIR and reset DIRDEPS. By setting DEP_RELDIR ourselves we can skip :tA * gendirdeps.mk: skip setting DEP_RELDIR. 2015-05-24 Simon J. Gerraty * dirdeps.mk: avoid wildcards like make(bootstrap*) 2015-05-20 Simon J. Gerraty * install-mk (MK_VERSION): 20150520 * dirdeps.mk: when we are building dirdeps cache file we *want* meta_oodate to look at all the Makefile.depend files, so set .MAKE.DEPENDFILE to something that won't match. * meta.stage.mk: for STAGE_AS_* basename of file may not be unique so first use absolute path as key. Also skip staging at level 0. 2015-04-30 Simon J. Gerraty * install-mk (MK_VERSION): 20150430 * dirdeps.mk: fix _count_dirdeps for non-cache case. 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 SUBDIRDEPS_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/install-mk =================================================================== --- head/contrib/bmake/mk/install-mk (revision 367862) +++ head/contrib/bmake/mk/install-mk (revision 367863) @@ -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.183 2020/11/02 16:34:12 sjg Exp $ +# $Id: install-mk,v 1.184 2020/11/08 05:47:56 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=20201101 +MK_VERSION=20201106 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 files inc 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/meta.autodep.mk =================================================================== --- head/contrib/bmake/mk/meta.autodep.mk (revision 367862) +++ head/contrib/bmake/mk/meta.autodep.mk (revision 367863) @@ -1,316 +1,317 @@ -# $Id: meta.autodep.mk,v 1.52 2020/07/18 05:57:57 sjg Exp $ +# $Id: meta.autodep.mk,v 1.53 2020/11/08 05:47:56 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 PICO?= .pico .if defined(SRCS) # it would be nice to be able to query .SUFFIXES OBJ_EXTENSIONS+= .o .po .lo ${PICO} # 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} _OBJDIR ?= ${.OBJDIR} _OBJTOP ?= ${OBJTOP} _OBJROOT ?= ${OBJROOT:U${_OBJTOP}} _DEPENDFILE := ${_CURDIR}/${.MAKE.DEPENDFILE:T} .if ${.MAKE.LEVEL} > 0 # do not allow auto update if we ever built this dir without filemon NO_FILEMON_COOKIE = .nofilemon CLEANFILES += ${NO_FILEMON_COOKIE} .if ${.MAKE.MODE:Uno:Mnofilemon} != "" UPDATE_DEPENDFILE = NO all: ${NO_FILEMON_COOKIE} ${NO_FILEMON_COOKIE}: .NOMETA @echo UPDATE_DEPENDFILE=NO > ${.TARGET} .elif exists(${NO_FILEMON_COOKIE}) UPDATE_DEPENDFILE = NO .warning ${RELDIR} built with nofilemon; UPDATE_DEPENDFILE=NO .endif .endif .if ${.MAKE.LEVEL} == 0 UPDATE_DEPENDFILE = NO .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:Uno:Mmeta*} == "" || ${.MAKE.MODE:Uno:M*read*} != "" UPDATE_DEPENDFILE = no .endif .if ${DEBUG_AUTODEP:Uno:@m@${RELDIR:M$m}@} != "" .info ${_DEPENDFILE:S,${SRCTOP}/,,} update=${UPDATE_DEPENDFILE} .endif .if ${UPDATE_DEPENDFILE:tl} == "yes" # sometimes we want .meta files generated to aid debugging/error detection # but do not want to consider them for dependencies # for example the result of running configure # just make sure this is not empty META_FILE_FILTER ?= N.meta # never consider these META_FILE_FILTER += Ndirdeps.cache* .if !empty(DPADD) # if we have any non-libs in DPADD, # they probably need to be paid attention to .if !empty(DPLIBS) FORCE_DPADD = ${DPADD:${DPLIBS:${M_ListToSkip}}:${DPADD_LAST:${M_ListToSkip}}} .else _nonlibs := ${DPADD:T:Nlib*:N*include} .if !empty(_nonlibs) FORCE_DPADD += ${_nonlibs:@x@${DPADD:M*/$x}@} .endif .endif .endif .if !make(gendirdeps) .END: gendirdeps .endif # if we don't have OBJS, then .depend isn't useful .if !target(.depend) && (!empty(OBJS) || ${.ALLTARGETS:M*.o} != "") # some makefiles and/or targets contain # circular dependencies if you dig too deep # (as meta mode is apt to do) # so we provide a means of suppressing them. # the input to the loop below is target: dependency # with just one dependency per line. # Also some targets are not really local, or use random names. # Use local.autodep.mk to provide local additions! SUPPRESS_DEPEND += \ ${SB:S,/,_,g}* \ *:y.tab.c \ *.c:*.c \ *.h:*.h .NOPATH: .depend # we use ${.MAKE.META.CREATED} to trigger an update but # we process using ${.MAKE.META.FILES} # the double $$ defers initial evaluation # if necessary, we fake .po dependencies, just so the result # in Makefile.depend* is stable # The current objdir may be referred to in various ways OBJDIR_REFS += ${.OBJDIR} ${.OBJDIR:tA} ${_OBJDIR} ${RELOBJTOP}/${RELDIR} _depend = .depend # it would be nice to be able to get .SUFFIXES as ${.SUFFIXES} # we actually only care about the .SUFFIXES of files that might be # generated by tools like yacc. DEPEND_SUFFIXES += .c .h .cpp .hpp .cxx .hxx .cc .hh .depend: .NOMETA $${.MAKE.META.CREATED} ${_this} @echo "Updating $@: ${.OODATE:T:[1..8]}" @egrep -i '^R .*\.(${DEPEND_SUFFIXES:tl:O:u:S,^.,,:ts|})$$' /dev/null ${.MAKE.META.FILES:T:O:u:${META_FILE_FILTER:ts:}:M*o.meta} | \ sed -e 's, \./, ,${OBJDIR_REFS:O:u:@d@;s, $d/, ,@};/\//d' \ -e 's,^\([^/][^/]*\).meta...[0-9]* ,\1: ,' | \ sort -u | \ while read t d; do \ case "$$d:" in $$t) continue;; esac; \ case "$$t$$d" in ${SUPPRESS_DEPEND:U.:O:u:ts|}) continue;; esac; \ echo $$t $$d; \ done > $@.${.MAKE.PID} @case "${.MAKE.META.FILES:T:M*.po.*}" in \ *.po.*) mv $@.${.MAKE.PID} $@;; \ *) { cat $@.${.MAKE.PID}; \ - sed 's,\${PICO}:,.o:,;s,\.o:,.po:,' $@.${.MAKE.PID}; } | sort -u > $@; \ + sed ${OBJ_EXTENSIONS:N.o:N.po:@o@-e 's,\$o:,.o:,'@} \ + -e '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}*:N${STAGE_ROOT:U${_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, ${PICO}.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 .if ${UPDATE_DEPENDFILE} == "yes" .-include <${.CURDIR}/${.MAKE.DEPENDFILE_PREFIX}.options> .endif .if !empty(GENDIRDEPS_FILTER) .export GENDIRDEPS_FILTER .endif # export to avoid blowing command line limit META_FILES := ${META_XTRAS:U:O:u} ${META_FILES:U:T:O:u:${META_FILE_FILTER:ts:}} .export META_FILES .endif # we might have .../ in MAKESYSPATH _makesyspath:= ${_PARSEDIR} ${_DEPENDFILE}: ${_depend} ${.PARSEDIR}/gendirdeps.mk ${META2DEPS} $${.MAKE.META.CREATED} @echo Checking $@: ${.OODATE:T:[1..8]} @(cd . && ${GENDIRDEPS_ENV} \ SKIP_GENDIRDEPS='${SKIP_GENDIRDEPS:O:u}' \ DPADD='${FORCE_DPADD:O:u}' ${_gendirdeps_mutex} \ MAKESYSPATH=${_makesyspath} \ ${.MAKE} -f gendirdeps.mk RELDIR=${RELDIR} _DEPENDFILE=${_DEPENDFILE}) @test -s $@ && touch $@; : .endif .endif .endif .if ${_bootstrap_dirdeps} == "yes" DIRDEPS+= ${RELDIR}.${TARGET_SPEC:U${MACHINE}} # 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(WITHOUT_META_STATS) && ${.MAKE.LEVEL} > 0 .END: _reldir_finish .ERROR: _reldir_failed .endif .endif Index: head/contrib/bmake/mk/meta2deps.sh =================================================================== --- head/contrib/bmake/mk/meta2deps.sh (revision 367862) +++ head/contrib/bmake/mk/meta2deps.sh (revision 367863) @@ -1,418 +1,418 @@ #!/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/include # $SB/obj-i386/bsd/lib/csu/i386 # $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.14 2020/10/02 03:11:17 sjg Exp $ +# $Id: meta2deps.sh,v 1.15 2020/11/08 06:31:08 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" } error() { echo "ERROR: $@" >&2 exit 1 } 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= 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,;/^[CREFLMV] /!d' -e "s,',,g" | $_excludes | ( version=no 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} case "$verion" in no) ;; # ignore 0) error "no filemon data";; *) ;; esac - version=0 + version=0 continue ;; $pid,$pid) ;; *) case "$lpid" in "") ;; *) eval ldir_$lpid=$ldir;; esac eval ldir=\${ldir_$pid:-$CWD} cwd=\${cwd_$pid:-$CWD} lpid=$pid ;; esac case "$op,$path" in V,*) version=$path; continue;; 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 eval cwd_$pid=$cwd continue ;; F,*) # $path is new pid 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:-.}|"") 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 case "$version" in 0) error "no filemon data";; esac ) || exit 1 _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/nonints.h =================================================================== --- head/contrib/bmake/nonints.h (revision 367862) +++ head/contrib/bmake/nonints.h (revision 367863) @@ -1,315 +1,334 @@ -/* $NetBSD: nonints.h,v 1.149 2020/11/01 00:24:57 rillig Exp $ */ +/* $NetBSD: nonints.h,v 1.162 2020/11/16 21:48:18 rillig 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 */ void Arch_Init(void); void Arch_End(void); Boolean Arch_ParseArchive(char **, GNodeList *, GNode *); void Arch_Touch(GNode *); void Arch_TouchLib(GNode *); -time_t Arch_MTime(GNode *); -time_t Arch_MemMTime(GNode *); +void Arch_UpdateMTime(GNode *gn); +void Arch_UpdateMemberMTime(GNode *gn); void Arch_FindLib(GNode *, SearchPath *); Boolean Arch_LibOODate(GNode *); Boolean Arch_IsLib(GNode *); /* compat.c */ int Compat_RunCommand(const char *, GNode *); void Compat_Run(GNodeList *); void Compat_Make(GNode *, GNode *); /* cond.c */ CondEvalResult Cond_EvalCondition(const char *, Boolean *); CondEvalResult Cond_EvalLine(const char *); void Cond_restore_depth(unsigned int); unsigned int Cond_save_depth(void); /* for.c */ int For_Eval(const char *); Boolean For_Accum(const char *); void For_Run(int); /* job.c */ #ifdef WAIT_T void JobReapChild(pid_t, WAIT_T, Boolean); #endif /* main.c */ +Boolean GetBooleanVar(const char *, Boolean); void Main_ParseArgLine(const char *); void MakeMode(const 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; void Finish(int) MAKE_ATTR_DEAD; int eunlink(const char *); void execDie(const char *, const char *); char *getTmpdir(void); -Boolean s2Boolean(const char *, Boolean); -Boolean getBoolean(const char *, Boolean); +Boolean ParseBoolean(const char *, Boolean); char *cached_realpath(const char *, char *); /* parse.c */ void Parse_Init(void); void Parse_End(void); typedef enum VarAssignOp { VAR_NORMAL, /* = */ VAR_SUBST, /* := */ VAR_SHELL, /* != or :sh= */ VAR_APPEND, /* += */ VAR_DEFAULT /* ?= */ } VarAssignOp; typedef struct VarAssign { char *varname; /* unexpanded */ VarAssignOp op; const char *value; /* unexpanded */ } VarAssign; typedef char *(*NextBufProc)(void *, size_t *); void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); Boolean Parse_IsVar(const char *, VarAssign *out_var); void Parse_DoVar(VarAssign *, GNode *); void Parse_AddIncludeDir(const char *); void Parse_File(const char *, int); void Parse_SetInput(const char *, int, int, NextBufProc, void *); GNodeList *Parse_MainName(void); int Parse_GetFatals(void); /* str.c */ typedef struct Words { char **words; size_t len; void *freeIt; } Words; Words Str_Words(const char *, Boolean); -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void Words_Free(Words w) { free(w.words); free(w.freeIt); } char *str_concat2(const char *, const char *); char *str_concat3(const char *, const char *, const char *); char *str_concat4(const char *, const char *, const char *, const char *); Boolean Str_Match(const char *, const char *); #ifndef HAVE_STRLCPY /* strlcpy.c */ size_t strlcpy(char *, const char *, size_t); #endif /* suff.c */ void Suff_Init(void); void Suff_End(void); void Suff_ClearSuffixes(void); Boolean Suff_IsTransform(const char *); GNode *Suff_AddTransform(const char *); void Suff_EndTransform(GNode *); void Suff_AddSuffix(const char *, GNode **); SearchPath *Suff_GetPath(const char *); void Suff_DoPaths(void); void Suff_AddInclude(const char *); void Suff_AddLib(const char *); void Suff_FindDeps(GNode *); SearchPath *Suff_FindPath(GNode *); void Suff_SetNull(const char *); void Suff_PrintAll(void); /* targ.c */ void Targ_Init(void); void Targ_End(void); void Targ_Stats(void); GNodeList *Targ_List(void); -GNode *Targ_NewGN(const char *); +GNode *GNode_New(const char *); GNode *Targ_FindNode(const char *); GNode *Targ_GetNode(const char *); GNode *Targ_NewInternalNode(const char *); GNode *Targ_GetEndNode(void); GNodeList *Targ_FindList(StringList *); -Boolean Targ_Ignore(GNode *); -Boolean Targ_Silent(GNode *); -Boolean Targ_Precious(GNode *); +Boolean Targ_Ignore(const GNode *); +Boolean Targ_Silent(const GNode *); +Boolean Targ_Precious(const GNode *); void Targ_SetMain(GNode *); void Targ_PrintCmds(GNode *); void Targ_PrintNode(GNode *, int); void Targ_PrintNodes(GNodeList *, int); char *Targ_FmtTime(time_t); void Targ_PrintType(int); void Targ_PrintGraph(int); void Targ_Propagate(void); /* var.c */ void Var_Init(void); void Var_End(void); typedef enum VarEvalFlags { VARE_NONE = 0, - /* Treat undefined variables as errors. */ - VARE_UNDEFERR = 0x01, - /* Expand and evaluate variables during parsing. */ - VARE_WANTRES = 0x02, - /* In an assignment using the ':=' operator, keep '$$' as '$$' instead - * of reducing it to a single '$'. */ - VARE_ASSIGN = 0x04 + + /* Expand and evaluate variables during parsing. + * + * TODO: Document what Var_Parse and Var_Subst return when this flag + * is not set. */ + VARE_WANTRES = 1 << 0, + + /* Treat undefined variables as errors. + * Must only be used in combination with VARE_WANTRES. */ + VARE_UNDEFERR = 1 << 1, + + /* Keep '$$' as '$$' instead of reducing it to a single '$'. + * + * Used in variable assignments using the ':=' operator. It allows + * multiple such assignments to be chained without accidentally expanding + * '$$file' to '$file' in the first assignment and interpreting it as + * '${f}' followed by 'ile' in the next assignment. + * + * See also preserveUndefined, which preserves subexpressions that are + * based on undefined variables; maybe that can be converted to a flag + * as well. */ + VARE_KEEP_DOLLAR = 1 << 2 } VarEvalFlags; -typedef enum VarSet_Flags { - VAR_NO_EXPORT = 0x01, /* do not export */ +typedef enum VarSetFlags { + VAR_SET_NONE = 0, + + /* do not export */ + VAR_SET_NO_EXPORT = 1 << 0, + /* Make the variable read-only. No further modification is possible, * except for another call to Var_Set with the same flag. */ - VAR_SET_READONLY = 0x02 -} VarSet_Flags; + VAR_SET_READONLY = 1 << 1 +} VarSetFlags; /* The state of error handling returned by Var_Parse. * * As of 2020-09-13, this bitset looks quite bloated, * with all the constants doubled. * * Its purpose is to first document the existing behavior, * and then migrate away from the SILENT constants, step by step, * as these are not suited for reliable, consistent error handling * and reporting. */ typedef enum VarParseResult { /* Both parsing and evaluation succeeded. */ VPR_OK = 0x0000, /* See if a message has already been printed for this error. */ VPR_ANY_MSG = 0x0001, /* Parsing failed. * No error message has been printed yet. * Deprecated, migrate to VPR_PARSE_MSG instead. */ VPR_PARSE_SILENT = 0x0002, /* Parsing failed. * An error message has already been printed. */ VPR_PARSE_MSG = VPR_PARSE_SILENT | VPR_ANY_MSG, /* Parsing succeeded. * During evaluation, VARE_UNDEFERR was set and there was an undefined * variable. * No error message has been printed yet. * Deprecated, migrate to VPR_UNDEF_MSG instead. */ VPR_UNDEF_SILENT = 0x0004, /* Parsing succeeded. * During evaluation, VARE_UNDEFERR was set and there was an undefined * variable. * An error message has already been printed. */ VPR_UNDEF_MSG = VPR_UNDEF_SILENT | VPR_ANY_MSG, /* Parsing succeeded. * Evaluation failed. * No error message has been printed yet. * Deprecated, migrate to VPR_EVAL_MSG instead. */ VPR_EVAL_SILENT = 0x0006, /* Parsing succeeded. * Evaluation failed. * An error message has already been printed. */ VPR_EVAL_MSG = VPR_EVAL_SILENT | VPR_ANY_MSG, /* The exact error handling status is not known yet. * Deprecated, migrate to VPR_OK or any VPE_*_MSG instead. */ VPR_UNKNOWN = 0x0008 } VarParseResult; void Var_Delete(const char *, GNode *); void Var_Set(const char *, const char *, GNode *); -void Var_Set_with_flags(const char *, const char *, GNode *, VarSet_Flags); +void Var_SetWithFlags(const char *, const char *, GNode *, VarSetFlags); void Var_Append(const char *, const char *, GNode *); Boolean Var_Exists(const char *, GNode *); const char *Var_Value(const char *, GNode *, void **); const char *Var_ValueDirect(const char *, GNode *); VarParseResult Var_Parse(const char **, GNode *, VarEvalFlags, const char **, void **); VarParseResult Var_Subst(const char *, GNode *, VarEvalFlags, char **); void Var_Stats(void); void Var_Dump(GNode *); void Var_ExportVars(void); void Var_Export(const char *, Boolean); void Var_UnExport(const char *); /* util.c */ typedef void (*SignalProc)(int); SignalProc bmake_signal(int, SignalProc); Index: head/contrib/bmake/parse.c =================================================================== --- head/contrib/bmake/parse.c (revision 367862) +++ head/contrib/bmake/parse.c (revision 367863) @@ -1,3246 +1,3180 @@ -/* $NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.443 2020/11/16 21:39:22 rillig 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. */ /* * Parsing of makefiles. * * Parse_File is the main entry point and controls most of the other * functions in this module. * * The directories for the .include "..." directive are kept in * 'parseIncPath', while those for .include <...> are kept in 'sysIncPath'. * The targets currently being defined are kept in 'targets'. * * Interface: * Parse_Init Initialize the module * * Parse_End Clean up the module * * Parse_File Parse a top-level makefile. Included files are * handled by Parse_include_file though. * * Parse_IsVar Return 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 Report a parse error, a warning or an informational * message. * * Parse_MainName Returns a list of the main target to create. */ #include #include #include #include #include "make.h" #ifdef HAVE_STDINT_H #include #endif #ifdef HAVE_MMAP #include #ifndef MAP_COPY #define MAP_COPY MAP_PRIVATE #endif #ifndef MAP_FILE #define MAP_FILE 0 #endif #endif #include "dir.h" #include "job.h" #include "pathnames.h" /* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $"); +MAKE_RCSID("$NetBSD: parse.c,v 1.443 2020/11/16 21:39:22 rillig Exp $"); /* types and constants */ /* * Structure for a file being read ("included file") */ typedef struct IFile { - char *fname; /* name of file */ + char *fname; /* name of file (relative? absolute?) */ Boolean fromForLoop; /* simulated .include by the .for loop */ int lineno; /* current line number in file */ int first_lineno; /* line number of start of text */ unsigned int cond_depth; /* 'if' nesting when file opened */ Boolean depending; /* state of doing_depend on EOF */ /* The buffer from which the file's content is read. */ char *buf_freeIt; char *buf_ptr; /* next char to be read */ char *buf_end; 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; /* * Tokens for target attributes */ typedef enum ParseSpecial { SP_ATTRIBUTE, /* Generic attribute */ SP_BEGIN, /* .BEGIN */ SP_DEFAULT, /* .DEFAULT */ SP_DELETE_ON_ERROR, /* .DELETE_ON_ERROR */ SP_END, /* .END */ SP_ERROR, /* .ERROR */ SP_IGNORE, /* .IGNORE */ SP_INCLUDES, /* .INCLUDES; not mentioned in the manual page */ SP_INTERRUPT, /* .INTERRUPT */ SP_LIBS, /* .LIBS; not mentioned in the manual page */ SP_MAIN, /* .MAIN and we don't have anything user-specified to * make */ SP_META, /* .META */ SP_MFLAGS, /* .MFLAGS or .MAKEFLAGS */ SP_NOMETA, /* .NOMETA */ SP_NOMETA_CMP, /* .NOMETA_CMP */ SP_NOPATH, /* .NOPATH */ SP_NOT, /* Not special */ SP_NOTPARALLEL, /* .NOTPARALLEL or .NO_PARALLEL */ SP_NULL, /* .NULL; not mentioned in the manual page */ SP_OBJDIR, /* .OBJDIR */ SP_ORDER, /* .ORDER */ SP_PARALLEL, /* .PARALLEL; not mentioned in the manual page */ SP_PATH, /* .PATH or .PATH.suffix */ SP_PHONY, /* .PHONY */ #ifdef POSIX SP_POSIX, /* .POSIX; not mentioned in the manual page */ #endif SP_PRECIOUS, /* .PRECIOUS */ SP_SHELL, /* .SHELL */ SP_SILENT, /* .SILENT */ SP_SINGLESHELL, /* .SINGLESHELL; not mentioned in the manual page */ SP_STALE, /* .STALE */ SP_SUFFIXES, /* .SUFFIXES */ SP_WAIT /* .WAIT */ } ParseSpecial; typedef List SearchPathList; typedef ListNode SearchPathListNode; /* 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 */ /* During parsing, the targets from the left-hand side of the currently * active dependency line, or NULL if the current line does not belong to a * dependency line, for example because it is a variable assignment. * * See unit-tests/deptgt.mk, keyword "parse.c:targets". */ static GNodeList *targets; #ifdef CLEANUP /* All shell commands for all targets, in no particular order and possibly * with duplicates. Kept in a separate list since the commands from .USE or * .USEBEFORE nodes are shared with other GNodes, thereby giving up the * easily understandable ownership over the allocated strings. */ static StringList *targCmds; #endif /* * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER * seen, then set to each successive source on the line. */ static GNode *order_pred; /* parser state */ /* number of fatal errors */ static int fatals = 0; /* * Variables for doing includes */ /* The include chain of makefiles. At the bottom is the top-level makefile * from the command line, and on top of that, there are the included files or * .for loops, up to and including the current file. * * This data could be used to print stack traces on parse errors. As of * 2020-09-14, this is not done though. It seems quite simple to print the * tuples (fname:lineno:fromForLoop), from top to bottom. This simple idea is * made complicated by the fact that the .for loops also use this stack for * storing information. * * The lineno fields of the IFiles with fromForLoop == TRUE look confusing, * which is demonstrated by the test 'include-main.mk'. They seem sorted * backwards since they tell the number of completely parsed lines, which for * a .for loop is right after the terminating .endfor. To compensate for this * confusion, there is another field first_lineno pointing at the start of the * .for loop, 1-based for human consumption. * * To make the stack trace intuitive, the entry below the first .for loop must * be ignored completely since neither its lineno nor its first_lineno is * useful. Instead, the topmost of each chain of .for loop needs to be * printed twice, once with its first_lineno and once with its lineno. * * As of 2020-10-28, using the above rules, the stack trace for the .info line * in include-subsub.mk would be: * * includes[5]: include-subsub.mk:4 * (lineno, from an .include) * includes[4]: include-sub.mk:32 * (lineno, from a .for loop below an .include) * includes[4]: include-sub.mk:31 * (first_lineno, from a .for loop, lineno == 32) * includes[3]: include-sub.mk:30 * (first_lineno, from a .for loop, lineno == 33) * includes[2]: include-sub.mk:29 * (first_lineno, from a .for loop, lineno == 34) * includes[1]: include-sub.mk:35 * (not printed since it is below a .for loop) * includes[0]: include-main.mk:27 */ static Vector /* of IFile */ includes; static IFile * GetInclude(size_t i) { return Vector_Get(&includes, i); } /* The file that is currently being read. */ static IFile * CurFile(void) { return GetInclude(includes.len - 1); } /* include paths */ SearchPath *parseIncPath; /* dirs for "..." includes */ SearchPath *sysIncPath; /* dirs for <...> includes */ SearchPath *defSysIncPath; /* 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 (SP_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 */ GNodeType op; /* Operator when used as a source */ } parseKeywords[] = { { ".BEGIN", SP_BEGIN, 0 }, { ".DEFAULT", SP_DEFAULT, 0 }, { ".DELETE_ON_ERROR", SP_DELETE_ON_ERROR, 0 }, { ".END", SP_END, 0 }, { ".ERROR", SP_ERROR, 0 }, { ".EXEC", SP_ATTRIBUTE, OP_EXEC }, { ".IGNORE", SP_IGNORE, OP_IGNORE }, { ".INCLUDES", SP_INCLUDES, 0 }, { ".INTERRUPT", SP_INTERRUPT, 0 }, { ".INVISIBLE", SP_ATTRIBUTE, OP_INVISIBLE }, { ".JOIN", SP_ATTRIBUTE, OP_JOIN }, { ".LIBS", SP_LIBS, 0 }, { ".MADE", SP_ATTRIBUTE, OP_MADE }, { ".MAIN", SP_MAIN, 0 }, { ".MAKE", SP_ATTRIBUTE, OP_MAKE }, { ".MAKEFLAGS", SP_MFLAGS, 0 }, { ".META", SP_META, OP_META }, { ".MFLAGS", SP_MFLAGS, 0 }, { ".NOMETA", SP_NOMETA, OP_NOMETA }, { ".NOMETA_CMP", SP_NOMETA_CMP, OP_NOMETA_CMP }, { ".NOPATH", SP_NOPATH, OP_NOPATH }, { ".NOTMAIN", SP_ATTRIBUTE, OP_NOTMAIN }, { ".NOTPARALLEL", SP_NOTPARALLEL, 0 }, { ".NO_PARALLEL", SP_NOTPARALLEL, 0 }, { ".NULL", SP_NULL, 0 }, { ".OBJDIR", SP_OBJDIR, 0 }, { ".OPTIONAL", SP_ATTRIBUTE, OP_OPTIONAL }, { ".ORDER", SP_ORDER, 0 }, { ".PARALLEL", SP_PARALLEL, 0 }, { ".PATH", SP_PATH, 0 }, { ".PHONY", SP_PHONY, OP_PHONY }, #ifdef POSIX { ".POSIX", SP_POSIX, 0 }, #endif { ".PRECIOUS", SP_PRECIOUS, OP_PRECIOUS }, { ".RECURSIVE", SP_ATTRIBUTE, OP_MAKE }, { ".SHELL", SP_SHELL, 0 }, { ".SILENT", SP_SILENT, OP_SILENT }, { ".SINGLESHELL", SP_SINGLESHELL, 0 }, { ".STALE", SP_STALE, 0 }, { ".SUFFIXES", SP_SUFFIXES, 0 }, { ".USE", SP_ATTRIBUTE, OP_USE }, { ".USEBEFORE", SP_ATTRIBUTE, OP_USEBEFORE }, { ".WAIT", SP_WAIT, 0 }, }; /* file loader */ struct loadedfile { + /* XXX: What is the lifetime of this path? Who manages the memory? */ 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 */ }; +/* XXX: What is the lifetime of the path? Who manages the memory? */ static struct loadedfile * loadedfile_create(const char *path) { struct loadedfile *lf; - lf = bmake_malloc(sizeof(*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 + if (lf->maplen > 0) munmap(lf->buf, lf->maplen); + else #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) { + if (lf->used) return NULL; - } + lf->used = TRUE; *len = lf->len; return lf->buf; } /* * Try to get the size of a file. */ static Boolean load_getsize(int fd, size_t *ret) { struct stat st; - if (fstat(fd, &st) < 0) { + if (fstat(fd, &st) < 0) return FALSE; - } - if (!S_ISREG(st.st_mode)) { + if (!S_ISREG(st.st_mode)) return FALSE; - } /* * 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) { + if (st.st_size < 0 || st.st_size > 0x7fffffff) return FALSE; - } *ret = (size_t)st.st_size; return TRUE; } #ifdef HAVE_MMAP static Boolean loadedfile_mmap(struct loadedfile *lf, int fd) { static unsigned long pagesize = 0; - if (load_getsize(fd, &lf->len)) { + if (!load_getsize(fd, &lf->len)) + return FALSE; - /* found a size, try mmap */ + /* found a size, try mmap */ #ifdef _SC_PAGESIZE - if (pagesize == 0) - pagesize = (unsigned long)sysconf(_SC_PAGESIZE); + if (pagesize == 0) + pagesize = (unsigned long)sysconf(_SC_PAGESIZE); #endif - if (pagesize == 0 || pagesize == (unsigned long)-1) { - pagesize = 0x1000; - } - /* round size up to a page */ - lf->maplen = pagesize * ((lf->len + pagesize - 1) / pagesize); + if (pagesize == 0 || pagesize == (unsigned long)-1) + pagesize = 0x1000; - /* - * 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; - } + /* round size up to a page */ + lf->maplen = pagesize * ((lf->len + pagesize - 1) / 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 = bmake_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; - } - return TRUE; - } + /* + * 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) + return FALSE; + + if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') { + char *b = bmake_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; } - return FALSE; + + return TRUE; } #endif /* * 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. + * If the path is NULL, use stdin. */ static struct loadedfile * loadfile(const char *path, int fd) { struct loadedfile *lf; 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 (loadedfile_mmap(lf, fd)) 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) { + for (;;) { assert(bufpos <= lf->len); if (bufpos == lf->len) { if (lf->len > SIZE_MAX/2) { errno = EFBIG; Error("%s: file too large", path); exit(1); } lf->len *= 2; lf->buf = bmake_realloc(lf->buf, lf->len); } assert(bufpos < 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) { + if (result == 0) break; - } + bufpos += (size_t)result; } assert(bufpos <= lf->len); lf->len = bufpos; /* truncate malloc region to actual length (maybe not useful) */ if (lf->len > 0) { /* as for mmap case, ensure trailing \n */ if (lf->buf[lf->len - 1] != '\n') lf->len++; lf->buf = bmake_realloc(lf->buf, lf->len); lf->buf[lf->len - 1] = '\n'; } #ifdef HAVE_MMAP done: #endif - if (path != NULL) { + if (path != NULL) close(fd); - } + return lf; } /* old code */ /* Check if the current character is escaped on the current line. */ static Boolean ParseIsEscaped(const char *line, const char *c) { Boolean active = FALSE; for (;;) { if (line == c) return active; if (*--c != '\\') return active; active = !active; } } /* Add the filename and lineno to the GNode so that we remember where it * was first defined. */ static void ParseMark(GNode *gn) { IFile *curFile = CurFile(); gn->fname = curFile->fname; gn->lineno = curFile->lineno; } /* Look in the table of keywords for one matching the given string. * Return the index of the keyword, or -1 if it isn't there. */ static int ParseFindKeyword(const char *str) { - int start, end, cur; - int diff; + int start = 0; + int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; - start = 0; - end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; - do { - cur = start + (end - start) / 2; - diff = strcmp(str, parseKeywords[cur].name); + int cur = start + (end - start) / 2; + int diff = strcmp(str, parseKeywords[cur].name); - if (diff == 0) { + if (diff == 0) return cur; - } else if (diff < 0) { + if (diff < 0) end = cur - 1; - } else { + else start = cur + 1; - } } while (start <= end); + return -1; } static void -PrintLocation(FILE *f, const char *filename, size_t lineno) +PrintLocation(FILE *f, const char *fname, size_t lineno) { char dirbuf[MAXPATHLEN+1]; const char *dir, *base; void *dir_freeIt, *base_freeIt; - if (*filename == '/' || strcmp(filename, "(stdin)") == 0) { - (void)fprintf(f, "\"%s\" line %zu: ", filename, lineno); + if (*fname == '/' || strcmp(fname, "(stdin)") == 0) { + (void)fprintf(f, "\"%s\" line %zu: ", fname, lineno); return; } /* Find out which makefile is the culprit. * We try ${.PARSEDIR} and apply realpath(3) if not absolute. */ dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &dir_freeIt); if (dir == NULL) dir = "."; if (*dir != '/') dir = realpath(dir, dirbuf); base = Var_Value(".PARSEFILE", VAR_GLOBAL, &base_freeIt); if (base == NULL) { - const char *slash = strrchr(filename, '/'); - base = slash != NULL ? slash + 1 : filename; + const char *slash = strrchr(fname, '/'); + base = slash != NULL ? slash + 1 : fname; } (void)fprintf(f, "\"%s/%s\" line %zu: ", dir, base, lineno); bmake_free(base_freeIt); bmake_free(dir_freeIt); } -/* Print a parse error message, including location information. - * - * Increment "fatals" if the level is PARSE_FATAL, and continue parsing - * until the end of the current top-level makefile, then exit (see - * Parse_File). */ static void -ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, +ParseVErrorInternal(FILE *f, const char *fname, size_t lineno, ParseErrorLevel type, const char *fmt, va_list ap) { static Boolean fatal_warning_error_printed = FALSE; (void)fprintf(f, "%s: ", progname); - if (cfname != NULL) - PrintLocation(f, cfname, clineno); + if (fname != NULL) + PrintLocation(f, fname, lineno); if (type == PARSE_WARNING) (void)fprintf(f, "warning: "); (void)vfprintf(f, fmt, ap); (void)fprintf(f, "\n"); (void)fflush(f); if (type == PARSE_INFO) return; if (type == PARSE_FATAL || opts.parseWarnFatal) fatals++; if (opts.parseWarnFatal && !fatal_warning_error_printed) { Error("parsing warnings being treated as errors"); fatal_warning_error_printed = TRUE; } } static void -ParseErrorInternal(const char *cfname, size_t clineno, ParseErrorLevel type, - const char *fmt, ...) +ParseErrorInternal(const char *fname, size_t lineno, + ParseErrorLevel type, const char *fmt, ...) { va_list ap; - va_start(ap, fmt); (void)fflush(stdout); - ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap); + va_start(ap, fmt); + ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); va_end(ap); if (opts.debug_file != stderr && opts.debug_file != stdout) { va_start(ap, fmt); - ParseVErrorInternal(opts.debug_file, cfname, clineno, type, + ParseVErrorInternal(opts.debug_file, fname, lineno, type, fmt, ap); va_end(ap); } } -/* External interface to ParseErrorInternal; uses the default filename and - * line number. +/* Print a parse error message, including location information. * + * If the level is PARSE_FATAL, continue parsing until the end of the + * current top-level makefile, then exit (see Parse_File). + * * Fmt is given without a trailing newline. */ void Parse_Error(ParseErrorLevel type, const char *fmt, ...) { va_list ap; const char *fname; size_t lineno; if (includes.len == 0) { fname = NULL; lineno = 0; } else { IFile *curFile = CurFile(); fname = curFile->fname; lineno = (size_t)curFile->lineno; } va_start(ap, fmt); (void)fflush(stdout); ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); va_end(ap); if (opts.debug_file != stderr && opts.debug_file != stdout) { va_start(ap, fmt); ParseVErrorInternal(opts.debug_file, fname, lineno, type, fmt, ap); va_end(ap); } } -/* 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. - */ +/* Parse and handle a .info, .warning or .error directive. + * For an .error directive, immediately exit. */ static Boolean ParseMessage(const char *directive) { const char *p = directive; int mtype = *p == 'i' ? PARSE_INFO : *p == 'w' ? PARSE_WARNING : PARSE_FATAL; char *arg; while (ch_isalpha(*p)) p++; if (!ch_isspace(*p)) return FALSE; /* missing argument */ cpp_skip_whitespace(&p); (void)Var_Subst(p, VAR_CMDLINE, VARE_WANTRES, &arg); /* TODO: handle errors */ Parse_Error(mtype, "%s", arg); free(arg); if (mtype == PARSE_FATAL) { PrintOnError(NULL, NULL); exit(1); } return TRUE; } /* Add the child to the parent's children. * * Additionally, add the parent to the child's parents, but only if the * target is not special. An example for such a special target is .END, * which does not need to be informed once the child target has been made. */ static void LinkSource(GNode *pgn, GNode *cgn, Boolean isSpecial) { if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(pgn->cohorts)) pgn = pgn->cohorts->last->datum; Lst_Append(pgn->children, cgn); pgn->unmade++; /* Special targets like .END don't need any children. */ if (!isSpecial) Lst_Append(cgn->parents, pgn); if (DEBUG(PARSE)) { debug_printf("# %s: added child %s - %s\n", __func__, pgn->name, cgn->name); Targ_PrintNode(pgn, 0); Targ_PrintNode(cgn, 0); } } /* Add the node to each target from the current dependency group. */ static void LinkToTargets(GNode *gn, Boolean isSpecial) { GNodeListNode *ln; for (ln = targets->first; ln != NULL; ln = ln->next) LinkSource(ln->datum, gn, isSpecial); } static Boolean TryApplyDependencyOperator(GNode *gn, GNodeType op) { /* * If the node occurred on the left-hand side of a dependency and the * operator also defines a dependency, they must match. */ if ((op & OP_OPMASK) && (gn->type & OP_OPMASK) && ((op & OP_OPMASK) != (gn->type & OP_OPMASK))) { Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name); return FALSE; } 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 + * If the node was of the left-hand side of a '::' operator, we need + * to create a new instance of it for the children and commands on + * this dependency line since each of these dependency groups has its + * own attributes and commands, separate from the others. + * + * 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_NewInternalNode(gn->name); 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; Lst_Append(gn->cohorts, cohort); cohort->centurion = gn; gn->unmade_cohorts++; snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", (unsigned int)gn->unmade_cohorts % 1000000); } else { /* * We don't want to nuke any previous flags (whatever they were) so we - * just OR the new operator into the old + * just OR the new operator into the old. */ gn->type |= op; } return TRUE; } static void ApplyDependencyOperator(GNodeType op) { GNodeListNode *ln; for (ln = targets->first; ln != NULL; ln = ln->next) if (!TryApplyDependencyOperator(ln->datum, op)) break; } static Boolean ParseDoSrcKeyword(const char *src, ParseSpecial specType) { static int wait_number = 0; char wait_src[16]; GNode *gn; if (*src == '.' && ch_isupper(src[1])) { int keywd = ParseFindKeyword(src); if (keywd != -1) { - int op = parseKeywords[keywd].op; + GNodeType op = parseKeywords[keywd].op; if (op != 0) { ApplyDependencyOperator(op); return TRUE; } if (parseKeywords[keywd].spec == SP_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. + * have happened, it is given a dependency on each + * previous child, back until the 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). + * We give each .WAIT node a unique name (mainly for + * diagnostics). */ snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number); gn = Targ_NewInternalNode(wait_src); if (doing_depend) ParseMark(gn); gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN; LinkToTargets(gn, specType != SP_NOT); return TRUE; } } } return FALSE; } static void ParseDoSrcMain(const char *src) { /* - * 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... + * In a line like ".MAIN: source1 source2", it means we need to add + * the sources of said target to the list of things to create. + * + * Note that this will only be invoked if the user didn't specify a + * target on the command line. This is to allow .ifmake to succeed. + * + * XXX: Double-check all of the above comment. */ Lst_Append(opts.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); } static void ParseDoSrcOrder(const char *src) { GNode *gn; /* * Create proper predecessor/successor links between the previous * source and the current one. */ gn = Targ_GetNode(src); if (doing_depend) ParseMark(gn); if (order_pred != NULL) { Lst_Append(order_pred->order_succ, gn); Lst_Append(gn->order_pred, order_pred); if (DEBUG(PARSE)) { debug_printf("# %s: added Order dependency %s - %s\n", __func__, order_pred->name, gn->name); Targ_PrintNode(order_pred, 0); Targ_PrintNode(gn, 0); } } /* * The current source now becomes the predecessor for the next one. */ order_pred = gn; } static void ParseDoSrcOther(const char *src, GNodeType tOp, ParseSpecial specType) { GNode *gn; /* * 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_GetNode(src); if (doing_depend) ParseMark(gn); - if (tOp) { + if (tOp != OP_NONE) gn->type |= tOp; - } else { + else LinkToTargets(gn, specType != SP_NOT); - } } /* Given the name of a source in a dependency line, figure out if it is an * attribute (such as .SILENT) 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 (such as .PHONY) and apply it if so. * Otherwise, make the source a child of the targets in the list 'targets'. * * Input: * tOp operator (if any) from special targets * src name of the source to handle */ static void ParseDoSrc(GNodeType tOp, const char *src, ParseSpecial specType) { if (ParseDoSrcKeyword(src, specType)) return; if (specType == SP_MAIN) ParseDoSrcMain(src); else if (specType == SP_ORDER) ParseDoSrcOrder(src); else ParseDoSrcOther(src, tOp, specType); } /* 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. */ static void FindMainTarget(void) { GNodeListNode *ln; if (mainNode != NULL) return; for (ln = targets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; if (!(gn->type & OP_NOTARGET)) { mainNode = gn; Targ_SetMain(gn); return; } } } /* * 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. */ static void ParseErrorNoDependency(const char *lstart) { if ((strncmp(lstart, "<<<<<<", 6) == 0) || (strncmp(lstart, "======", 6) == 0) || (strncmp(lstart, ">>>>>>", 6) == 0)) Parse_Error(PARSE_FATAL, "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); else if (lstart[0] == '.') { const char *dirstart = lstart + 1; const char *dirend; cpp_skip_whitespace(&dirstart); dirend = dirstart; while (ch_isalnum(*dirend) || *dirend == '-') dirend++; Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"", (int)(dirend - dirstart), dirstart); } else Parse_Error(PARSE_FATAL, "Need an operator"); } static void ParseDependencyTargetWord(/*const*/ char **pp, const char *lstart) { /*const*/ char *cp = *pp; while (*cp != '\0') { if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' || *cp == '(') && !ParseIsEscaped(lstart, cp)) break; 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. */ const char *nested_p = cp; const char *nested_val; void *freeIt; - (void)Var_Parse(&nested_p, VAR_CMDLINE, VARE_UNDEFERR|VARE_WANTRES, - &nested_val, &freeIt); + (void)Var_Parse(&nested_p, VAR_CMDLINE, + VARE_WANTRES | VARE_UNDEFERR, &nested_val, &freeIt); /* TODO: handle errors */ free(freeIt); cp += nested_p - cp; } else cp++; } *pp = cp; } -/* - * 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 - * .DELETE_ON_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 - */ +/* Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. */ static void ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, const char *line, SearchPathList **inout_paths) { switch (*inout_specType) { case SP_PATH: - if (*inout_paths == NULL) { + if (*inout_paths == NULL) *inout_paths = Lst_New(); - } Lst_Append(*inout_paths, dirSearchPath); break; case SP_MAIN: - if (!Lst_IsEmpty(opts.create)) { + /* Allow targets from the command line to override the .MAIN node. */ + if (!Lst_IsEmpty(opts.create)) *inout_specType = SP_NOT; - } break; case SP_BEGIN: case SP_END: case SP_STALE: case SP_ERROR: case SP_INTERRUPT: { GNode *gn = Targ_GetNode(line); if (doing_depend) ParseMark(gn); gn->type |= OP_NOTMAIN|OP_SPECIAL; Lst_Append(targets, gn); break; } case SP_DEFAULT: { - GNode *gn = Targ_NewGN(".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. 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. */ + GNode *gn = GNode_New(".DEFAULT"); gn->type |= OP_NOTMAIN|OP_TRANSFORM; Lst_Append(targets, gn); - DEFAULT = gn; + defaultNode = gn; break; } case SP_DELETE_ON_ERROR: deleteOnError = TRUE; break; case SP_NOTPARALLEL: opts.maxJobs = 1; break; case SP_SINGLESHELL: opts.compatMake = TRUE; break; case SP_ORDER: order_pred = NULL; break; default: break; } } /* * .PATH has to be handled specially. * Call on the suffix module to give us a path to modify. */ static Boolean ParseDoDependencyTargetPath(const char *line, SearchPathList **inout_paths) { SearchPath *path; path = Suff_GetPath(&line[5]); if (path == NULL) { Parse_Error(PARSE_FATAL, "Suffix '%s' not defined (yet)", &line[5]); return FALSE; - } else { - if (*inout_paths == NULL) { - *inout_paths = Lst_New(); - } - Lst_Append(*inout_paths, path); } + + if (*inout_paths == NULL) + *inout_paths = Lst_New(); + Lst_Append(*inout_paths, path); + return TRUE; } /* * See if it's a special target and if so set specType to match it. */ static Boolean ParseDoDependencyTarget(const char *line, ParseSpecial *inout_specType, GNodeType *out_tOp, SearchPathList **inout_paths) { int keywd; if (!(*line == '.' && ch_isupper(line[1]))) return TRUE; /* * See if the target is a special target that must have it * or its sources handled specially. */ keywd = ParseFindKeyword(line); if (keywd != -1) { if (*inout_specType == SP_PATH && parseKeywords[keywd].spec != SP_PATH) { Parse_Error(PARSE_FATAL, "Mismatched special targets"); return FALSE; } *inout_specType = parseKeywords[keywd].spec; *out_tOp = parseKeywords[keywd].op; ParseDoDependencyTargetSpecial(inout_specType, line, inout_paths); } else if (strncmp(line, ".PATH", 5) == 0) { *inout_specType = SP_PATH; if (!ParseDoDependencyTargetPath(line, inout_paths)) return FALSE; } return TRUE; } static void ParseDoDependencyTargetMundane(char *line, StringList *curTargs) { 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... */ SearchPath *emptyPath = Lst_New(); 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. */ Lst_Append(curTargs, line); } /* Apply the targets. */ while (!Lst_IsEmpty(curTargs)) { char *targName = Lst_Dequeue(curTargs); GNode *gn = Suff_IsTransform(targName) ? Suff_AddTransform(targName) : Targ_GetNode(targName); if (doing_depend) ParseMark(gn); Lst_Append(targets, gn); } } static void ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart) { Boolean warning = FALSE; char *cp = *pp; - while (*cp && (ParseIsEscaped(lstart, cp) || - (*cp != '!' && *cp != ':'))) { - if (ParseIsEscaped(lstart, cp) || - (*cp != ' ' && *cp != '\t')) { + while (*cp != '\0') { + if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':')) + break; + if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t')) warning = TRUE; - } cp++; } - if (warning) { + if (warning) Parse_Error(PARSE_WARNING, "Extra target ignored"); - } + *pp = cp; } static void ParseDoDependencyCheckSpec(ParseSpecial specType) { switch (specType) { default: Parse_Error(PARSE_WARNING, - "Special and mundane targets don't mix. Mundane ones ignored"); + "Special and mundane targets don't mix. " + "Mundane ones ignored"); break; case SP_DEFAULT: case SP_STALE: case SP_BEGIN: case SP_END: case SP_ERROR: case SP_INTERRUPT: /* - * These four create nodes on which to hang commands, so - * targets shouldn't be empty... + * These create nodes on which to hang commands, so targets + * shouldn't be empty... */ case SP_NOT: - /* - * Nothing special here -- targets can be empty if it wants. - */ + /* Nothing special here -- targets can be empty if it wants. */ break; } } static Boolean ParseDoDependencyParseOp(char **pp, const char *lstart, GNodeType *out_op) { const char *cp = *pp; if (*cp == '!') { *out_op = OP_FORCE; (*pp)++; return TRUE; } if (*cp == ':') { if (cp[1] == ':') { *out_op = OP_DOUBLEDEP; (*pp) += 2; } else { *out_op = OP_DEPENDS; (*pp)++; } return TRUE; } { const char *msg = lstart[0] == '.' ? "Unknown directive" : "Missing dependency operator"; Parse_Error(PARSE_FATAL, "%s", msg); return FALSE; } } static void ClearPaths(SearchPathList *paths) { if (paths != NULL) { SearchPathListNode *ln; for (ln = paths->first; ln != NULL; ln = ln->next) Dir_ClearPath(ln->datum); } Dir_SetPATH(); } static void ParseDoDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) { switch (specType) { case SP_SUFFIXES: Suff_ClearSuffixes(); break; case SP_PRECIOUS: allPrecious = TRUE; break; case SP_IGNORE: opts.ignoreErrors = TRUE; break; case SP_SILENT: opts.beSilent = TRUE; break; case SP_PATH: ClearPaths(paths); break; #ifdef POSIX case SP_POSIX: Var_Set("%POSIX", "1003.2", VAR_GLOBAL); break; #endif default: break; } } static void AddToPaths(const char *dir, SearchPathList *paths) { if (paths != NULL) { SearchPathListNode *ln; for (ln = paths->first; ln != NULL; ln = ln->next) (void)Dir_AddDir(ln->datum, dir); } } /* * 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. */ static void ParseDoDependencySourceSpecial(ParseSpecial specType, char *word, SearchPathList *paths) { switch (specType) { case SP_SUFFIXES: Suff_AddSuffix(word, &mainNode); break; case SP_PATH: AddToPaths(word, paths); break; case SP_INCLUDES: Suff_AddInclude(word); break; case SP_LIBS: Suff_AddLib(word); break; case SP_NULL: Suff_SetNull(word); break; case SP_OBJDIR: - Main_SetObjdir("%s", word); + Main_SetObjdir(FALSE, "%s", word); break; default: break; } } static Boolean ParseDoDependencyTargets(char **inout_cp, char **inout_line, const char *lstart, ParseSpecial *inout_specType, GNodeType *inout_tOp, SearchPathList **inout_paths, StringList *curTargs) { char *cp = *inout_cp; char *line = *inout_line; char savec; for (;;) { /* * 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. */ cp = line; ParseDependencyTargetWord(&cp, lstart); /* * 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 == '(') { /* * 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 TRUE if all * went well and FALSE if there was an error in the * specification. On error, line should remain untouched. */ if (!Arch_ParseArchive(&line, targets, VAR_CMDLINE)) { Parse_Error(PARSE_FATAL, "Error in archive specification: \"%s\"", line); return FALSE; } else { /* Done with this word; on to the next. */ cp = line; continue; } } if (!*cp) { ParseErrorNoDependency(lstart); return FALSE; } /* Insert a null terminator. */ savec = *cp; *cp = '\0'; if (!ParseDoDependencyTarget(line, inout_specType, inout_tOp, inout_paths)) return FALSE; /* * Have word in line. Get or create its node and stick it at * the end of the targets list */ - if (*inout_specType == SP_NOT && *line != '\0') { + if (*inout_specType == SP_NOT && *line != '\0') ParseDoDependencyTargetMundane(line, curTargs); - } else if (*inout_specType == SP_PATH && *line != '.' && *line != '\0') { + else if (*inout_specType == SP_PATH && *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 (*inout_specType != SP_NOT && *inout_specType != SP_PATH) { + if (*inout_specType != SP_NOT && *inout_specType != SP_PATH) ParseDoDependencyTargetExtraWarn(&cp, lstart); - } else { + else pp_skip_whitespace(&cp); - } + line = cp; if (*line == '\0') break; if ((*line == '!' || *line == ':') && !ParseIsEscaped(lstart, line)) break; } *inout_cp = cp; *inout_line = line; return TRUE; } static void ParseDoDependencySourcesSpecial(char *start, char *end, ParseSpecial specType, SearchPathList *paths) { char savec; while (*start) { while (*end && !ch_isspace(*end)) end++; savec = *end; *end = '\0'; ParseDoDependencySourceSpecial(specType, start, paths); *end = savec; if (savec != '\0') end++; pp_skip_whitespace(&end); start = end; } } static Boolean ParseDoDependencySourcesMundane(char *start, char *end, ParseSpecial specType, GNodeType tOp) { - while (*start) { + while (*start != '\0') { /* * 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 (; *end && !ch_isspace(*end); end++) { if (*end == '(' && end > start && end[-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 (*end == '(') { GNodeList *sources = Lst_New(); if (!Arch_ParseArchive(&start, sources, VAR_CMDLINE)) { Parse_Error(PARSE_FATAL, "Error in source archive spec \"%s\"", start); return FALSE; } while (!Lst_IsEmpty(sources)) { GNode *gn = Lst_Dequeue(sources); ParseDoSrc(tOp, gn->name, specType); } Lst_Free(sources); end = start; } else { if (*end) { *end = '\0'; end++; } ParseDoSrc(tOp, start, specType); } pp_skip_whitespace(&end); start = end; } return TRUE; } /* Parse a dependency line consisting of targets, followed by a dependency * operator, optionally followed by sources. * * The nodes of the sources are linked as children to the nodes of the * targets. Nodes are created as necessary. * * The operator is applied to each node in the global 'targets' list, * which is where the nodes found for the targets are kept, by means of * the ParseDoOp function. * * The sources are parsed in much the same way as the targets, except * that 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. + * and a target is created for each expanded word. Each of the resulting + * nodes is then linked to each of the targets as one of its children. * * Certain targets and sources such as .PHONY or .PRECIOUS are handled * specially. These are the ones detailed by the specType variable. * * The storing of transformation rules such as '.c.o' 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. + * + * Upon return, the value of the line is unspecified. */ static void ParseDoDependency(char *line) { char *cp; /* our current position */ GNodeType op; /* the operator on the line */ SearchPathList *paths; /* search paths to alter when parsing * a list of .PATH targets */ - int tOp; /* operator from special target */ + GNodeType tOp; /* operator from special target */ StringList *curTargs; /* target names to be found and added * to the targets list */ char *lstart = line; /* * specType contains the SPECial TYPE of the current target. It is SP_NOT * if the target is unspecial. If it *is* special, however, the children * are linked as children of the parent but not vice versa. */ ParseSpecial specType = SP_NOT; DEBUG1(PARSE, "ParseDoDependency(%s)\n", line); - tOp = 0; + tOp = OP_NONE; paths = NULL; curTargs = Lst_New(); /* * First, grind through the targets. */ if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp, &paths, curTargs)) goto out; - /* - * Don't need the list of target names anymore... - */ + /* Don't need the list of target names anymore. + * The targets themselves are now in the global variable 'targets'. */ Lst_Free(curTargs); curTargs = NULL; if (!Lst_IsEmpty(targets)) ParseDoDependencyCheckSpec(specType); /* * Have now parsed all the target names. Must parse the operator next. */ if (!ParseDoDependencyParseOp(&cp, lstart, &op)) goto out; /* * 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. */ ApplyDependencyOperator(op); /* * Onward to the sources. * * LINE will now point to the first source word, if any, or the * end of the string if not. */ pp_skip_whitespace(&cp); - line = cp; + line = cp; /* XXX: 'line' is an inappropriate name */ /* * 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) { + if (line[0] == '\0') { ParseDoDependencySourcesEmpty(specType, paths); } else if (specType == SP_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 == SP_SHELL) { if (!Job_ParseShell(line)) { Parse_Error(PARSE_FATAL, "improper shell specification"); goto out; } *line = '\0'; } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL || specType == SP_DELETE_ON_ERROR) { *line = '\0'; } - /* - * NOW GO FOR THE SOURCES - */ + /* Now go for the sources. */ if (specType == SP_SUFFIXES || specType == SP_PATH || specType == SP_INCLUDES || specType == SP_LIBS || specType == SP_NULL || specType == SP_OBJDIR) { ParseDoDependencySourcesSpecial(line, cp, specType, paths); if (paths) { Lst_Free(paths); paths = NULL; } if (specType == SP_PATH) Dir_SetPATH(); } else { assert(paths == NULL); if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp)) goto out; } FindMainTarget(); out: if (paths != NULL) Lst_Free(paths); if (curTargs != NULL) Lst_Free(curTargs); } typedef struct VarAssignParsed { const char *nameStart; /* unexpanded */ const char *nameEnd; /* before operator adjustment */ const char *eq; /* the '=' of the assignment operator */ } VarAssignParsed; /* Determine the assignment operator and adjust the end of the variable * name accordingly. */ static void AdjustVarassignOp(const VarAssignParsed *pvar, const char *value, VarAssign *out_var) { const char *op = pvar->eq; const char * const name = pvar->nameStart; VarAssignOp type; if (op > name && op[-1] == '+') { type = VAR_APPEND; op--; } else if (op > name && op[-1] == '?') { op--; type = VAR_DEFAULT; } else if (op > name && op[-1] == ':') { op--; type = VAR_SUBST; } else if (op > name && op[-1] == '!') { op--; type = VAR_SHELL; } else { type = VAR_NORMAL; #ifdef SUNSHCMD while (op > name && ch_isspace(op[-1])) op--; if (op >= name + 3 && op[-3] == ':' && op[-2] == 's' && op[-1] == 'h') { type = VAR_SHELL; op -= 3; } #endif } { const char *nameEnd = pvar->nameEnd < op ? pvar->nameEnd : op; out_var->varname = bmake_strsedup(pvar->nameStart, nameEnd); out_var->op = type; out_var->value = value; } } /* Parse a variable assignment, consisting of a single-word variable name, * optional whitespace, an assignment operator, optional whitespace and the * variable value. * * 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++ =". * * Used for both lines in a file and command line arguments. */ Boolean Parse_IsVar(const char *p, VarAssign *out_var) { VarAssignParsed pvar; const char *firstSpace = NULL; - char ch; int level = 0; - /* Skip to variable name */ - while (*p == ' ' || *p == '\t') - p++; + cpp_skip_hspace(&p); /* Skip to variable name */ /* During parsing, the '+' of the '+=' operator is initially parsed * as part of the variable name. It is later corrected, as is the ':sh' * modifier. Of these two (nameEnd and op), the earlier one determines the * actual end of the variable name. */ pvar.nameStart = p; #ifdef CLEANUP pvar.nameEnd = NULL; pvar.eq = NULL; #endif /* Scan for one of the assignment operators outside a variable expansion */ - while ((ch = *p++) != 0) { + while (*p != '\0') { + char ch = *p++; if (ch == '(' || ch == '{') { level++; continue; } if (ch == ')' || ch == '}') { level--; continue; } if (level != 0) continue; if (ch == ' ' || ch == '\t') if (firstSpace == NULL) firstSpace = p - 1; while (ch == ' ' || ch == '\t') ch = *p++; #ifdef SUNSHCMD - if (ch == ':' && strncmp(p, "sh", 2) == 0) { + if (ch == ':' && p[0] == 's' && p[1] == 'h') { p += 2; continue; } #endif if (ch == '=') { pvar.eq = p - 1; pvar.nameEnd = firstSpace != NULL ? firstSpace : p - 1; cpp_skip_whitespace(&p); AdjustVarassignOp(&pvar, p, out_var); return TRUE; } if (*p == '=' && (ch == '+' || ch == ':' || ch == '?' || ch == '!')) { pvar.eq = p; pvar.nameEnd = firstSpace != NULL ? firstSpace : p; p++; cpp_skip_whitespace(&p); AdjustVarassignOp(&pvar, p, out_var); return TRUE; } if (firstSpace != NULL) return FALSE; } return FALSE; } static void VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *ctxt) { - if (DEBUG(LINT)) { + if (opts.lint) { if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) { /* Check for syntax errors such as unclosed expressions or * unknown modifiers. */ char *expandedValue; (void)Var_Subst(uvalue, ctxt, VARE_NONE, &expandedValue); /* TODO: handle errors */ free(expandedValue); } } } static void VarAssign_EvalSubst(const char *name, const char *uvalue, GNode *ctxt, const char **out_avalue, void **out_avalue_freeIt) { const char *avalue = uvalue; char *evalue; - /* - * Allow variables in the old value to be undefined, but leave their - * expressions 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 + Boolean savedPreserveUndefined = preserveUndefined; + + /* TODO: Can this assignment to preserveUndefined be moved further down + * to the actually interesting Var_Subst call, without affecting any + * edge cases? * - * CFLAGS = $(.INCLUDES) - * CFLAGS := -I.. $(CFLAGS) - * - * And not get an error. - */ - Boolean oldOldVars = oldVars; + * It might affect the implicit expansion of the variable name in the + * Var_Exists and Var_Set calls, even though it's unlikely that anyone + * cared about this edge case when adding this code. In addition, + * variable assignments should not refer to any undefined variables in + * the variable name. */ + preserveUndefined = TRUE; - oldVars = FALSE; - /* * make sure that we set the variable the first time to nothing * so that it gets substituted! */ if (!Var_Exists(name, ctxt)) Var_Set(name, "", ctxt); - (void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_ASSIGN, &evalue); + (void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_KEEP_DOLLAR, &evalue); /* TODO: handle errors */ - oldVars = oldOldVars; + preserveUndefined = savedPreserveUndefined; avalue = evalue; Var_Set(name, avalue, ctxt); *out_avalue = avalue; *out_avalue_freeIt = evalue; } static void VarAssign_EvalShell(const char *name, const char *uvalue, GNode *ctxt, const char **out_avalue, void **out_avalue_freeIt) { const char *cmd, *errfmt; char *cmdOut; void *cmd_freeIt = NULL; cmd = uvalue; if (strchr(cmd, '$') != NULL) { char *ecmd; - (void)Var_Subst(cmd, VAR_CMDLINE, VARE_UNDEFERR | VARE_WANTRES, &ecmd); + (void)Var_Subst(cmd, VAR_CMDLINE, VARE_WANTRES | VARE_UNDEFERR, &ecmd); /* TODO: handle errors */ cmd = cmd_freeIt = ecmd; } cmdOut = Cmd_Exec(cmd, &errfmt); Var_Set(name, cmdOut, ctxt); *out_avalue = *out_avalue_freeIt = cmdOut; if (errfmt) Parse_Error(PARSE_WARNING, errfmt, cmd); free(cmd_freeIt); } /* Perform a variable assignment. * * The actual value of the variable is returned in *out_avalue and * *out_avalue_freeIt. Especially for VAR_SUBST and VAR_SHELL this can differ * from the literal value. * * Return whether the assignment was actually done. The assignment is only * skipped if the operator is '?=' and the variable already exists. */ static Boolean VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, GNode *ctxt, const char **out_avalue, void **out_avalue_freeIt) { const char *avalue = uvalue; void *avalue_freeIt = NULL; - if (op == VAR_APPEND) { + if (op == VAR_APPEND) Var_Append(name, uvalue, ctxt); - } else if (op == VAR_SUBST) { + else if (op == VAR_SUBST) VarAssign_EvalSubst(name, uvalue, ctxt, &avalue, &avalue_freeIt); - } else if (op == VAR_SHELL) { + else if (op == VAR_SHELL) VarAssign_EvalShell(name, uvalue, ctxt, &avalue, &avalue_freeIt); - } else { + else { if (op == VAR_DEFAULT && Var_Exists(name, ctxt)) { *out_avalue_freeIt = NULL; return FALSE; } /* Normal assignment -- just do it. */ Var_Set(name, uvalue, ctxt); } *out_avalue = avalue; *out_avalue_freeIt = avalue_freeIt; return TRUE; } static void VarAssignSpecial(const char *name, const char *avalue) { if (strcmp(name, MAKEOVERRIDES) == 0) Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */ else if (strcmp(name, ".CURDIR") == 0) { /* * Someone is being (too?) clever... * Let's pretend they know what they are doing and * re-initialize the 'cur' CachedDir. */ Dir_InitCur(avalue); Dir_SetPATH(); - } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) { + } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) Job_SetPrefix(); - } else if (strcmp(name, MAKE_EXPORTED) == 0) { + else if (strcmp(name, MAKE_EXPORTED) == 0) Var_Export(avalue, FALSE); - } } /* Perform the variable variable assignment in the given context. */ void Parse_DoVar(VarAssign *var, GNode *ctxt) { const char *avalue; /* actual value (maybe expanded) */ void *avalue_freeIt; VarCheckSyntax(var->op, var->value, ctxt); if (VarAssign_Eval(var->varname, var->op, var->value, ctxt, &avalue, &avalue_freeIt)) VarAssignSpecial(var->varname, avalue); free(avalue_freeIt); free(var->varname); } -/* - * 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. - */ +/* See if the command possibly calls a sub-make by using the variable + * expressions ${.MAKE}, ${MAKE} or the plain word "make". */ static Boolean -ParseMaybeSubMake(const char *cmd) +MaybeSubMake(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) + const char *start; + + for (start = cmd; *start != '\0'; start++) { + const char *p = start; + char endc; + + /* XXX: What if progname != "make"? */ + if (p[0] == 'm' && p[1] == 'a' && p[2] == 'k' && p[3] == 'e') + if (start == cmd || !ch_isalnum(p[-1])) + if (!ch_isalnum(p[4])) + return TRUE; + + if (*p != '$') continue; - if ((ptr == cmd || !ch_isalnum(ptr[-1])) - && !ch_isalnum(ptr[vals[i].len])) - return TRUE; + p++; + + if (*p == '{') + endc = '}'; + else if (*p == '(') + endc = ')'; + else + continue; + p++; + + if (*p == '.') /* Accept either ${.MAKE} or ${MAKE}. */ + p++; + + if (p[0] == 'M' && p[1] == 'A' && p[2] == 'K' && p[3] == 'E') + if (p[4] == endc) + return TRUE; } return FALSE; } /* Append the command to the target node. * * The node may be marked as a submake node if the command is determined to * be that. */ static void ParseAddCmd(GNode *gn, char *cmd) { /* Add to last (ie current) cohort for :: targets */ if ((gn->type & OP_DOUBLEDEP) && gn->cohorts->last != NULL) gn = gn->cohorts->last->datum; /* if target already supplied, ignore commands */ if (!(gn->type & OP_HAS_COMMANDS)) { Lst_Append(gn->commands, cmd); - if (ParseMaybeSubMake(cmd)) + if (MaybeSubMake(cmd)) gn->type |= OP_SUBMAKE; ParseMark(gn); } else { #if 0 /* XXX: We cannot do this until we fix the tree */ Lst_Append(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, (size_t)gn->lineno, PARSE_WARNING, "using previous script for \"%s\" defined here", gn->name); #endif } } /* Add a directory to the path searched for included makefiles bracketed * by double-quotes. */ void Parse_AddIncludeDir(const char *dir) { (void)Dir_AddDir(parseIncPath, dir); } -/* Push to another file. +/* Handle one of the .[-ds]include directives by remembering the current file + * and pushing the included file on the stack. After the included file has + * finished, parsing continues with the including file; see Parse_SetInput + * and ParseEOF. * - * The input is the line minus the '.'. A file spec is a string enclosed in - * <> or "". The <> file is looked for only in sysIncPath. The "" file is - * first searched in the parsedir and then in the directories specified by - * the -I command line options. + * System includes are looked up in sysIncPath, any other includes are looked + * up in the parsedir and then in the directories specified by the -I command + * line options. */ static void -Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) +Parse_include_file(char *file, Boolean isSystem, Boolean depinc, Boolean silent) { struct loadedfile *lf; char *fullname; /* full pathname of file */ char *newName; - char *prefEnd, *incdir; + char *slash, *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 + * Include files contained in double-quotes are first searched * 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. + * we can locate the file. */ incdir = bmake_strdup(CurFile()->fname); - prefEnd = strrchr(incdir, '/'); - if (prefEnd != NULL) { - *prefEnd = '\0'; + slash = strrchr(incdir, '/'); + if (slash != NULL) { + *slash = '\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) + slash = strrchr(incdir + 1, '/'); + if (slash == NULL || strcmp(slash, "/..") == 0) break; - *prefEnd = '\0'; + *slash = '\0'; } newName = str_concat3(incdir, "/", file + i); 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; + const char *suff; SearchPath *suffPath = NULL; if ((suff = strrchr(file, '.'))) { suffPath = Suff_GetPath(suff); - if (suffPath != NULL) { + if (suffPath != NULL) fullname = Dir_FindFile(file, suffPath); - } } if (fullname == NULL) { fullname = Dir_FindFile(file, parseIncPath); - if (fullname == NULL) { + 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 */ SearchPath *path = Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath; fullname = Dir_FindFile(file, path); } 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); /* Start reading from this file next */ Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf); CurFile()->lf = lf; if (depinc) doing_depend = depinc; /* only turn it on */ } 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'; + Boolean silent = *line != 'i'; char *file = line + (silent ? 8 : 7); /* Skip to delimiter character so we know where to look */ - while (*file == ' ' || *file == '\t') - file++; + pp_skip_hspace(&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 == '<') { + if (*file == '<') endc = '>'; - } else { + 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); + "Unclosed .include filename. '%c' expected", endc); return; } + *cp = '\0'; /* - * Substitute for any variables in the file name before trying to - * find the thing. + * Substitute for any variables in the filename before trying to + * find the file. */ (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &file); /* TODO: handle errors */ Parse_include_file(file, endc == '>', *line == 'd', silent); free(file); } /* Split filename into dirname + basename, then assign these to the * given variables. */ static void SetFilenameVars(const char *filename, const char *dirvar, const char *filevar) { const char *slash, *dirname, *basename; void *freeIt; slash = strrchr(filename, '/'); if (slash == NULL) { dirname = curdir; basename = filename; freeIt = NULL; } else { dirname = freeIt = bmake_strsedup(filename, slash); basename = slash + 1; } Var_Set(dirvar, dirname, VAR_GLOBAL); Var_Set(filevar, basename, VAR_GLOBAL); DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n", __func__, dirvar, dirname, filevar, basename); free(freeIt); } /* Return the immediately including file. * * This is made complicated since the .for loop is implemented as a special * kind of .include; see For_Run. */ static const char * GetActuallyIncludingFile(void) { size_t i; const IFile *incs = GetInclude(0); for (i = includes.len; i >= 2; i--) if (!incs[i - 1].fromForLoop) return incs[i - 2].fname; return NULL; } /* Set .PARSEDIR, .PARSEFILE, .INCLUDEDFROMDIR and .INCLUDEDFROMFILE. */ static void ParseSetParseFile(const char *filename) { const char *including; SetFilenameVars(filename, ".PARSEDIR", ".PARSEFILE"); including = GetActuallyIncludingFile(); if (including != NULL) { SetFilenameVars(including, ".INCLUDEDFROMDIR", ".INCLUDEDFROMFILE"); } else { Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL); Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL); } } static Boolean StrContainsWord(const char *str, const char *word) { size_t strLen = strlen(str); size_t wordLen = strlen(word); const char *p, *end; if (strLen < wordLen) return FALSE; /* str is too short to contain word */ end = str + strLen - wordLen; for (p = str; p != NULL; p = strchr(p, ' ')) { if (*p == ' ') p++; if (p > end) return FALSE; /* cannot contain word */ if (memcmp(p, word, wordLen) == 0 && (p[wordLen] == '\0' || p[wordLen] == ' ')) return TRUE; } return FALSE; } /* XXX: Searching through a set of words with this linear search is * inefficient for variables that contain thousands of words. */ +/* XXX: The paths in this list don't seem to be normalized in any way. */ static Boolean VarContainsWord(const char *varname, const char *word) { void *val_freeIt; const char *val = Var_Value(varname, VAR_GLOBAL, &val_freeIt); Boolean found = val != NULL && StrContainsWord(val, word); bmake_free(val_freeIt); return found; } /* Track the makefiles we read - so makefiles can set dependencies on them. - * Avoid adding anything more than once. */ + * Avoid adding anything more than once. + * + * Time complexity: O(n) per call, in total O(n^2), where n is the number + * of makefiles that have been loaded. */ static void ParseTrackInput(const char *name) { if (!VarContainsWord(MAKE_MAKEFILES, name)) Var_Append(MAKE_MAKEFILES, name, VAR_GLOBAL); } -/* Start Parsing from the given source. +/* Start parsing from the given source. * * The given file is added to the includes stack. */ void Parse_SetInput(const char *name, int line, int fd, char *(*nextbuf)(void *, size_t *), void *arg) { IFile *curFile; char *buf; size_t len; Boolean fromForLoop = name == NULL; if (fromForLoop) name = CurFile()->fname; else ParseTrackInput(name); if (DEBUG(PARSE)) debug_printf("%s: file %s, line %d, fd %d, nextbuf %s, arg %p\n", __func__, name, line, fd, nextbuf == loadedfile_nextbuf ? "loadedfile" : "other", arg); if (fd == -1 && nextbuf == NULL) /* sanity */ return; curFile = Vector_Push(&includes); - - /* - * 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->fromForLoop = fromForLoop; curFile->lineno = line; curFile->first_lineno = line; curFile->nextbuf = nextbuf; curFile->nextbuf_arg = arg; curFile->lf = NULL; curFile->depending = doing_depend; /* restore this on EOF */ 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->buf_freeIt = buf; curFile->buf_ptr = buf; curFile->buf_end = buf + len; curFile->cond_depth = Cond_save_depth(); ParseSetParseFile(name); } /* Check if the directive is an include directive. */ static Boolean IsInclude(const char *dir, Boolean sysv) { if (dir[0] == 's' || dir[0] == '-' || (dir[0] == 'd' && !sysv)) dir++; if (strncmp(dir, "include", 7) != 0) return FALSE; /* Space is not mandatory for BSD .include */ return !sysv || ch_isspace(dir[7]); } #ifdef SYSVINCLUDE /* Check if the line is a SYSV include directive. */ static Boolean IsSysVInclude(const char *line) { const char *p; if (!IsInclude(line, TRUE)) return FALSE; /* Avoid interpreting a dependency line as an include */ for (p = line; (p = strchr(p, ':')) != NULL;) { - if (*++p == '\0') { - /* end of line -> dependency */ + + /* end of line -> it's a dependency */ + if (*++p == '\0') return FALSE; - } - if (*p == ':' || ch_isspace(*p)) { - /* :: operator or ': ' -> dependency */ + + /* '::' operator or ': ' -> it's a dependency */ + if (*p == ':' || ch_isspace(*p)) return FALSE; - } } return TRUE; } /* Push to another file. The line points to the word "include". */ static void ParseTraditionalInclude(char *line) { char *cp; /* current position in file spec */ - int done = 0; - int silent = line[0] != 'i'; + Boolean done = FALSE; + Boolean silent = line[0] != 'i'; char *file = line + (silent ? 8 : 7); char *all_files; DEBUG2(PARSE, "%s: %s\n", __func__, file); pp_skip_whitespace(&file); /* * Substitute for any variables in the file name before trying to * find the thing. */ (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &all_files); /* TODO: handle errors */ if (*file == '\0') { Parse_Error(PARSE_FATAL, "Filename missing from \"include\""); goto out; } for (file = all_files; !done; file = cp + 1) { /* Skip to end of line or next whitespace */ for (cp = file; *cp && !ch_isspace(*cp); cp++) continue; - if (*cp) + if (*cp != '\0') *cp = '\0'; else - done = 1; + done = TRUE; Parse_include_file(file, FALSE, FALSE, silent); } out: free(all_files); } #endif #ifdef GMAKEEXPORT /* Parse "export =", and actually export it. */ static void ParseGmakeExport(char *line) { char *variable = line + 6; char *value; DEBUG2(PARSE, "%s: %s\n", __func__, variable); pp_skip_whitespace(&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. */ (void)Var_Subst(value, VAR_CMDLINE, VARE_WANTRES, &value); /* TODO: handle errors */ setenv(variable, value, 1); free(value); } #endif /* 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. + * include file or a .for loop, the includes stack is popped and things set + * up to go back to reading the previous file at the previous location. * * Results: * TRUE to continue parsing, i.e. it had only reached the end of an * included file, FALSE if the main file has been parsed completely. */ static Boolean ParseEOF(void) { char *ptr; size_t len; IFile *curFile = CurFile(); assert(curFile->nextbuf != NULL); doing_depend = curFile->depending; /* restore this */ /* get next input buffer, if any */ ptr = curFile->nextbuf(curFile->nextbuf_arg, &len); curFile->buf_ptr = ptr; curFile->buf_freeIt = ptr; - curFile->buf_end = ptr + len; + curFile->buf_end = ptr + len; /* XXX: undefined behavior if ptr == NULL */ curFile->lineno = curFile->first_lineno; - if (ptr != NULL) { - /* Iterate again */ - return TRUE; - } + if (ptr != NULL) + return TRUE; /* Iterate again */ /* 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->buf_freeIt); Vector_Pop(&includes); if (includes.len == 0) { /* 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 FALSE; } curFile = CurFile(); DEBUG2(PARSE, "ParseEOF: returning to file %s, line %d\n", curFile->fname, curFile->lineno); ParseSetParseFile(curFile->fname); return TRUE; } -#define PARSE_RAW 1 -#define PARSE_SKIP 2 +typedef enum GetLineMode { + PARSE_NORMAL, + PARSE_RAW, + PARSE_SKIP +} GetLineMode; static char * -ParseGetLine(int flags) +ParseGetLine(GetLineMode mode) { 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->buf_ptr; ptr = line; line_end = line; escaped = NULL; comment = NULL; for (;;) { /* XXX: can buf_end ever be null? */ if (cf->buf_end != NULL && ptr == cf->buf_end) { /* end of buffer */ - ch = 0; + ch = '\0'; break; } ch = *ptr; - if (ch == 0 || (ch == '\\' && ptr[1] == 0)) { + if (ch == '\0' || (ch == '\\' && ptr[1] == '\0')) { /* XXX: can buf_end ever be null? */ if (cf->buf_end == NULL) /* End of string (aka for loop) data */ break; /* see if there is more we can parse */ while (ptr++ < cf->buf_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; } } + /* XXX: Can cf->nextbuf ever be NULL? */ 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 (!ch_isspace(ch)) /* We are not interested in trailing whitespace */ line_end = ptr; } /* Save next 'to be processed' location */ cf->buf_ptr = ptr; /* Check we have a non-comment, non-blank line */ if (line_end == line || comment == line) { - if (ch == 0) + if (ch == '\0') /* At end of file */ return NULL; /* Parse another line */ continue; } /* We now have a line of data */ - *line_end = 0; + *line_end = '\0'; - if (flags & PARSE_RAW) { + if (mode == PARSE_RAW) { /* Leave '\' (etc) in line buffer (eg 'for' lines) */ return line; } - if (flags & PARSE_SKIP) { + if (mode == 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; + *line_end = '\0'; } /* If we didn't see a '\\' then the in-situ data is fine */ if (escaped == NULL) return line; /* Remove escapes from '\n' and '#' */ tp = ptr = escaped; escaped = line; for (; ; *tp++ = ch) { ch = *ptr++; if (ch != '\\') { - if (ch == 0) + if (ch == '\0') break; continue; } ch = *ptr++; - if (ch == 0) { + 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++; + /* Escaped '\n' -- replace following whitespace with a single ' '. */ + pp_skip_hspace(&ptr); ch = ' '; } /* Delete any trailing spaces - eg from empty continuations */ while (tp > escaped && ch_isspace(tp[-1])) tp--; - *tp = 0; + *tp = '\0'; return line; } /* Read an entire line from the input file. Called only by Parse_File. * * Results: - * A line without its newline. - * - * Side Effects: - * Only those associated with reading a character + * A line without its newline and without any trailing whitespace. */ static char * ParseReadLine(void) { char *line; /* Result */ int lineno; /* Saved line # */ int rval; for (;;) { - line = ParseGetLine(0); + line = ParseGetLine(PARSE_NORMAL); 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_EvalLine(line)) { case COND_SKIP: /* Skip to next conditional that evaluates to COND_PARSE. */ do { line = ParseGetLine(PARSE_SKIP); } while (line && Cond_EvalLine(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); 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; } } static void FinishDependencyGroup(void) { - if (targets != NULL) { - GNodeListNode *ln; - for (ln = targets->first; ln != NULL; ln = ln->next) { - GNode *gn = ln->datum; + GNodeListNode *ln; - Suff_EndTransform(gn); + if (targets == NULL) + return; - /* Mark the target as already having commands if it does, to - * keep from having shell commands on multiple dependency lines. */ - if (!Lst_IsEmpty(gn->commands)) - gn->type |= OP_HAS_COMMANDS; - } + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; - Lst_Free(targets); - targets = NULL; + Suff_EndTransform(gn); + + /* Mark the target as already having commands if it does, to + * keep from having shell commands on multiple dependency lines. */ + if (!Lst_IsEmpty(gn->commands)) + gn->type |= OP_HAS_COMMANDS; } + + Lst_Free(targets); + targets = NULL; } /* Add the command to each target from the current dependency spec. */ static void ParseLine_ShellCommand(const char *p) { cpp_skip_whitespace(&p); if (*p == '\0') return; /* skip empty commands */ if (targets == NULL) { Parse_Error(PARSE_FATAL, "Unassociated shell command \"%s\"", p); return; } { char *cmd = bmake_strdup(p); GNodeListNode *ln; for (ln = targets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; ParseAddCmd(gn, cmd); } #ifdef CLEANUP Lst_Append(targCmds, cmd); #endif } } static Boolean ParseDirective(char *line) { char *cp; 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 '.'. + * Lines that begin with '.' can be pretty much anything: + * - directives like '.include' or '.if', + * - suffix rules like '.c.o:', + * - dependencies for filenames that start with '.', + * - variable assignments like '.tmp=value'. */ cp = line + 1; pp_skip_whitespace(&cp); if (IsInclude(cp, FALSE)) { ParseDoInclude(cp); return TRUE; } if (strncmp(cp, "undef", 5) == 0) { const char *varname; cp += 5; pp_skip_whitespace(&cp); varname = cp; for (; !ch_isspace(*cp) && *cp != '\0'; cp++) continue; *cp = '\0'; Var_Delete(varname, VAR_GLOBAL); /* TODO: undefine all variables, not only the first */ /* TODO: use Str_Words, like everywhere else */ return TRUE; } else if (strncmp(cp, "export", 6) == 0) { cp += 6; pp_skip_whitespace(&cp); Var_Export(cp, TRUE); return TRUE; } else if (strncmp(cp, "unexport", 8) == 0) { Var_UnExport(cp); return TRUE; } else if (strncmp(cp, "info", 4) == 0 || strncmp(cp, "error", 5) == 0 || strncmp(cp, "warning", 7) == 0) { if (ParseMessage(cp)) return TRUE; } } return FALSE; } static Boolean ParseVarassign(const char *line) { VarAssign var; - if (Parse_IsVar(line, &var)) { - FinishDependencyGroup(); - Parse_DoVar(&var, VAR_GLOBAL); - return TRUE; - } - return FALSE; + + if (!Parse_IsVar(line, &var)) + return FALSE; + + FinishDependencyGroup(); + Parse_DoVar(&var, VAR_GLOBAL); + return TRUE; } static char * FindSemicolon(char *p) { int level = 0; for (; *p != '\0'; p++) { if (*p == '\\' && p[1] != '\0') { p++; continue; } - if (*p == '$' && (p[1] == '(' || p[1] == '{')) { + if (*p == '$' && (p[1] == '(' || p[1] == '{')) level++; - continue; - } - - if (level > 0 && (*p == ')' || *p == '}')) { + else if (level > 0 && (*p == ')' || *p == '}')) level--; - continue; - } - - if (level == 0 && *p == ';') { + else if (level == 0 && *p == ';') break; - } } return p; } /* dependency -> target... op [source...] * op -> ':' | '::' | '!' */ static void ParseDependency(char *line) { VarEvalFlags eflags; char *expanded_line; const char *shellcmd = NULL; /* * For some reason - probably to make the parser impossible - * a ';' can be used to separate commands from dependencies. * Attempt to avoid ';' inside substitution patterns. */ { char *semicolon = FindSemicolon(line); if (*semicolon != '\0') { /* Terminate the dependency list at the ';' */ *semicolon = '\0'; shellcmd = semicolon + 1; } } /* * We now know it's a dependency line so it needs to have all * variables expanded before being parsed. * * XXX: Ideally the dependency line would first be split into * its left-hand side, dependency operator and right-hand side, * and then each side would be expanded on its own. This would * allow for the left-hand side to allow only defined variables * and to allow variables on the right-hand side to be undefined * as well. * * Parsing the line first would also prevent that targets * generated from variable expressions are interpreted as the - * dependency operator, such as in "target${:U:} middle: source", + * dependency operator, such as in "target${:U\:} middle: source", * in which the middle is interpreted as a source, not a target. */ /* In lint mode, allow undefined variables to appear in * dependency lines. * * Ideally, only the right-hand side would allow undefined - * variables since it is common to have no dependencies. + * variables since it is common to have optional dependencies. * Having undefined variables on the left-hand side is more * unusual though. Since both sides are expanded in a single * pass, there is not much choice what to do here. * * In normal mode, it does not matter whether undefined * variables are allowed or not since as of 2020-09-14, * Var_Parse does not print any parse errors in such a case. * It simply returns the special empty string var_Error, * which cannot be detected in the result of Var_Subst. */ - eflags = DEBUG(LINT) ? VARE_WANTRES : VARE_UNDEFERR | VARE_WANTRES; + eflags = opts.lint ? VARE_WANTRES : VARE_WANTRES | VARE_UNDEFERR; (void)Var_Subst(line, VAR_CMDLINE, eflags, &expanded_line); /* TODO: handle errors */ /* Need a fresh list for the target nodes */ if (targets != NULL) Lst_Free(targets); targets = Lst_New(); ParseDoDependency(expanded_line); free(expanded_line); if (shellcmd != NULL) ParseLine_ShellCommand(shellcmd); } static void ParseLine(char *line) { if (ParseDirective(line)) return; if (*line == '\t') { ParseLine_ShellCommand(line + 1); return; } #ifdef SYSVINCLUDE if (IsSysVInclude(line)) { /* * It's an S3/S5-style "include". */ ParseTraditionalInclude(line); return; } #endif #ifdef GMAKEEXPORT if (strncmp(line, "export", 6) == 0 && ch_isspace(line[6]) && strchr(line, ':') == NULL) { /* * It's a Gmake "export". */ ParseGmakeExport(line); return; } #endif if (ParseVarassign(line)) return; FinishDependencyGroup(); ParseDependency(line); } -/* Parse a top-level makefile into its component parts, incorporating them - * into the global dependency graph. +/* Parse a top-level makefile, incorporating its content into the global + * dependency graph. * * Input: * name The name of the file being read * fd The open file to parse; will be closed at the end */ void Parse_File(const char *name, int fd) { char *line; /* the line we're working on */ struct loadedfile *lf; lf = loadfile(name, fd); assert(targets == NULL); - fatals = 0; if (name == NULL) name = "(stdin)"; Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf); CurFile()->lf = lf; do { while ((line = ParseReadLine()) != NULL) { DEBUG2(PARSE, "ParseReadLine (%d): '%s'\n", CurFile()->lineno, line); ParseLine(line); } /* * Reached EOF, but it may be just EOF of an include file... */ } while (ParseEOF()); FinishDependencyGroup(); - if (fatals) { + if (fatals != 0) { (void)fflush(stdout); (void)fprintf(stderr, "%s: Fatal errors encountered -- cannot continue", progname); PrintOnError(NULL, NULL); exit(1); } } /* Initialize the parsing module. */ void Parse_Init(void) { mainNode = NULL; parseIncPath = Lst_New(); sysIncPath = Lst_New(); defSysIncPath = Lst_New(); Vector_Init(&includes, sizeof(IFile)); #ifdef CLEANUP targCmds = Lst_New(); #endif } /* Clean up the parsing module. */ void Parse_End(void) { #ifdef CLEANUP Lst_Destroy(targCmds, free); assert(targets == NULL); Lst_Destroy(defSysIncPath, Dir_Destroy); Lst_Destroy(sysIncPath, Dir_Destroy); Lst_Destroy(parseIncPath, Dir_Destroy); assert(includes.len == 0); Vector_Done(&includes); #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. - * - *----------------------------------------------------------------------- +/* + * Return a list containing the single main target to create. + * If no such target exists, we Punt with an obnoxious error message. */ GNodeList * Parse_MainName(void) { GNodeList *mainList; mainList = Lst_New(); - if (mainNode == NULL) { + if (mainNode == NULL) Punt("no target to make."); - /*NOTREACHED*/ - } else if (mainNode->type & OP_DOUBLEDEP) { + + if (mainNode->type & OP_DOUBLEDEP) { Lst_Append(mainList, mainNode); Lst_AppendAll(mainList, mainNode->cohorts); } else Lst_Append(mainList, mainNode); + Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL); + return mainList; } int Parse_GetFatals(void) { return fatals; } Index: head/contrib/bmake/str.c =================================================================== --- head/contrib/bmake/str.c (revision 367862) +++ head/contrib/bmake/str.c (revision 367863) @@ -1,374 +1,368 @@ -/* $NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $ */ +/* $NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig 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. */ #include "make.h" /* "@(#)str.c 5.8 (Berkeley) 6/1/90" */ -MAKE_RCSID("$NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $"); +MAKE_RCSID("$NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig Exp $"); /* Return the concatenation of s1 and s2, freshly allocated. */ char * str_concat2(const char *s1, const char *s2) { size_t len1 = strlen(s1); size_t len2 = strlen(s2); char *result = bmake_malloc(len1 + len2 + 1); memcpy(result, s1, len1); memcpy(result + len1, s2, len2 + 1); return result; } /* Return the concatenation of s1, s2 and s3, freshly allocated. */ char * str_concat3(const char *s1, const char *s2, const char *s3) { size_t len1 = strlen(s1); size_t len2 = strlen(s2); size_t len3 = strlen(s3); char *result = bmake_malloc(len1 + len2 + len3 + 1); memcpy(result, s1, len1); memcpy(result + len1, s2, len2); memcpy(result + len1 + len2, s3, len3 + 1); return result; } /* Return the concatenation of s1, s2, s3 and s4, freshly allocated. */ char * str_concat4(const char *s1, const char *s2, const char *s3, const char *s4) { size_t len1 = strlen(s1); size_t len2 = strlen(s2); size_t len3 = strlen(s3); size_t len4 = strlen(s4); char *result = bmake_malloc(len1 + len2 + len3 + len4 + 1); memcpy(result, s1, len1); memcpy(result + len1, s2, len2); memcpy(result + len1 + len2, s3, len3); memcpy(result + len1 + len2 + len3, s4, len4 + 1); return result; } /* Fracture a string into an array of words (as delineated by tabs or spaces) - * taking quotation marks into account. Leading tabs/spaces are ignored. + * taking quotation marks into account. * * If expand is TRUE, quotes are removed and escape sequences such as \r, \t, - * etc... are expanded. In this case, the return value is NULL on parse - * errors. + * etc... are expanded. In this case, return NULL on parse errors. * - * Returns the fractured words, which must be freed later using Words_Free. - * If expand was TRUE and there was a parse error, words is NULL, and in that - * case, nothing needs to be freed. + * Returns the fractured words, which must be freed later using Words_Free, + * unless the returned Words.words was NULL. */ Words Str_Words(const char *str, Boolean expand) { size_t str_len; char *words_buf; size_t words_cap; char **words; size_t words_len; char inquote; char *word_start; char *word_end; const char *str_p; - /* skip leading space chars. */ - for (; *str == ' ' || *str == '\t'; ++str) - continue; + /* XXX: why only hspace, not whitespace? */ + cpp_skip_hspace(&str); /* skip leading space chars. */ /* words_buf holds the words, separated by '\0'. */ str_len = strlen(str); words_buf = bmake_malloc(strlen(str) + 1); words_cap = str_len / 5 > 50 ? str_len / 5 : 50; words = bmake_malloc((words_cap + 1) * sizeof(char *)); /* * copy the string; at the same time, parse backslashes, * quotes and build the word list. */ words_len = 0; inquote = '\0'; word_start = words_buf; word_end = words_buf; for (str_p = str;; ++str_p) { char ch = *str_p; switch (ch) { case '"': case '\'': if (inquote) { if (inquote == ch) inquote = '\0'; else break; } else { inquote = ch; /* Don't miss "" or '' */ if (word_start == NULL && str_p[1] == inquote) { if (!expand) { word_start = word_end; *word_end++ = ch; } else word_start = word_end + 1; str_p++; inquote = '\0'; break; } } if (!expand) { if (word_start == NULL) word_start = word_end; *word_end++ = ch; } continue; case ' ': case '\t': case '\n': if (inquote) break; if (word_start == NULL) continue; /* FALLTHROUGH */ case '\0': /* * end of a token -- make sure there's enough words * space and save off a pointer. */ if (word_start == NULL) goto done; *word_end++ = '\0'; if (words_len == words_cap) { size_t new_size; words_cap *= 2; /* ramp up fast */ new_size = (words_cap + 1) * sizeof(char *); words = bmake_realloc(words, new_size); } words[words_len++] = word_start; word_start = NULL; if (ch == '\n' || ch == '\0') { if (expand && inquote) { free(words); free(words_buf); return (Words){ NULL, 0, NULL }; } goto done; } continue; case '\\': if (!expand) { if (word_start == NULL) word_start = word_end; *word_end++ = '\\'; /* catch '\' at end of line */ if (str_p[1] == '\0') continue; ch = *++str_p; break; } switch (ch = *++str_p) { case '\0': case '\n': /* hmmm; fix it up as best we can */ ch = '\\'; - --str_p; + str_p--; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; } break; } if (word_start == NULL) word_start = word_end; *word_end++ = ch; } done: - words[words_len] = NULL; + words[words_len] = NULL; /* useful for argv */ return (Words){ words, words_len, words_buf }; } /* * Str_Match -- Test if a string matches a pattern like "*.[ch]". + * The following special characters are known *?\[] (as in fnmatch(3)). * - * XXX this function does not detect or report malformed patterns. - * - * Results: - * Non-zero is returned if string matches the pattern, 0 otherwise. The - * matching operation permits the following special characters in the - * pattern: *?\[] (as in fnmatch(3)). - * - * Side effects: None. + * XXX: this function does not detect or report malformed patterns. */ Boolean Str_Match(const char *str, const char *pat) { for (;;) { /* * See if we're at the end of both the pattern and the - * string. If, we succeeded. If we're at the end of the + * string. If so, we succeeded. If we're at the end of the * pattern but not at the end of the string, we failed. */ - if (*pat == 0) - return *str == 0; - if (*str == 0 && *pat != '*') + if (*pat == '\0') + return *str == '\0'; + if (*str == '\0' && *pat != '*') return FALSE; /* * A '*' in the pattern matches any substring. We handle this * by calling ourselves for each suffix of the string. */ if (*pat == '*') { pat++; while (*pat == '*') pat++; - if (*pat == 0) + if (*pat == '\0') return TRUE; - while (*str != 0) { + while (*str != '\0') { if (Str_Match(str, pat)) return TRUE; str++; } return FALSE; } /* A '?' in the pattern matches any single character. */ if (*pat == '?') goto thisCharOK; /* * A '[' in the pattern matches a character from a list. * The '[' is followed by the list of acceptable characters, * or by ranges (two characters separated by '-'). In these * character lists, the backslash is an ordinary character. */ if (*pat == '[') { Boolean neg = pat[1] == '^'; pat += neg ? 2 : 1; for (;;) { - if (*pat == ']' || *pat == 0) { + if (*pat == ']' || *pat == '\0') { if (neg) break; return FALSE; } + /* XXX: This naive comparison makes the parser + * for the pattern dependent on the actual of + * the string. This is unpredictable. */ if (*pat == *str) break; if (pat[1] == '-') { - if (pat[2] == 0) + if (pat[2] == '\0') return neg; if (*pat <= *str && pat[2] >= *str) break; if (*pat >= *str && pat[2] <= *str) break; pat += 2; } pat++; } - if (neg && *pat != ']' && *pat != 0) + if (neg && *pat != ']' && *pat != '\0') return FALSE; - while (*pat != ']' && *pat != 0) + while (*pat != ']' && *pat != '\0') pat++; - if (*pat == 0) + if (*pat == '\0') pat--; goto thisCharOK; } /* * A backslash in the pattern matches the character following * it exactly. */ if (*pat == '\\') { pat++; - if (*pat == 0) + if (*pat == '\0') return FALSE; } if (*pat != *str) return FALSE; thisCharOK: pat++; str++; } } Index: head/contrib/bmake/suff.c =================================================================== --- head/contrib/bmake/suff.c (revision 367862) +++ head/contrib/bmake/suff.c (revision 367863) @@ -1,2112 +1,2033 @@ -/* $NetBSD: suff.c,v 1.230 2020/10/31 11:54:33 rillig Exp $ */ +/* $NetBSD: suff.c,v 1.247 2020/11/16 23:27:41 rillig 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. */ -/*- - * suff.c -- - * Functions to maintain suffix lists and find implicit dependents - * using suffix transformation rules +/* + * Maintain suffix lists and find implicit dependents using suffix + * transformation rules such as ".c.o". * * Interface: - * Suff_Init Initialize all things to do with suffixes. + * Suff_Init Initialize the module. * - * Suff_End Clean up the module + * Suff_End Clean up 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_DoPaths Extend the search path of each suffix to include the + * default search path. * * Suff_ClearSuffixes - * Clear out all the suffixes and defined - * transformations. + * Clear out all the suffixes and transformations. * * Suff_IsTransform - * Return TRUE if the passed string is the lhs - * of a transformation rule. + * See if the passed string is 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. + * Add another transformation to the suffix graph. * * 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 "make.h" #include "dir.h" /* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ -MAKE_RCSID("$NetBSD: suff.c,v 1.230 2020/10/31 11:54:33 rillig Exp $"); +MAKE_RCSID("$NetBSD: suff.c,v 1.247 2020/11/16 23:27:41 rillig Exp $"); #define SUFF_DEBUG0(text) DEBUG0(SUFF, text) #define SUFF_DEBUG1(fmt, arg1) DEBUG1(SUFF, fmt, arg1) #define SUFF_DEBUG2(fmt, arg1, arg2) DEBUG2(SUFF, fmt, arg1, arg2) -#define SUFF_DEBUG3(fmt, arg1, arg2, arg3) DEBUG3(SUFF, fmt, arg1, arg2, arg3) -#define SUFF_DEBUG4(fmt, arg1, arg2, arg3, arg4) \ - DEBUG4(SUFF, fmt, arg1, arg2, arg3, arg4) typedef List SuffList; typedef ListNode SuffListNode; typedef List SrcList; typedef ListNode SrcListNode; static SuffList *sufflist; /* List of suffixes */ #ifdef CLEANUP static SuffList *suffClean; /* List of suffixes to be cleaned */ #endif static SrcList *srclist; /* List of sources */ -static GNodeList *transforms; /* List of transformation rules */ -static int sNum = 0; /* Counter for assigning suffix numbers */ +/* List of transformation rules, such as ".c.o" */ +static GNodeList *transforms; +static int sNum = 0; /* Counter for assigning suffix numbers */ + typedef enum SuffFlags { SUFF_INCLUDE = 0x01, /* One which is #include'd */ SUFF_LIBRARY = 0x02, /* One which contains a library */ SUFF_NULL = 0x04 /* The empty suffix */ /* XXX: Why is SUFF_NULL needed? Wouldn't nameLen == 0 mean the same? */ } SuffFlags; ENUM_FLAGS_RTTI_3(SuffFlags, SUFF_INCLUDE, SUFF_LIBRARY, SUFF_NULL); typedef List SuffListList; -/* - * Structure describing an individual suffix. - */ typedef struct Suff { - char *name; /* The suffix itself, such as ".c" */ - size_t nameLen; /* Length of the name, to avoid strlen calls */ - SuffFlags flags; /* Type of suffix */ - SearchPath *searchPath; /* The path along which files of this suffix - * may be found */ - int sNum; /* The suffix number */ - int refCount; /* Reference count of list membership - * and several other places */ - SuffList *parents; /* Suffixes we have a transformation to */ - SuffList *children; /* Suffixes we have a transformation from */ - SuffListList *ref; /* Lists in which this suffix is referenced */ + /* The suffix itself, such as ".c" */ + char *name; + /* Length of the name, to avoid strlen calls */ + size_t nameLen; + /* Type of suffix */ + SuffFlags flags; + /* The path along which files of this suffix may be found */ + SearchPath *searchPath; + /* The suffix number; TODO: document the purpose of this number */ + int sNum; + /* Reference count of list membership and several other places */ + int refCount; + /* Suffixes we have a transformation to */ + SuffList *parents; + /* Suffixes we have a transformation from */ + SuffList *children; + + /* Lists in which this suffix is referenced. + * XXX: These lists are used nowhere, they are just appended to, for no + * apparent reason. They do have the side effect of increasing refCount + * though. */ + SuffListList *ref; } Suff; /* * 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 + int numChildren; /* Count of existing children (so we don't free * this thing too early or never nuke it) */ #ifdef DEBUG_SRC SrcList *childrenList; #endif } Src; -static Suff *suffNull; /* The NULL suffix for this run */ -static Suff *emptySuff; /* The empty suffix required for POSIX - * single-suffix transformation rules */ +/* TODO: Document the difference between suffNull and emptySuff. */ +/* The NULL suffix for this run */ +static Suff *suffNull; +/* The empty suffix required for POSIX single-suffix transformation rules */ +static Suff *emptySuff; static void SuffFindDeps(GNode *, SrcList *); static void SuffExpandWildcards(GNodeListNode *, GNode *); - /*************** 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 - *----------------------------------------------------------------------- +/* + * See if pref is a prefix of str. + * Return NULL if it ain't, pointer to character in str after prefix if so. */ static const char * SuffStrIsPrefix(const char *pref, const char *str) { while (*str && *pref == *str) { pref++; str++; } - return *pref ? NULL : str; + return *pref != '\0' ? NULL : str; } -/* See if suff is a suffix of str. - * - * Input: - * s possible suffix - * nameLen length of the string to examine - * nameEnd end of the string to examine - * - * Results: - * NULL if it ain't, pointer to the start of suffix in str if it is. +/* + * See if suff is a suffix of name. + * Return NULL if it ain't, pointer to the start of suffix in name if it is. */ static const char * SuffSuffGetSuffix(const Suff *s, size_t nameLen, const char *nameEnd) { const char *p1; /* Pointer into suffix name */ const char *p2; /* Pointer into string being examined */ if (nameLen < s->nameLen) return NULL; /* this string is shorter than the suffix */ p1 = s->name + s->nameLen; p2 = nameEnd; while (p1 >= s->name && *p1 == *p2) { p1--; p2--; } /* XXX: s->name - 1 invokes undefined behavior */ return p1 == s->name - 1 ? p2 + 1 : NULL; } static Boolean SuffSuffIsSuffix(const Suff *suff, size_t nameLen, const char *nameEnd) { return SuffSuffGetSuffix(suff, nameLen, nameEnd) != NULL; } static Suff * FindSuffByNameLen(const char *name, size_t nameLen) { SuffListNode *ln; for (ln = sufflist->first; ln != NULL; ln = ln->next) { Suff *suff = ln->datum; if (suff->nameLen == nameLen && memcmp(suff->name, name, nameLen) == 0) return suff; } return NULL; } static Suff * FindSuffByName(const char *name) { return FindSuffByNameLen(name, strlen(name)); } static GNode * FindTransformByName(const char *name) { GNodeListNode *ln; for (ln = transforms->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; if (strcmp(gn->name, name) == 0) return gn; } return NULL; } - /*********** Maintenance Functions ************/ - static void -SuffUnRef(SuffList *list, Suff *suff) +SuffList_Unref(SuffList *list, Suff *suff) { SuffListNode *ln = Lst_FindDatum(list, suff); if (ln != NULL) { Lst_Remove(list, ln); suff->refCount--; } } /* Free up all memory associated with the given suffix structure. */ static void SuffFree(void *sp) { - Suff *s = sp; + Suff *suff = sp; - if (s == suffNull) + if (suff == suffNull) suffNull = NULL; - if (s == emptySuff) + if (suff == emptySuff) emptySuff = NULL; #if 0 /* 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); + if (suff->refCount != 0) + Punt("Internal error deleting suffix `%s' with refcount = %d", + suff->name, suff->refCount); #endif - Lst_Free(s->ref); - Lst_Free(s->children); - Lst_Free(s->parents); - Lst_Destroy(s->searchPath, Dir_Destroy); + Lst_Free(suff->ref); + Lst_Free(suff->children); + Lst_Free(suff->parents); + Lst_Destroy(suff->searchPath, Dir_Destroy); - free(s->name); - free(s); + free(suff->name); + free(suff); } /* Remove the suffix from the list, and free if it is otherwise unused. */ static void -SuffRemove(SuffList *list, Suff *suff) +SuffList_Remove(SuffList *list, Suff *suff) { - SuffUnRef(list, suff); + SuffList_Unref(list, suff); if (suff->refCount == 0) { - SuffUnRef(sufflist, suff); + /* XXX: can lead to suff->refCount == -1 */ + SuffList_Unref(sufflist, suff); SuffFree(suff); } } /* Insert the suffix into the list, keeping the list ordered by suffix - * numbers. */ + * number. */ static void -SuffInsert(SuffList *list, Suff *suff) +SuffList_Insert(SuffList *list, Suff *suff) { SuffListNode *ln; Suff *listSuff = NULL; for (ln = list->first; ln != NULL; ln = ln->next) { listSuff = ln->datum; if (listSuff->sNum >= suff->sNum) break; } if (ln == NULL) { SUFF_DEBUG2("inserting \"%s\" (%d) at end of list\n", suff->name, suff->sNum); Lst_Append(list, suff); suff->refCount++; Lst_Append(suff->ref, list); } else if (listSuff->sNum != suff->sNum) { - SUFF_DEBUG4("inserting \"%s\" (%d) before \"%s\" (%d)\n", - suff->name, suff->sNum, listSuff->name, listSuff->sNum); + DEBUG4(SUFF, "inserting \"%s\" (%d) before \"%s\" (%d)\n", + suff->name, suff->sNum, listSuff->name, listSuff->sNum); Lst_InsertBefore(list, ln, suff); suff->refCount++; Lst_Append(suff->ref, list); } else { SUFF_DEBUG2("\"%s\" (%d) is already there\n", suff->name, suff->sNum); } } +static void +SuffRelate(Suff *srcSuff, Suff *targSuff) +{ + SuffList_Insert(targSuff->children, srcSuff); + SuffList_Insert(srcSuff->parents, targSuff); +} + static Suff * SuffNew(const char *name) { - Suff *s = bmake_malloc(sizeof(Suff)); + Suff *suff = bmake_malloc(sizeof *suff); - s->name = bmake_strdup(name); - s->nameLen = strlen(s->name); - s->searchPath = Lst_New(); - s->children = Lst_New(); - s->parents = Lst_New(); - s->ref = Lst_New(); - s->sNum = sNum++; - s->flags = 0; - s->refCount = 1; + suff->name = bmake_strdup(name); + suff->nameLen = strlen(suff->name); + suff->searchPath = Lst_New(); + suff->children = Lst_New(); + suff->parents = Lst_New(); + suff->ref = Lst_New(); + suff->sNum = sNum++; + suff->flags = 0; + suff->refCount = 1; /* XXX: why 1? It's not assigned anywhere yet. */ - return s; + return suff; } -/* 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. */ +/* + * 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 when a line '.SUFFIXES:' with an empty suffixes list is + * encountered in a makefile. + */ void Suff_ClearSuffixes(void) { #ifdef CLEANUP Lst_MoveAll(suffClean, sufflist); #endif sufflist = Lst_New(); sNum = 0; if (suffNull) SuffFree(suffNull); emptySuff = suffNull = SuffNew(""); Dir_Concat(suffNull->searchPath, dirSearchPath); suffNull->flags = SUFF_NULL; } /* Parse a transformation string such as ".c.o" to find its two component * suffixes (the source ".c" and the target ".o"). If there are no such * suffixes, try a single-suffix transformation as well. * * Return TRUE if the string is a valid transformation. */ static Boolean SuffParseTransform(const char *str, Suff **out_src, Suff **out_targ) { SuffListNode *ln; Suff *singleSrc = 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 (ln = sufflist->first; ln != NULL; ln = ln->next) { Suff *src = ln->datum; if (SuffStrIsPrefix(src->name, str) == NULL) continue; if (str[src->nameLen] == '\0') { singleSrc = src; } else { Suff *targ = FindSuffByName(str + src->nameLen); if (targ != NULL) { *out_src = src; *out_targ = targ; return TRUE; } } } if (singleSrc != 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? */ *out_src = singleSrc; *out_targ = suffNull; return TRUE; } return FALSE; } /* Return TRUE if the given string is a transformation rule, that is, a - * concatenation of two known suffixes. */ + * concatenation of two known suffixes such as ".c.o" or a single suffix + * such as ".o". */ Boolean Suff_IsTransform(const char *str) { Suff *src, *targ; return SuffParseTransform(str, &src, &targ); } /* Add the transformation rule to the list of rules and place the * transformation itself in the graph. * * The transformation is linked to the two suffixes mentioned in the name. * * Input: * name must have the form ".from.to" or just ".from" * * Results: * The created or existing transformation node in the transforms list */ GNode * Suff_AddTransform(const char *name) { - GNode *gn; /* GNode of transformation rule */ - Suff *s, /* source suffix */ - *t; /* target suffix */ - Boolean ok; + Suff *srcSuff; + Suff *targSuff; - gn = FindTransformByName(name); + GNode *gn = FindTransformByName(name); if (gn == NULL) { /* * Make a new graph node for the transformation. It will be filled in * by the Parse module. */ - gn = Targ_NewGN(name); + gn = GNode_New(name); Lst_Append(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. */ Lst_Free(gn->commands); Lst_Free(gn->children); gn->commands = Lst_New(); gn->children = Lst_New(); } gn->type = OP_TRANSFORM; - ok = SuffParseTransform(name, &s, &t); - assert(ok); - (void)ok; + { + Boolean ok = SuffParseTransform(name, &srcSuff, &targSuff); + assert(ok); + (void)ok; + } /* * link the two together in the proper relationship and order */ SUFF_DEBUG2("defining transformation from `%s' to `%s'\n", - s->name, t->name); - SuffInsert(t->children, s); - SuffInsert(s->parents, t); + srcSuff->name, targSuff->name); + SuffRelate(srcSuff, targSuff); return gn; } /* Handle the finish of a transformation definition, removing the * transformation from the graph if it has neither commands nor sources. * * If the node has no commands or children, the children and parents lists * of the affected suffixes are altered. * * Input: * gn Node for transformation */ void Suff_EndTransform(GNode *gn) { if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(gn->cohorts)) gn = gn->cohorts->last->datum; + if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && Lst_IsEmpty(gn->children)) { - Suff *s, *t; + Suff *srcSuff, *targSuff; /* * SuffParseTransform() may fail for special rules which are not * actual transformation rules. (e.g. .DEFAULT) */ - if (SuffParseTransform(gn->name, &s, &t)) { - SuffList *p; + if (SuffParseTransform(gn->name, &srcSuff, &targSuff)) { - SUFF_DEBUG2("deleting transformation from `%s' to `%s'\n", - s->name, t->name); - /* - * Store s->parents because s could be deleted in SuffRemove + * Remember parents since srcSuff could be deleted in + * SuffList_Remove */ - p = s->parents; + SuffList *srcSuffParents = srcSuff->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); + SUFF_DEBUG2("deleting transformation from `%s' to `%s'\n", + srcSuff->name, targSuff->name); - /* - * Remove the target from the source's parents list - */ - SuffRemove(p, t); + SuffList_Remove(targSuff->children, srcSuff); + SuffList_Remove(srcSuffParents, targSuff); } } else if (gn->type & OP_TRANSFORM) { SUFF_DEBUG1("transformation %s complete\n", gn->name); } } /* Called from Suff_AddSuffix 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. * * The appropriate links will be made between this suffix and others if * transformation rules exist for it. * * Input: * transform Transformation to test * suff Suffix to rebuild */ static void SuffRebuildGraph(GNode *transform, Suff *suff) { const char *name = transform->name; size_t nameLen = strlen(name); const char *toName; /* * First see if it is a transformation from this suffix. */ toName = SuffStrIsPrefix(suff->name, name); if (toName != NULL) { Suff *to = FindSuffByName(toName); if (to != NULL) { /* Link in and return, since it can't be anything else. */ - SuffInsert(to->children, suff); - SuffInsert(suff->parents, to); + SuffRelate(suff, to); return; } } /* * Not from, maybe to? */ toName = SuffSuffGetSuffix(suff, nameLen, name + nameLen); if (toName != NULL) { Suff *from = FindSuffByNameLen(name, (size_t)(toName - name)); - - if (from != NULL) { - /* establish the proper relationship */ - SuffInsert(suff->children, from); - SuffInsert(from->parents, suff); - } + if (from != NULL) + SuffRelate(from, suff); } } /* During Suff_AddSuffix, search through the list of existing targets and find * if any of the existing targets can be turned into a transformation rule. * * 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. * * Results: * TRUE iff a new main target has been selected. */ static Boolean SuffScanTargets(GNode *target, GNode **inout_main, Suff *gs_s, Boolean *gs_r) { - Suff *s, *t; + Suff *srcSuff, *targSuff; char *ptr; if (*inout_main == NULL && *gs_r && !(target->type & OP_NOTARGET)) { *inout_main = target; Targ_SetMain(target); return TRUE; } if (target->type == OP_TRANSFORM) return FALSE; if ((ptr = strstr(target->name, gs_s->name)) == NULL || ptr == target->name) return FALSE; - if (SuffParseTransform(target->name, &s, &t)) { + if (SuffParseTransform(target->name, &srcSuff, &targSuff)) { if (*inout_main == target) { *gs_r = TRUE; *inout_main = NULL; Targ_SetMain(NULL); } Lst_Free(target->children); target->children = Lst_New(); target->type = OP_TRANSFORM; /* * link the two together in the proper relationship and order */ SUFF_DEBUG2("defining transformation from `%s' to `%s'\n", - s->name, t->name); - SuffInsert(t->children, s); - SuffInsert(s->parents, t); + srcSuff->name, targSuff->name); + SuffRelate(srcSuff, targSuff); } return FALSE; } /* Look at all existing targets to see if adding this suffix will make one * of the current targets mutate into a suffix rule. * * This is ugly, but other makes treat all targets that start with a '.' as * suffix rules. */ static void UpdateTargets(GNode **inout_main, Suff *s) { Boolean r = FALSE; GNodeListNode *ln; for (ln = Targ_List()->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; if (SuffScanTargets(gn, inout_main, s, &r)) break; } } /* Add the suffix to the end of the list of known suffixes. * Should we restructure the suffix graph? Make doesn't... * * 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. * * Input: * name the name of the suffix to add */ void Suff_AddSuffix(const char *name, GNode **inout_main) { GNodeListNode *ln; Suff *s = FindSuffByName(name); if (s != NULL) return; s = SuffNew(name); Lst_Append(sufflist, s); UpdateTargets(inout_main, s); /* * Look for any existing transformations from or to this suffix. * XXX: Only do this after a Suff_ClearSuffixes? */ for (ln = transforms->first; ln != NULL; ln = ln->next) SuffRebuildGraph(ln->datum, s); } /* Return the search path for the given suffix, or NULL. */ SearchPath * Suff_GetPath(const char *sname) { Suff *s = FindSuffByName(sname); return s != NULL ? s->searchPath : NULL; } -/* Extend the search paths for all suffixes to include the default search - * path. +/* + * Extend the search paths for all suffixes to include the default search + * path (dirSearchPath). * - * 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. + * The default search path can be defined using the special target '.PATH'. + * The search path of each suffix can be defined using the special target + * '.PATH'. + * + * If paths were specified for the ".h" suffix, the directories are stuffed + * into a global variable called ".INCLUDES" with each directory preceded by + * '-I'. The same is done for the ".a" suffix, except the variable is called + * ".LIBS" and the flag is '-L'. */ void Suff_DoPaths(void) { SuffListNode *ln; char *ptr; SearchPath *inIncludes; /* Cumulative .INCLUDES path */ SearchPath *inLibs; /* Cumulative .LIBS path */ inIncludes = Lst_New(); inLibs = Lst_New(); for (ln = sufflist->first; ln != NULL; ln = ln->next) { Suff *s = ln->datum; if (!Lst_IsEmpty(s->searchPath)) { #ifdef INCLUDES - if (s->flags & SUFF_INCLUDE) { + if (s->flags & SUFF_INCLUDE) Dir_Concat(inIncludes, s->searchPath); - } #endif #ifdef LIBRARIES - if (s->flags & SUFF_LIBRARY) { + if (s->flags & SUFF_LIBRARY) Dir_Concat(inLibs, s->searchPath); - } #endif Dir_Concat(s->searchPath, dirSearchPath); } else { Lst_Destroy(s->searchPath, Dir_Destroy); s->searchPath = Dir_CopyDirSearchPath(); } } Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL); free(ptr); Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL); free(ptr); Lst_Destroy(inIncludes, Dir_Destroy); Lst_Destroy(inLibs, Dir_Destroy); } /* 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. * The SUFF_INCLUDE bit is set in the suffix's flags field. * * Input: * sname Name of the suffix to mark */ void Suff_AddInclude(const char *sname) { Suff *suff = FindSuffByName(sname); if (suff != NULL) suff->flags |= SUFF_INCLUDE; } /* 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. * The SUFF_LIBRARY bit is set in the suffix's flags field. * * Input: * sname Name of the suffix to mark */ void Suff_AddLib(const char *sname) { Suff *suff = FindSuffByName(sname); if (suff != NULL) suff->flags |= SUFF_LIBRARY; } /********** Implicit Source Search Functions *********/ #ifdef DEBUG_SRC static void SrcList_PrintAddrs(SrcList *srcList) { SrcListNode *ln; for (ln = srcList->first; ln != NULL; ln = ln->next) debug_printf(" %p", ln->datum); debug_printf("\n"); } #endif static Src * SrcNew(char *name, char *pref, Suff *suff, Src *parent, GNode *gn) { Src *src = bmake_malloc(sizeof *src); src->file = name; src->pref = pref; src->suff = suff; src->parent = parent; src->node = gn; - src->children = 0; + src->numChildren = 0; #ifdef DEBUG_SRC src->childrenList = Lst_New(); #endif return src; } static void SuffAddSrc(Suff *suff, SrcList *srcList, Src *targ, char *srcName, - const char *debug_tag) + const char *debug_tag MAKE_ATTR_UNUSED) { Src *s2 = SrcNew(srcName, targ->pref, suff, targ, NULL); suff->refCount++; - targ->children++; + targ->numChildren++; Lst_Append(srcList, s2); #ifdef DEBUG_SRC Lst_Append(targ->childrenList, s2); debug_printf("%s add suff %p src %p to list %p:", debug_tag, targ, s2, srcList); SrcList_PrintAddrs(srcList); #endif } /* 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. + * the prefix is used unaltered as the filename in the Src structure. * * Input: * suff suffix for which to create a Src structure * srcList list for the new Src * targ parent for the new Src */ static void SuffAddSources(Suff *suff, SrcList *srcList, Src *targ) { if ((suff->flags & SUFF_NULL) && suff->name[0] != '\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... */ SuffAddSrc(suff, srcList, targ, bmake_strdup(targ->pref), "1"); } SuffAddSrc(suff, srcList, targ, str_concat2(targ->pref, suff->name), "2"); } -/* 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 - */ +/* Add all the children of targ to the list. */ static void -SuffAddLevel(SrcList *l, Src *targ) +SuffAddLevel(SrcList *srcs, Src *targ) { SrcListNode *ln; for (ln = targ->suff->children->first; ln != NULL; ln = ln->next) { Suff *childSuff = ln->datum; - SuffAddSources(childSuff, l, targ); + SuffAddSources(childSuff, srcs, targ); } } /* Free the first Src in the list that is not referenced anymore. * Return whether a Src was removed. */ static Boolean SuffRemoveSrc(SrcList *l) { SrcListNode *ln; #ifdef DEBUG_SRC debug_printf("cleaning list %p:", l); SrcList_PrintAddrs(l); #endif for (ln = l->first; ln != NULL; ln = ln->next) { - Src *s = ln->datum; + Src *src = ln->datum; - if (s->children == 0) { - free(s->file); - if (s->parent == NULL) - free(s->pref); + if (src->numChildren == 0) { + free(src->file); + if (src->parent == NULL) + free(src->pref); else { #ifdef DEBUG_SRC - SrcListNode *ln2 = Lst_FindDatum(s->parent->childrenList, s); + SrcListNode *ln2 = Lst_FindDatum(src->parent->childrenList, src); if (ln2 != NULL) - Lst_Remove(s->parent->childrenList, ln2); + Lst_Remove(src->parent->childrenList, ln2); #endif - s->parent->children--; + src->parent->numChildren--; } #ifdef DEBUG_SRC debug_printf("free: list %p src %p children %d\n", - l, s, s->children); - Lst_Free(s->childrenList); + l, src, src->children); + Lst_Free(src->childrenList); #endif Lst_Remove(l, ln); - free(s); + free(src); return TRUE; } #ifdef DEBUG_SRC else { debug_printf("keep: list %p src %p children %d:", - l, s, s->children); - SrcList_PrintAddrs(s->childrenList); + l, src, src->children); + SrcList_PrintAddrs(src->childrenList); } #endif } return FALSE; } -/* 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, or NULL. - */ +/* Find the first existing file/target in srcs. */ static Src * SuffFindThem(SrcList *srcs, SrcList *slst) { Src *retsrc = NULL; while (!Lst_IsEmpty(srcs)) { Src *src = Lst_Dequeue(srcs); SUFF_DEBUG1("\ttrying %s...", src->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(src->file) != NULL) { #ifdef DEBUG_SRC debug_printf("remove from list %p src %p\n", srcs, src); #endif retsrc = src; break; } { char *file = Dir_FindFile(src->file, src->suff->searchPath); if (file != NULL) { retsrc = src; #ifdef DEBUG_SRC debug_printf("remove from list %p src %p\n", srcs, src); #endif free(file); break; } } SUFF_DEBUG0("not there\n"); SuffAddLevel(srcs, src); Lst_Append(slst, src); } if (retsrc) { SUFF_DEBUG0("got it\n"); } return retsrc; } /* 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 to play with * * Results: * The Src of the "winning" child, or NULL. */ static Src * SuffFindCmds(Src *targ, SrcList *slst) { GNodeListNode *gln; GNode *tgn; /* Target GNode */ GNode *sgn; /* Source GNode */ size_t prefLen; /* The length of the defined prefix */ Suff *suff; /* Suffix on matching beastie */ Src *ret; /* Return value */ char *cp; tgn = targ->node; prefLen = strlen(targ->pref); for (gln = tgn->children->first; gln != NULL; gln = gln->next) { sgn = gln->datum; if (sgn->type & OP_OPTIONAL && Lst_IsEmpty(tgn->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(sgn->name, '/'); if (cp == NULL) { cp = sgn->name; } else { cp++; } if (strncmp(cp, targ->pref, prefLen) != 0) continue; - /* - * The node matches the prefix ok, see if it has a known - * suffix. - */ + /* The node matches the prefix ok, see if it has a known suffix. */ suff = FindSuffByName(cp + prefLen); if (suff == 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. */ /* XXX: Can targ->suff be NULL here? */ if (targ->suff != NULL && Lst_FindDatum(suff->parents, targ->suff) != NULL) break; } if (gln == NULL) return NULL; /* * 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 = SrcNew(bmake_strdup(sgn->name), targ->pref, suff, targ, sgn); suff->refCount++; - targ->children++; + targ->numChildren++; #ifdef DEBUG_SRC debug_printf("3 add targ %p ret %p\n", targ, ret); Lst_Append(targ->childrenList, ret); #endif Lst_Append(slst, ret); SUFF_DEBUG1("\tusing existing source %s\n", sgn->name); return ret; } /* Expand the names of any children of a given node that contain variable * expressions or file wildcards into actual targets. * * 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. * * Input: * cln Child to examine * pgn Parent node being processed */ static void SuffExpandChildren(GNodeListNode *cln, GNode *pgn) { GNode *cgn = cln->datum; 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; } SUFF_DEBUG1("Expanding \"%s\"...", cgn->name); - (void)Var_Subst(cgn->name, pgn, VARE_UNDEFERR|VARE_WANTRES, &cp); + (void)Var_Subst(cgn->name, pgn, VARE_WANTRES | VARE_UNDEFERR, &cp); /* TODO: handle errors */ { GNodeList *members = Lst_New(); 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 + * Unfortunately, we can't use Str_Words because 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; + start = cp; + pp_skip_hspace(&start); cp = start; while (*cp != '\0') { if (*cp == ' ' || *cp == '\t') { /* * White-space -- terminate element, find the node, * add it, skip any further spaces. */ *cp++ = '\0'; gn = Targ_GetNode(start); Lst_Append(members, gn); - while (*cp == ' ' || *cp == '\t') { - cp++; - } + pp_skip_hspace(&cp); start = cp; /* Continue at the next non-space. */ } else if (*cp == '$') { /* * Start of a variable spec -- contact variable module * to find the end so we can skip over it. */ const char *nested_p = cp; const char *junk; void *freeIt; /* XXX: Why VARE_WANTRES when the result is not used? */ (void)Var_Parse(&nested_p, pgn, - VARE_UNDEFERR|VARE_WANTRES, + VARE_WANTRES | VARE_UNDEFERR, &junk, &freeIt); /* TODO: handle errors */ if (junk == var_Error) { Parse_Error(PARSE_FATAL, "Malformed variable expression at \"%s\"", cp); cp++; } else { cp += nested_p - cp; } free(freeIt); - } else if (*cp == '\\' && cp[1] != '\0') { + } else if (cp[0] == '\\' && cp[1] != '\0') { /* * Escaped something -- skip over it */ /* XXX: In other places, escaping at this syntactical * position is done by a '$', not a '\'. The '\' is only * used in variable modifiers. */ cp += 2; } else { cp++; } } if (cp != start) { /* * Stuff left over -- add it to the list too */ gn = Targ_GetNode(start); Lst_Append(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 = Lst_Dequeue(members); SUFF_DEBUG1("%s...", gn->name); /* Add gn to the parents child list before the original child */ Lst_InsertBefore(pgn->children, cln, gn); Lst_Append(gn->parents, pgn); pgn->unmade++; /* Expand wildcards on new node */ SuffExpandWildcards(cln->prev, pgn); } Lst_Free(members); /* * Free the result */ free(cp); } SUFF_DEBUG0("\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_FindDatum(cgn->parents, pgn)); } static void SuffExpandWildcards(GNodeListNode *cln, GNode *pgn) { GNode *cgn = cln->datum; - StringList *explist; + StringList *expansions; if (!Dir_HasWildcards(cgn->name)) return; /* * Expand the word along the chosen path */ - explist = Lst_New(); - Dir_Expand(cgn->name, Suff_FindPath(cgn), explist); + expansions = Lst_New(); + Dir_Expand(cgn->name, Suff_FindPath(cgn), expansions); - while (!Lst_IsEmpty(explist)) { + while (!Lst_IsEmpty(expansions)) { GNode *gn; /* * Fetch next expansion off the list and find its GNode */ - char *cp = Lst_Dequeue(explist); + char *cp = Lst_Dequeue(expansions); SUFF_DEBUG1("%s...", cp); gn = Targ_GetNode(cp); /* Add gn to the parents child list before the original child */ Lst_InsertBefore(pgn->children, cln, gn); Lst_Append(gn->parents, pgn); pgn->unmade++; } - Lst_Free(explist); + Lst_Free(expansions); SUFF_DEBUG0("\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_FindDatum(cgn->parents, pgn)); } /* Find a path along which to expand the node. * - * If the word has a known suffix, use that path. + * If the node 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. */ SearchPath * Suff_FindPath(GNode* gn) { Suff *suff = gn->suffix; if (suff == NULL) { char *name = gn->name; size_t nameLen = strlen(gn->name); SuffListNode *ln; for (ln = sufflist->first; ln != NULL; ln = ln->next) if (SuffSuffIsSuffix(ln->datum, nameLen, name + nameLen)) break; SUFF_DEBUG1("Wildcard expanding \"%s\"...", gn->name); if (ln != NULL) suff = ln->datum; /* XXX: Here we can save the suffix so we don't have to do this again */ } if (suff != NULL) { SUFF_DEBUG1("suffix is \"%s\"...\n", suff->name); return suff->searchPath; } else { SUFF_DEBUG0("\n"); return dirSearchPath; /* Use default search path */ } } /* 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 + * The source and target are linked and the commands from the transformation + * are added to the target node's commands list. The target also inherits all + * the sources for the transformation rule. * * 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) +SuffApplyTransform(GNode *tgn, GNode *sgn, Suff *tsuff, Suff *ssuff) { - GNodeListNode *ln, *nln; /* General node */ + GNodeListNode *ln; char *tname; /* Name of transformation rule */ GNode *gn; /* Node for same */ /* * Form the proper links between the target and source. */ - Lst_Append(tGn->children, sGn); - Lst_Append(sGn->parents, tGn); - tGn->unmade++; + Lst_Append(tgn->children, sgn); + Lst_Append(sgn->parents, tgn); + tgn->unmade++; /* * Locate the transformation rule itself */ - tname = str_concat2(s->name, t->name); + tname = str_concat2(ssuff->name, tsuff->name); gn = FindTransformByName(tname); free(tname); if (gn == 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. - */ + /* This can happen when linking an OP_MEMBER and OP_ARCHV node. */ return FALSE; } - SUFF_DEBUG3("\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name); + DEBUG3(SUFF,"\tapplying %s -> %s to \"%s\"\n", + ssuff->name, tsuff->name, tgn->name); - /* - * Record last child for expansion purposes - */ - ln = tGn->children->last; + /* Record last child; Make_HandleUse may add child nodes. */ + ln = tgn->children->last; - /* - * Pass the buck to Make_HandleUse to apply the rule - */ - (void)Make_HandleUse(gn, tGn); + /* Apply the rule. */ + Make_HandleUse(gn, tgn); - /* - * Deal with wildcards and variables in any acquired sources - */ - for (ln = ln != NULL ? ln->next : NULL; ln != NULL; ln = nln) { - nln = ln->next; - SuffExpandChildren(ln, tGn); + /* Deal with wildcards and variables in any acquired sources. */ + ln = ln != NULL ? ln->next : NULL; + while (ln != NULL) { + GNodeListNode *nln = ln->next; + SuffExpandChildren(ln, tgn); + ln = nln; } /* - * Keep track of another parent to which this beast is transformed so + * Keep track of another parent to which this node is transformed so * the .IMPSRC variable can be set correctly for the parent. */ - Lst_Append(sGn->implicitParents, tGn); + Lst_Append(sgn->implicitParents, tgn); return TRUE; } /* Locate dependencies for an OP_ARCHV node. * * Input: * gn Node for which to locate dependencies * * Side Effects: * Same as Suff_FindDeps */ static void SuffFindArchiveDeps(GNode *gn, SrcList *slst) { char *eoarch; /* End of archive portion */ char *eoname; /* End of member portion */ GNode *mem; /* Node for member */ SuffListNode *ln, *nln; /* Next suffix node to check */ 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, ')'); /* * Caller guarantees the format `libname(member)', via * Arch_ParseArchive. */ assert(eoarch != NULL); assert(eoname != NULL); *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_GetNode(name); SuffFindDeps(mem, slst); /* * Create the link between the two nodes right off */ Lst_Append(gn->children, mem); Lst_Append(mem->parents, gn); gn->unmade++; /* * Copy in the variables from the member node to this one. */ Var_Set(PREFIX, GNode_VarPrefix(mem), gn); Var_Set(TARGET, GNode_VarTarget(mem), gn); ms = mem->suffix; - if (ms == NULL) { - /* - * Didn't know what it was -- use .NULL suffix if not in make mode - */ + if (ms == NULL) { /* Didn't know what it was. */ SUFF_DEBUG0("using null suffix\n"); ms = suffNull; } /* * Set the other two local variables required for this target. */ Var_Set(MEMBER, name, gn); Var_Set(ARCHIVE, gn->name, gn); /* * Set $@ for compatibility with other makes */ Var_Set(TARGET, gn->name, gn); /* * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ for (ln = gn->children->first; ln != NULL; ln = nln) { nln = ln->next; SuffExpandChildren(ln, gn); } 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... */ size_t nameLen = (size_t)(eoarch - gn->name); /* Use first matching suffix... */ for (ln = ms->parents->first; ln != NULL; ln = ln->next) if (SuffSuffIsSuffix(ln->datum, nameLen, eoarch)) break; if (ln != NULL) { /* * Got one -- apply it */ Suff *suff = ln->datum; if (!SuffApplyTransform(gn, mem, suff, ms)) { SUFF_DEBUG2("\tNo transformation from %s -> %s\n", ms->name, suff->name); } } } /* * Replace the opening and closing parens now we've no need of the separate * pieces. */ - *eoarch = '('; *eoname = ')'; + *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 (!GNode_IsTarget(gn)) { + if (!GNode_IsTarget(gn)) gn->type |= OP_DEPENDS; - } /* * Flag the member as such so we remember to look in the archive for * its modification time. The OP_JOIN | OP_MADE is needed because this * target should never get made. */ mem->type |= OP_MEMBER | OP_JOIN | OP_MADE; } static void SuffFindNormalDepsKnown(const char *name, size_t nameLen, GNode *gn, SrcList *srcs, SrcList *targs) { SuffListNode *ln; Src *targ; char *pref; for (ln = sufflist->first; ln != NULL; ln = ln->next) { Suff *suff = ln->datum; if (!SuffSuffIsSuffix(suff, nameLen, name + nameLen)) continue; pref = bmake_strldup(name, (size_t)(nameLen - suff->nameLen)); targ = SrcNew(bmake_strdup(gn->name), pref, suff, NULL, gn); suff->refCount++; /* * Add nodes from which the target can be made */ SuffAddLevel(srcs, targ); /* * Record the target so we can nuke it */ Lst_Append(targs, targ); } } static void SuffFindNormalDepsUnknown(GNode *gn, const char *sopref, SrcList *srcs, SrcList *targs) { Src *targ; if (!Lst_IsEmpty(targs) || suffNull == NULL) return; SUFF_DEBUG1("\tNo known suffix on %s. Using .NULL suffix\n", gn->name); targ = SrcNew(bmake_strdup(gn->name), bmake_strdup(sopref), suffNull, NULL, gn); targ->suff->refCount++; /* * 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 { SUFF_DEBUG0("not "); } SUFF_DEBUG0("adding suffix rules\n"); Lst_Append(targs, targ); } /* * 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. */ static void SuffFindNormalDepsPath(GNode *gn, Src *targ) { if (gn->type & (OP_PHONY | OP_NOPATH)) return; free(gn->path); gn->path = Dir_FindFile(gn->name, (targ == NULL ? dirSearchPath : targ->suff->searchPath)); if (gn->path == NULL) return; Var_Set(TARGET, gn->path, gn); if (targ != NULL) { /* * Suffix known for the thing -- trim the suffix off * the path to form the proper .PREFIX variable. */ size_t savep = strlen(gn->path) - targ->suff->nameLen; char savec; char *ptr; 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); gn->path[savep] = savec; } else { char *ptr; /* 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); } } /* Locate implicit dependencies for regular targets. * * Input: * gn Node for which to find sources * * Side Effects: * Same as Suff_FindDeps */ static void SuffFindNormalDeps(GNode *gn, SrcList *slst) { SrcList *srcs; /* List of sources at which to look */ SrcList *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 */ const char *name = gn->name; size_t nameLen = strlen(name); /* * Begin at the beginning... */ srcs = Lst_New(); targs = Lst_New(); /* * 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)) { SuffFindNormalDepsKnown(name, nameLen, gn, srcs, targs); /* Handle target of unknown suffix... */ SuffFindNormalDepsUnknown(gn, name, srcs, targs); /* * 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)) { + if (targs->first != NULL) targ = targs->first->datum; - } else { + 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, GNode_Path(gn), gn); - pref = (targ != NULL) ? targ->pref : gn->name; + pref = targ != NULL ? targ->pref : gn->name; Var_Set(PREFIX, pref, gn); /* * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ { SuffListNode *ln, *nln; for (ln = gn->children->first; ln != NULL; ln = nln) { nln = ln->next; SuffExpandChildren(ln, gn); } } if (targ == NULL) { SUFF_DEBUG1("\tNo valid suffix on %s\n", gn->name); sfnd_abort: SuffFindNormalDepsPath(gn, targ); 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) { + 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_FindDatum(slst, bottom) == NULL) { + while (bottom != NULL && bottom->parent != NULL) { + if (Lst_FindDatum(slst, bottom) == NULL) Lst_Append(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) { + if (bottom->node == NULL) bottom->node = Targ_GetNode(bottom->file); - } 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) { + if (targ->node == NULL) targ->node = Targ_GetNode(targ->file); - } 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. + * node, so all we need to do is set the standard variables. */ targ->node->type |= OP_DEPS_FOUND; - Var_Set(PREFIX, targ->pref, targ->node); - Var_Set(TARGET, targ->node->name, targ->node); } } - if (gn->suffix) + if (gn->suffix != NULL) 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_FindDatum(slst, bottom) == NULL) - Lst_Append(slst, bottom); + if (bottom != NULL && Lst_FindDatum(slst, bottom) == NULL) + Lst_Append(slst, bottom); while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs)) continue; Lst_MoveAll(slst, srcs); Lst_MoveAll(slst, targs); } -/* Find implicit sources for the target described by the graph node. +/* Find implicit sources for the target. * * 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. * * 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; } static void SuffFindDeps(GNode *gn, SrcList *slst) { if (gn->type & OP_DEPS_FOUND) return; gn->type |= OP_DEPS_FOUND; /* * Make sure we have these set, may get revised below. */ Var_Set(TARGET, GNode_Path(gn), gn); Var_Set(PREFIX, gn->name, gn); SUFF_DEBUG1("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). */ Suff *s = FindSuffByName(LIBSUFF); if (gn->suffix) gn->suffix->refCount--; if (s != NULL) { gn->suffix = s; gn->suffix->refCount++; Arch_FindLib(gn, s->searchPath); } else { gn->suffix = NULL; Var_Set(TARGET, gn->name, gn); } /* * 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); } else { SuffFindNormalDeps(gn, slst); } } /* Define which suffix is the null suffix. * * Need to handle the changing of the null suffix gracefully so the old * transformation rules don't just go away. * * Input: * name Name of null suffix */ void Suff_SetNull(const char *name) { - Suff *s = FindSuffByName(name); - if (s == NULL) { + Suff *suff = FindSuffByName(name); + if (suff == NULL) { Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.", name); return; } - if (suffNull != NULL) { + if (suffNull != NULL) suffNull->flags &= ~(unsigned)SUFF_NULL; - } - s->flags |= SUFF_NULL; + suff->flags |= SUFF_NULL; /* * XXX: Here's where the transformation mangling would take place */ - suffNull = s; + suffNull = suff; } /* Initialize the suffixes module. */ void Suff_Init(void) { #ifdef CLEANUP suffClean = Lst_New(); sufflist = Lst_New(); #endif srclist = Lst_New(); transforms = Lst_New(); /* * 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. */ Suff_ClearSuffixes(); } /* Clean up the suffixes module. */ void Suff_End(void) { #ifdef CLEANUP Lst_Destroy(sufflist, SuffFree); Lst_Destroy(suffClean, SuffFree); if (suffNull) SuffFree(suffNull); Lst_Free(srclist); Lst_Free(transforms); #endif } -/********************* DEBUGGING FUNCTIONS **********************/ - static void PrintSuffNames(const char *prefix, SuffList *suffs) { SuffListNode *ln; debug_printf("#\t%s: ", prefix); for (ln = suffs->first; ln != NULL; ln = ln->next) { Suff *suff = ln->datum; debug_printf("%s ", suff->name); } debug_printf("\n"); } static void -PrintSuff(Suff *s) +PrintSuff(Suff *suff) { - debug_printf("# \"%s\" (num %d, ref %d)", s->name, s->sNum, s->refCount); - if (s->flags != 0) { + debug_printf("# \"%s\" (num %d, ref %d)", + suff->name, suff->sNum, suff->refCount); + if (suff->flags != 0) { char flags_buf[SuffFlags_ToStringSize]; debug_printf(" (%s)", Enum_FlagsToString(flags_buf, sizeof flags_buf, - s->flags, SuffFlags_ToStringSpecs)); + suff->flags, SuffFlags_ToStringSpecs)); } debug_printf("\n"); - PrintSuffNames("To", s->parents); - PrintSuffNames("From", s->children); + PrintSuffNames("To", suff->parents); + PrintSuffNames("From", suff->children); debug_printf("#\tSearch Path: "); - Dir_PrintPath(s->searchPath); + Dir_PrintPath(suff->searchPath); debug_printf("\n"); } static void PrintTransformation(GNode *t) { debug_printf("%-16s:", t->name); Targ_PrintType(t->type); debug_printf("\n"); Targ_PrintCmds(t); debug_printf("\n"); } void Suff_PrintAll(void) { debug_printf("#*** Suffixes:\n"); { SuffListNode *ln; for (ln = sufflist->first; ln != NULL; ln = ln->next) PrintSuff(ln->datum); } debug_printf("#*** Transformations:\n"); { GNodeListNode *ln; for (ln = transforms->first; ln != NULL; ln = ln->next) PrintTransformation(ln->datum); } } Index: head/contrib/bmake/targ.c =================================================================== --- head/contrib/bmake/targ.c (revision 367862) +++ head/contrib/bmake/targ.c (revision 367863) @@ -1,571 +1,599 @@ -/* $NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $ */ +/* $NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig 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. */ -/*- - * targ.c -- - * Functions for maintaining the Lst allTargets. Target nodes are - * kept in two structures: a Lst and a hash table. +/* + * Maintaining the targets and sources, which are both implemented as GNode. * * Interface: - * Targ_Init Initialization procedure. + * Targ_Init Initialize the module. * - * Targ_End Clean up the module + * Targ_End Clean up the module. * * Targ_List Return the list of all targets so far. * - * Targ_NewGN Create a new GNode for the passed target + * GNode_New 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, or return NULL. * * Targ_GetNode Find the node, or create it. * * Targ_NewInternalNode * Create an internal node. * * Targ_FindList Given a list of names, find nodes for all * of them, creating them as necessary. * * 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 "make.h" #include "dir.h" /* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $"); +MAKE_RCSID("$NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig Exp $"); -static GNodeList *allTargets; /* the list of all targets found so far */ -#ifdef CLEANUP -static GNodeList *allGNs; /* List of all the GNodes */ -#endif -static HashTable targets; /* a hash table of same */ +/* All target nodes found so far, but not the source nodes. */ +static GNodeList *allTargets; +static HashTable allTargetsByName; #ifdef CLEANUP -static void TargFreeGN(void *); +static GNodeList *allNodes; + +static void GNode_Free(void *); #endif void Targ_Init(void) { allTargets = Lst_New(); - HashTable_Init(&targets); + HashTable_Init(&allTargetsByName); +#ifdef CLEANUP + allNodes = Lst_New(); +#endif } void Targ_End(void) { Targ_Stats(); #ifdef CLEANUP Lst_Free(allTargets); - if (allGNs != NULL) - Lst_Destroy(allGNs, TargFreeGN); - HashTable_Done(&targets); + HashTable_Done(&allTargetsByName); + Lst_Destroy(allNodes, GNode_Free); #endif } void Targ_Stats(void) { - HashTable_DebugStats(&targets, "targets"); + HashTable_DebugStats(&allTargetsByName, "targets"); } -/* Return the list of all targets. */ +/* + * Return the list of all targets, which are all nodes that appear on the + * left-hand side of a dependency declaration such as "target: source". + * The returned list does not contain pure sources. + */ GNodeList * Targ_List(void) { return allTargets; } -/* Create and initialize a new graph node. The gnode is added to the list of - * all gnodes. +/* Create a new graph node, but don't register it anywhere. * - * Input: - * name the name of the node, such as "clean", "src.c", ".END" + * Graph nodes that appear on the left-hand side of a dependency line such + * as "target: source" are called targets. XXX: In some cases (like the + * .ALLTARGETS variable), all nodes are called targets as well, even if they + * never appear on the left-hand side. This is a mistake. + * + * Typical names for graph nodes are: + * "src.c" (an ordinary file) + * "clean" (a .PHONY target) + * ".END" (a special hook target) + * "-lm" (a library) + * "libc.a(isspace.o)" (an archive member) */ GNode * -Targ_NewGN(const char *name) +GNode_New(const char *name) { GNode *gn; - gn = bmake_malloc(sizeof(GNode)); + gn = bmake_malloc(sizeof *gn); gn->name = bmake_strdup(name); gn->uname = NULL; gn->path = NULL; gn->type = name[0] == '-' && name[1] == 'l' ? OP_LIB : 0; - gn->unmade = 0; - gn->unmade_cohorts = 0; - gn->cohort_num[0] = '\0'; - gn->centurion = NULL; - gn->made = UNMADE; gn->flags = 0; - gn->checked_seqno = 0; + gn->made = UNMADE; + gn->unmade = 0; gn->mtime = 0; gn->youngestChild = NULL; gn->implicitParents = Lst_New(); - gn->cohorts = Lst_New(); gn->parents = Lst_New(); gn->children = Lst_New(); gn->order_pred = Lst_New(); gn->order_succ = Lst_New(); + gn->cohorts = Lst_New(); + gn->cohort_num[0] = '\0'; + gn->unmade_cohorts = 0; + gn->centurion = NULL; + gn->checked_seqno = 0; HashTable_Init(&gn->context); gn->commands = Lst_New(); gn->suffix = NULL; gn->fname = NULL; gn->lineno = 0; #ifdef CLEANUP - if (allGNs == NULL) - allGNs = Lst_New(); - Lst_Append(allGNs, gn); + Lst_Append(allNodes, gn); #endif return gn; } #ifdef CLEANUP static void -TargFreeGN(void *gnp) +GNode_Free(void *gnp) { GNode *gn = gnp; free(gn->name); free(gn->uname); free(gn->path); + /* gn->youngestChild is not owned by this node. */ + Lst_Free(gn->implicitParents); /* ... but not the nodes themselves, */ + Lst_Free(gn->parents); /* as they are not owned by this node. */ + Lst_Free(gn->children); /* likewise */ + Lst_Free(gn->order_pred); /* likewise */ + Lst_Free(gn->order_succ); /* likewise */ + Lst_Free(gn->cohorts); /* likewise */ + HashTable_Done(&gn->context); /* ... but not the variables themselves, + * even though they are owned by this node. + * XXX: they should probably be freed. */ + Lst_Free(gn->commands); /* ... but not the commands themselves, + * as they may be shared with other nodes. */ + /* gn->suffix is not owned by this node. */ + /* XXX: gn->suffix should be unreferenced here. This requires a thorough + * check that the reference counting is done correctly in all places, + * otherwise a suffix might be freed too early. */ - Lst_Free(gn->implicitParents); - Lst_Free(gn->cohorts); - Lst_Free(gn->parents); - Lst_Free(gn->children); - Lst_Free(gn->order_succ); - Lst_Free(gn->order_pred); - HashTable_Done(&gn->context); - Lst_Free(gn->commands); - - /* XXX: does gn->suffix need to be freed? It is reference-counted. */ - free(gn); } #endif /* Get the existing global node, or return NULL. */ GNode * Targ_FindNode(const char *name) { - return HashTable_FindValue(&targets, name); + return HashTable_FindValue(&allTargetsByName, name); } /* Get the existing global node, or create it. */ GNode * Targ_GetNode(const char *name) { Boolean isNew; - HashEntry *he = HashTable_CreateEntry(&targets, name, &isNew); + HashEntry *he = HashTable_CreateEntry(&allTargetsByName, name, &isNew); if (!isNew) return HashEntry_Get(he); { GNode *gn = Targ_NewInternalNode(name); HashEntry_Set(he, gn); return gn; } } -/* Create a node, register it in .ALLTARGETS but don't store it in the +/* + * Create a node, register it in .ALLTARGETS but don't store it in the * table of global nodes. This means it cannot be found by name. * - * This is used for internal nodes, such as cohorts or .WAIT nodes. */ + * This is used for internal nodes, such as cohorts or .WAIT nodes. + */ GNode * Targ_NewInternalNode(const char *name) { - GNode *gn = Targ_NewGN(name); + GNode *gn = GNode_New(name); Var_Append(".ALLTARGETS", name, VAR_GLOBAL); Lst_Append(allTargets, gn); if (doing_depend) gn->flags |= FROM_DEPEND; return gn; } -/* Return the .END node, which contains the commands to be executed when - * everything else is done. */ +/* + * Return the .END node, which contains the commands to be run when + * everything else has been made. + */ GNode *Targ_GetEndNode(void) { /* Save the node locally to avoid having to search for it all the time. */ static GNode *endNode = NULL; if (endNode == NULL) { endNode = Targ_GetNode(".END"); endNode->type = OP_SPECIAL; } return endNode; } /* Return the named nodes, creating them as necessary. */ GNodeList * Targ_FindList(StringList *names) { StringListNode *ln; GNodeList *nodes = Lst_New(); for (ln = names->first; ln != NULL; ln = ln->next) { const char *name = ln->datum; GNode *gn = Targ_GetNode(name); Lst_Append(nodes, gn); } return nodes; } /* Return true if should ignore errors when creating gn. */ Boolean -Targ_Ignore(GNode *gn) +Targ_Ignore(const GNode *gn) { return opts.ignoreErrors || gn->type & OP_IGNORE; } /* Return true if be silent when creating gn. */ Boolean -Targ_Silent(GNode *gn) +Targ_Silent(const GNode *gn) { return opts.beSilent || gn->type & OP_SILENT; } /* See if the given target is precious. */ Boolean -Targ_Precious(GNode *gn) +Targ_Precious(const GNode *gn) { + /* XXX: Why are '::' targets precious? */ return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP); } -/******************* DEBUG INFO PRINTING ****************/ +/* + * The main target to be made; only for debugging output. + * See mainNode in parse.c for the definitive source. + */ +static GNode *mainTarg; -static GNode *mainTarg; /* the main target, as set by Targ_SetMain */ - -/* Set our idea of the main target we'll be creating. Used for debugging - * output. */ +/* Remember the main target to make; only used for debugging. */ void Targ_SetMain(GNode *gn) { mainTarg = gn; } static void PrintNodeNames(GNodeList *gnodes) { GNodeListNode *node; for (node = gnodes->first; node != NULL; node = node->next) { GNode *gn = node->datum; debug_printf(" %s%s", gn->name, gn->cohort_num); } } static void PrintNodeNamesLine(const char *label, GNodeList *gnodes) { if (Lst_IsEmpty(gnodes)) return; debug_printf("# %s:", label); PrintNodeNames(gnodes); debug_printf("\n"); } void Targ_PrintCmds(GNode *gn) { StringListNode *ln; for (ln = gn->commands->first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; debug_printf("\t%s\n", cmd); } } /* Format a modification time in some reasonable way and return it. * 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; } /* Print out a type field giving only those attributes the user can set. */ void Targ_PrintType(int type) { int tbit; #define PRINTBIT(attr) case CONCAT(OP_,attr): debug_printf(" ." #attr); break #define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))debug_printf(" ." #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))debug_printf(" .MEMBER"); break; PRINTDBIT(ARCHV); PRINTDBIT(MADE); PRINTDBIT(PHONY); } } } static const char * made_name(GNodeMade 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"; } } static const char * GNode_OpName(const GNode *gn) { switch (gn->type & OP_OPMASK) { case OP_DEPENDS: return ":"; case OP_FORCE: return "!"; case OP_DOUBLEDEP: return "::"; } return ""; } /* Print the contents of a node. */ void Targ_PrintNode(GNode *gn, int pass) { debug_printf("# %s%s", gn->name, gn->cohort_num); GNode_FprintDetails(opts.debug_file, ", ", gn, "\n"); if (gn->flags == 0) return; if (GNode_IsTarget(gn)) { debug_printf("#\n"); if (gn == mainTarg) { debug_printf("# *** MAIN TARGET ***\n"); } if (pass >= 2) { - if (gn->unmade) { + if (gn->unmade > 0) { debug_printf("# %d unmade children\n", gn->unmade); } else { debug_printf("# No unmade children\n"); } - if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { + if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { if (gn->mtime != 0) { debug_printf("# last modified %s: %s\n", Targ_FmtTime(gn->mtime), made_name(gn->made)); } else if (gn->made != UNMADE) { debug_printf("# non-existent (maybe): %s\n", made_name(gn->made)); } else { debug_printf("# unmade\n"); } } PrintNodeNamesLine("implicit parents", gn->implicitParents); } else { if (gn->unmade) debug_printf("# %d unmade children\n", gn->unmade); } PrintNodeNamesLine("parents", gn->parents); PrintNodeNamesLine("order_pred", gn->order_pred); PrintNodeNamesLine("order_succ", gn->order_succ); debug_printf("%-16s%s", gn->name, GNode_OpName(gn)); Targ_PrintType(gn->type); PrintNodeNames(gn->children); debug_printf("\n"); Targ_PrintCmds(gn); debug_printf("\n\n"); if (gn->type & OP_DOUBLEDEP) { Targ_PrintNodes(gn->cohorts, pass); } } } void Targ_PrintNodes(GNodeList *gnodes, int pass) { GNodeListNode *ln; for (ln = gnodes->first; ln != NULL; ln = ln->next) Targ_PrintNode(ln->datum, pass); } /* Print only those targets that are just a source. */ static void PrintOnlySources(void) { GNodeListNode *ln; for (ln = allTargets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; if (GNode_IsTarget(gn)) continue; debug_printf("#\t%s [%s]", gn->name, GNode_Path(gn)); Targ_PrintType(gn->type); debug_printf("\n"); } } /* Input: * pass 1 => before processing * 2 => after processing * 3 => after processing, an error occurred */ void Targ_PrintGraph(int pass) { debug_printf("#*** Input graph:\n"); Targ_PrintNodes(allTargets, pass); - debug_printf("\n\n"); - debug_printf("#\n# Files that are only sources:\n"); + debug_printf("\n"); + debug_printf("\n"); + + debug_printf("#\n"); + debug_printf("# Files that are only sources:\n"); PrintOnlySources(); + debug_printf("#*** Global Variables:\n"); Var_Dump(VAR_GLOBAL); + debug_printf("#*** Command-line Variables:\n"); Var_Dump(VAR_CMDLINE); + debug_printf("\n"); Dir_PrintDirectories(); debug_printf("\n"); + Suff_PrintAll(); } -/* Propagate some type information to cohort nodes (those from the :: +/* Propagate some type information to cohort nodes (those from the '::' * dependency operator). * * Should be called after the makefiles are parsed but before any action is * taken. */ void Targ_Propagate(void) { GNodeListNode *ln, *cln; for (ln = allTargets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; GNodeType type = gn->type; if (!(type & OP_DOUBLEDEP)) continue; for (cln = gn->cohorts->first; cln != NULL; cln = cln->next) { GNode *cohort = cln->datum; cohort->type |= type & ~OP_OPMASK; } } } Index: head/contrib/bmake/unit-tests/directives.mk =================================================================== --- head/contrib/bmake/unit-tests/directives.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directives.mk (nonexistent) @@ -1,163 +0,0 @@ -# $NetBSD: directives.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $ -# -# Tests for parsing directives, in the same order as in the manual page. -# -# Each test group has 10 lines, to keep the line numbers in directives.exp -# stable. -# -# no tests for .error since it exits immediately, see ParseMessage. - -.info begin .export tests -.expor # misspelled -.export # oops: missing argument -.export VARNAME -.exporting works # oops: misspelled - - - - - -.info begin .export-env tests -.export-en # oops: misspelled -.export-env -.export-environment # oops: misspelled - - - - - - -.info begin .export-literal tests -.export-litera # oops: misspelled -.export-literal # oops: missing argument -.export-literal VARNAME -.export-literally # oops: misspelled - - - - - -.info begin .info tests -.inf # misspelled -.info # oops: message should be "missing parameter" -.info message -.info indented message -.information -.information message # oops: misspelled -.info.man: # not a message, but a suffix rule - - -.info begin .undef tests -.unde # misspelled -.undef # oops: missing argument -.undefined # oops: misspelled -.undef VARNAME - - - - - -.info begin .unexport tests -.unexpor # misspelled -.unexport # oops: missing argument -.unexport VARNAME # ok -.unexporting works # oops: misspelled - - - - - -.info begin .unexport-env tests -.unexport-en # misspelled -.unexport-env # ok -.unexport-environment # oops: misspelled - - - - - - -.info begin .warning tests -.warn # misspelled -.warnin # misspelled -.warning # oops: should be "missing argument" -.warning message # ok -.warnings # misspelled -.warnings messages # oops - - - -.info begin .elif misspellings tests, part 1 -.if 1 -.elif 1 # ok -.elsif 1 # oops: misspelled -.elseif 1 # oops: misspelled -.endif - - - - -.info begin .elif misspellings tests, part 2 -.if 0 -.elif 0 # ok -.elsif 0 # oops: misspelled -.elseif 0 # oops: misspelled -.endif - - - - -.info begin .elif misspellings tests, part 3 -.if 0 -.elsif 0 # oops: misspelled -.endif -.if 0 -.elseif 0 # oops: misspelled -.endif - - - -.info which branch is taken on misspelling after false? -.if 0 -.elsif 1 -. info 1 taken -.elsif 2 -. info 2 taken -.else -. info else taken -.endif - -.info which branch is taken on misspelling after true? -.if 1 -.elsif 1 -. info 1 taken -.elsif 2 -. info 2 taken -.else -. info else taken -.endif - -.indented none -. indented 2 spaces -. indented tab -.${:Uinfo} directives cannot be indirect - - - - - - -.include "nonexistent.mk" -.include "/dev/null" # size 0 -# including a directory technically succeeds, but shouldn't. -#.include "." # directory - - - - - - -.info end of the tests - -all: - @: Property changes on: head/contrib/bmake/unit-tests/directives.mk ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Deleted: svn:keywords ## -1 +0,0 ## -FreeBSD=%H \ No newline at end of property Deleted: svn:mime-type ## -1 +0,0 ## -text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/varshell.mk =================================================================== --- head/contrib/bmake/unit-tests/varshell.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varshell.mk (nonexistent) @@ -1,20 +0,0 @@ -# $Id: varshell.mk,v 1.6 2020/10/26 17:55:23 sjg Exp $ -# $NetBSD: varshell.mk,v 1.4 2020/10/24 08:50:17 rillig 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 ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Deleted: svn:keywords ## -1 +0,0 ## -FreeBSD=%H \ No newline at end of property Deleted: svn:mime-type ## -1 +0,0 ## -text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/directives.exp =================================================================== --- head/contrib/bmake/unit-tests/directives.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directives.exp (nonexistent) @@ -1,42 +0,0 @@ -make: "directives.mk" line 10: begin .export tests -make: "directives.mk" line 11: Unknown directive "expor" -make: "directives.mk" line 20: begin .export-env tests -make: "directives.mk" line 30: begin .export-literal tests -make: "directives.mk" line 40: begin .info tests -make: "directives.mk" line 41: Unknown directive "inf" -make: "directives.mk" line 42: Unknown directive "info" -make: "directives.mk" line 43: message -make: "directives.mk" line 44: indented message -make: "directives.mk" line 45: Unknown directive "information" -make: "directives.mk" line 46: message -make: "directives.mk" line 50: begin .undef tests -make: "directives.mk" line 51: Unknown directive "unde" -make: "directives.mk" line 60: begin .unexport tests -make: "directives.mk" line 61: Unknown directive "unexpor" -make: "directives.mk" line 70: begin .unexport-env tests -make: "directives.mk" line 80: begin .warning tests -make: "directives.mk" line 81: Unknown directive "warn" -make: "directives.mk" line 82: Unknown directive "warnin" -make: "directives.mk" line 83: Unknown directive "warning" -make: "directives.mk" line 84: warning: message -make: "directives.mk" line 85: Unknown directive "warnings" -make: "directives.mk" line 86: warning: messages -make: "directives.mk" line 90: begin .elif misspellings tests, part 1 -make: "directives.mk" line 100: begin .elif misspellings tests, part 2 -make: "directives.mk" line 110: begin .elif misspellings tests, part 3 -make: "directives.mk" line 120: which branch is taken on misspelling after false? -make: "directives.mk" line 127: else taken -make: "directives.mk" line 130: which branch is taken on misspelling after true? -make: "directives.mk" line 132: Unknown directive "elsif" -make: "directives.mk" line 133: 1 taken -make: "directives.mk" line 134: Unknown directive "elsif" -make: "directives.mk" line 135: 2 taken -make: "directives.mk" line 140: Unknown directive "indented" -make: "directives.mk" line 141: Unknown directive "indented" -make: "directives.mk" line 142: Unknown directive "indented" -make: "directives.mk" line 143: Unknown directive "info" -make: "directives.mk" line 150: Could not find nonexistent.mk -make: "directives.mk" line 160: end of the tests -make: Fatal errors encountered -- cannot continue -make: stopped in unit-tests -exit status 1 Index: head/contrib/bmake/unit-tests/varshell.exp =================================================================== --- head/contrib/bmake/unit-tests/varshell.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varshell.exp (nonexistent) @@ -1,10 +0,0 @@ -make: "varshell.mk" line 6: warning: "/bin/no/such/command 2> /dev/null" returned non-zero status -make: "varshell.mk" line 9: warning: "false" returned non-zero status -make: "varshell.mk" line 10: 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/Makefile =================================================================== --- head/contrib/bmake/unit-tests/Makefile (revision 367862) +++ head/contrib/bmake/unit-tests/Makefile (revision 367863) @@ -1,565 +1,602 @@ -# $Id: Makefile,v 1.107 2020/11/02 00:40:25 sjg Exp $ +# $Id: Makefile,v 1.115 2020/11/18 04:01:07 sjg Exp $ # -# $NetBSD: Makefile,v 1.181 2020/11/01 19:02:22 rillig Exp $ +# $NetBSD: Makefile,v 1.206 2020/11/18 01:12:00 sjg Exp $ # # Unit tests for make(1) # # The main targets are: # # all: # run all the tests # test: # run 'all', and compare to expected results # accept: # move generated output to expected results # # Settable variables # # TEST_MAKE # The make program to be tested. # # # Adding a test case # # Each feature should get its own set of tests in its own suitably # named makefile (*.mk), with its own set of expected results (*.exp), # and it should be added to the TESTS list. # # A few *.mk files are helper files for other tests (such as include-sub.mk) # and are thus not added to TESTS. Such files must be ignored in # src/tests/usr.bin/make/t_make.sh. # # Each test is in a sub-makefile. # Keep the list sorted. # Any test that is commented out must be ignored in # src/tests/usr.bin/make/t_make.sh as well. #TESTS+= archive TESTS+= archive-suffix +TESTS+= cmd-errors +TESTS+= cmd-errors-lint TESTS+= cmd-interrupt TESTS+= cmdline +TESTS+= cmdline-undefined TESTS+= comment TESTS+= cond-cmp-numeric TESTS+= cond-cmp-numeric-eq TESTS+= cond-cmp-numeric-ge TESTS+= cond-cmp-numeric-gt TESTS+= cond-cmp-numeric-le TESTS+= cond-cmp-numeric-lt TESTS+= cond-cmp-numeric-ne TESTS+= cond-cmp-string TESTS+= cond-cmp-unary TESTS+= cond-func TESTS+= cond-func-commands TESTS+= cond-func-defined TESTS+= cond-func-empty TESTS+= cond-func-exists TESTS+= cond-func-make TESTS+= cond-func-target TESTS+= cond-late TESTS+= cond-op TESTS+= cond-op-and +TESTS+= cond-op-and-lint TESTS+= cond-op-not TESTS+= cond-op-or +TESTS+= cond-op-or-lint TESTS+= cond-op-parentheses TESTS+= cond-short TESTS+= cond-token-number TESTS+= cond-token-plain TESTS+= cond-token-string TESTS+= cond-token-var TESTS+= cond-undef-lint TESTS+= cond1 TESTS+= counter TESTS+= counter-append TESTS+= dep TESTS+= dep-colon TESTS+= dep-colon-bug-cross-file TESTS+= dep-double-colon TESTS+= dep-double-colon-indep TESTS+= dep-exclam TESTS+= dep-none TESTS+= dep-percent TESTS+= dep-var TESTS+= dep-wildcards TESTS+= depsrc TESTS+= depsrc-end TESTS+= depsrc-exec TESTS+= depsrc-ignore TESTS+= depsrc-made TESTS+= depsrc-make TESTS+= depsrc-meta TESTS+= depsrc-nometa TESTS+= depsrc-nometa_cmp TESTS+= depsrc-nopath TESTS+= depsrc-notmain TESTS+= depsrc-optional TESTS+= depsrc-phony TESTS+= depsrc-precious TESTS+= depsrc-recursive TESTS+= depsrc-silent TESTS+= depsrc-use TESTS+= depsrc-usebefore TESTS+= depsrc-usebefore-double-colon TESTS+= depsrc-wait TESTS+= deptgt TESTS+= deptgt-begin TESTS+= deptgt-default TESTS+= deptgt-delete_on_error TESTS+= deptgt-end TESTS+= deptgt-end-jobs TESTS+= deptgt-error TESTS+= deptgt-ignore TESTS+= deptgt-interrupt TESTS+= deptgt-main TESTS+= deptgt-makeflags TESTS+= deptgt-no_parallel TESTS+= deptgt-nopath TESTS+= deptgt-notparallel TESTS+= deptgt-objdir TESTS+= deptgt-order TESTS+= deptgt-path TESTS+= deptgt-path-suffix TESTS+= deptgt-phony TESTS+= deptgt-precious TESTS+= deptgt-shell TESTS+= deptgt-silent TESTS+= deptgt-stale TESTS+= deptgt-suffixes TESTS+= dir TESTS+= dir-expand-path TESTS+= directive TESTS+= directive-dinclude TESTS+= directive-elif TESTS+= directive-elifdef TESTS+= directive-elifmake TESTS+= directive-elifndef TESTS+= directive-elifnmake TESTS+= directive-else TESTS+= directive-endif TESTS+= directive-error TESTS+= directive-export TESTS+= directive-export-env TESTS+= directive-export-gmake TESTS+= directive-export-literal TESTS+= directive-for TESTS+= directive-for-generating-endif TESTS+= directive-hyphen-include TESTS+= directive-if +TESTS+= directive-if-nested TESTS+= directive-ifdef TESTS+= directive-ifmake TESTS+= directive-ifndef TESTS+= directive-ifnmake TESTS+= directive-include TESTS+= directive-include-fatal TESTS+= directive-info TESTS+= directive-sinclude TESTS+= directive-undef TESTS+= directive-unexport TESTS+= directive-unexport-env TESTS+= directive-warning -TESTS+= directives TESTS+= dollar TESTS+= doterror TESTS+= dotwait TESTS+= envfirst TESTS+= error TESTS+= # escape # broken by reverting POSIX changes TESTS+= export TESTS+= export-all TESTS+= export-env TESTS+= export-variants TESTS+= forloop TESTS+= forsubst +TESTS+= gnode-submake TESTS+= hanoi-include TESTS+= impsrc TESTS+= include-main +TESTS+= job-flags #TESTS+= job-output-long-lines TESTS+= lint TESTS+= make-exported TESTS+= moderrs TESTS+= modmatch TESTS+= modmisc TESTS+= modts TESTS+= modword +TESTS+= objdir-writable TESTS+= opt TESTS+= opt-backwards TESTS+= opt-chdir TESTS+= opt-debug TESTS+= opt-debug-all TESTS+= opt-debug-archive TESTS+= opt-debug-curdir TESTS+= opt-debug-cond TESTS+= opt-debug-dir TESTS+= opt-debug-errors TESTS+= opt-debug-file TESTS+= opt-debug-for TESTS+= opt-debug-graph1 TESTS+= opt-debug-graph2 TESTS+= opt-debug-graph3 TESTS+= opt-debug-hash #TESTS+= opt-debug-jobs TESTS+= opt-debug-lint TESTS+= opt-debug-loud TESTS+= opt-debug-meta TESTS+= opt-debug-making TESTS+= opt-debug-no-rm TESTS+= opt-debug-parse TESTS+= opt-debug-suff TESTS+= opt-debug-targets TESTS+= opt-debug-varraw TESTS+= opt-debug-var TESTS+= opt-debug-x-trace TESTS+= opt-define TESTS+= opt-env TESTS+= opt-file TESTS+= opt-ignore TESTS+= opt-include-dir TESTS+= opt-jobs TESTS+= opt-jobs-internal TESTS+= opt-keep-going TESTS+= opt-m-include-dir TESTS+= opt-no-action TESTS+= opt-no-action-at-all TESTS+= opt-query TESTS+= opt-raw TESTS+= opt-silent TESTS+= opt-touch +TESTS+= opt-touch-jobs TESTS+= opt-tracefile TESTS+= opt-var-expanded TESTS+= opt-var-literal TESTS+= opt-warnings-as-errors TESTS+= opt-where-am-i TESTS+= opt-x-reduce-exported TESTS+= order TESTS+= parse-var TESTS+= phony-end TESTS+= posix TESTS+= # posix1 # broken by reverting POSIX changes TESTS+= qequals TESTS+= recursive TESTS+= sh TESTS+= sh-dots TESTS+= sh-jobs TESTS+= sh-jobs-error TESTS+= sh-leading-at TESTS+= sh-leading-hyphen TESTS+= sh-leading-plus TESTS+= sh-meta-chars TESTS+= sh-multi-line TESTS+= sh-single-line TESTS+= shell-csh TESTS+= shell-custom +.if exists(/bin/ksh) TESTS+= shell-ksh +.endif TESTS+= shell-sh TESTS+= suff-add-later TESTS+= suff-clear-regular TESTS+= suff-clear-single TESTS+= suff-lookup TESTS+= suff-main TESTS+= suff-rebuild +TESTS+= suff-self TESTS+= suff-transform-endless TESTS+= suff-transform-expand TESTS+= suff-transform-select TESTS+= sunshcmd TESTS+= ternary TESTS+= unexport TESTS+= unexport-env TESTS+= use-inference TESTS+= var-class TESTS+= var-class-cmdline TESTS+= var-class-env TESTS+= var-class-global TESTS+= var-class-local TESTS+= var-class-local-legacy TESTS+= var-op TESTS+= var-op-append TESTS+= var-op-assign TESTS+= var-op-default TESTS+= var-op-expand TESTS+= var-op-shell TESTS+= var-op-sunsh TESTS+= var-recursive TESTS+= varcmd TESTS+= vardebug TESTS+= varfind TESTS+= varmisc TESTS+= varmod TESTS+= varmod-assign TESTS+= varmod-defined TESTS+= varmod-edge TESTS+= varmod-exclam-shell TESTS+= varmod-extension TESTS+= varmod-gmtime TESTS+= varmod-hash TESTS+= varmod-head TESTS+= varmod-ifelse TESTS+= varmod-l-name-to-value TESTS+= varmod-localtime TESTS+= varmod-loop TESTS+= varmod-match TESTS+= varmod-match-escape TESTS+= varmod-no-match TESTS+= varmod-order TESTS+= varmod-order-reverse TESTS+= varmod-order-shuffle TESTS+= varmod-path TESTS+= varmod-quote TESTS+= varmod-quote-dollar TESTS+= varmod-range TESTS+= varmod-remember TESTS+= varmod-root TESTS+= varmod-select-words TESTS+= varmod-shell TESTS+= varmod-subst TESTS+= varmod-subst-regex TESTS+= varmod-sysv TESTS+= varmod-tail TESTS+= varmod-to-abs TESTS+= varmod-to-lower TESTS+= varmod-to-many-words TESTS+= varmod-to-one-word TESTS+= varmod-to-separator TESTS+= varmod-to-upper TESTS+= varmod-undefined TESTS+= varmod-unique TESTS+= varname TESTS+= varname-dollar TESTS+= varname-dot-alltargets TESTS+= varname-dot-curdir TESTS+= varname-dot-includes TESTS+= varname-dot-includedfromdir TESTS+= varname-dot-includedfromfile TESTS+= varname-dot-libs TESTS+= varname-dot-make-dependfile TESTS+= varname-dot-make-expand_variables TESTS+= varname-dot-make-exported TESTS+= varname-dot-make-jobs TESTS+= varname-dot-make-jobs-prefix TESTS+= varname-dot-make-level TESTS+= varname-dot-make-makefile_preference TESTS+= varname-dot-make-makefiles TESTS+= varname-dot-make-meta-bailiwick TESTS+= varname-dot-make-meta-created TESTS+= varname-dot-make-meta-files TESTS+= varname-dot-make-meta-ignore_filter TESTS+= varname-dot-make-meta-ignore_paths TESTS+= varname-dot-make-meta-ignore_patterns TESTS+= varname-dot-make-meta-prefix TESTS+= varname-dot-make-mode TESTS+= varname-dot-make-path_filemon TESTS+= varname-dot-make-pid TESTS+= varname-dot-make-ppid TESTS+= varname-dot-make-save_dollars TESTS+= varname-dot-makeoverrides TESTS+= varname-dot-newline TESTS+= varname-dot-objdir TESTS+= varname-dot-parsedir TESTS+= varname-dot-parsefile TESTS+= varname-dot-path TESTS+= varname-dot-shell TESTS+= varname-dot-targets TESTS+= varname-empty TESTS+= varname-make TESTS+= varname-make_print_var_on_error TESTS+= varname-make_print_var_on_error-jobs TESTS+= varname-makefile TESTS+= varname-makeflags TESTS+= varname-pwd TESTS+= varname-vpath TESTS+= varparse-dynamic +TESTS+= varparse-errors TESTS+= varparse-mod TESTS+= varparse-undef-partial TESTS+= varquote -TESTS+= varshell +# Ideas for more tests: +# char-0020-space.mk +# char-005C-backslash.mk +# escape-cond-str.mk +# escape-cond-func-arg.mk +# escape-cond-func-arg.mk +# escape-varmod.mk +# escape-varmod-define.mk +# escape-varmod-match.mk +# escape-varname.mk +# escape-varassign-varname.mk +# escape-varassign-varname-cmdline.mk +# escape-varassign-value.mk +# escape-varassign-value-cmdline.mk +# escape-dependency-source.mk +# escape-dependency-target.mk +# escape-for-varname.mk +# escape-for-item.mk +# posix-*.mk (see posix.mk and posix1.mk) + +.if ${.OBJDIR} != ${.CURDIR} +RO_OBJDIR:= ${.OBJDIR}/roobj +.else +RO_OBJDIR:= ${TMPDIR:U/tmp}/roobj +.endif # Additional environment variables for some of the tests. # The base environment is -i PATH="$PATH". +ENV.depsrc-optional+= TZ=UTC ENV.envfirst= FROM_ENV=value-from-env +ENV.objdir-writable+= RO_OBJDIR=${RO_OBJDIR} ENV.varmisc= FROM_ENV=env ENV.varmisc+= FROM_ENV_BEFORE=env ENV.varmisc+= FROM_ENV_AFTER=env ENV.varmod-localtime+= TZ=Europe/Berlin +ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2 # Override make flags for some of the tests; default is -k. # If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of # settings FLAGS.test=-dv here, since that is closer to the test code. FLAGS.cond-func-make= via-cmdline FLAGS.directive-ifmake= first second -FLAGS.doterror= # none -FLAGS.envfirst= -e -FLAGS.export= # none -FLAGS.opt-ignore= -i -FLAGS.opt-keep-going= -k -FLAGS.opt-no-action= -n -FLAGS.opt-query= -q -FLAGS.opt-var-expanded= -v VAR -v VALUE -FLAGS.opt-var-literal= -V VAR -V VALUE -FLAGS.opt-warnings-as-errors= -W -FLAGS.order= -j1 -FLAGS.recursive= -dL -FLAGS.sh-leading-plus= -n -FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmline-plain' +FLAGS.doterror= # none, especially not -k +FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain' # Some tests need extra postprocessing. SED_CMDS.export= \ -e '/^[^=_A-Za-z0-9]*=/d' # these all share the same requirement .for t in export-all export-env SED_CMDS.$t= ${SED_CMDS.export} .endfor +SED_CMDS.directive-export-gmake= \ + ${:D dash is a pain } \ + -e /non-zero/d SED_CMDS.job-output-long-lines= \ ${:D Job separators on their own line are ok. } \ -e '/^--- job-[ab] ---$$/d' \ ${:D Plain output lines are ok as well. } \ ${:D They may come in multiples of 1024 or as 10000. } \ -e '/^aa*$$/d' \ -e '/^bb*$$/d' \ ${:D The following lines should rather not occur since the job } \ ${:D marker should always be at the beginning of the line. } \ -e '/^aa*--- job-b ---$$/d' \ -e '/^bb*--- job-a ---$$/d' +SED_CMDS.objdir-writable= -e 's,${RO_OBJDIR},OBJDIR/roobj,g' SED_CMDS.opt-debug-graph1= \ -e 's,${.CURDIR},CURDIR,' SED_CMDS.opt-debug-graph1+= \ -e '/Global Variables:/,/Suffixes:/d' SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,,' SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(),' SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid ,' SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process ,' SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: ,' # The "-q" may be there or not, see jobs.c, variable shells. SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: sh\) -q,\1,' +SED_CMDS.var-op-shell+= -e 's,^${.SHELL:T}: ,,' +SED_CMDS.var-op-shell+= -e '/command/{ s,^[1-9]: ,,;s,No such.*,not found,; }' +SED_CMDS.vardebug= \ + ${:D canonicalize .SHELL } \ + -e 's,${.SHELL},,' SED_CMDS.varmod-subst-regex+= \ -e 's,\(Regex compilation error:\).*,\1 (details omitted),' SED_CMDS.varmod-edge+= -e 's, line [0-9]*:, line omitted:,' -SED_CMDS.varshell+= -e 's,^${.SHELL:T}: ,,' -SED_CMDS.varshell+= -e '/command/s,No such.*,not found,' SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: ",' SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: ",' SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g' SED_CMDS.varname-dot-shell+= -e 's,"/[^" ]*","(details omitted)",g' SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g' # Some tests need an additional round of postprocessing. POSTPROC.deptgt-suffixes= \ ${TOOL_SED} -n -e '/^\#\*\*\* Suffixes/,/^\#\*/p' -POSTPROC.varname= ${TOOL_SED} -n -e '/^MAGIC/p' -e '/^ORDER_/p' +POSTPROC.gnode-submake= awk '/Input graph/, /^$$/' POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p' # Some tests reuse other tests, which makes them unnecessarily fragile. export-all.rawout: export.mk unexport.rawout: export.mk unexport-env.rawout: export.mk # End of the configuration section. .MAIN: all .-include "Makefile.inc" .-include "Makefile.config" UNIT_TESTS:= ${.PARSEDIR} .PATH: ${UNIT_TESTS} .if ${USE_ABSOLUTE_TESTNAMES:Uno} == yes OUTFILES= ${TESTS:@test@${.CURDIR:tA}/${test}.out@} .else OUTFILES= ${TESTS:=.out} .endif all: ${OUTFILES} CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp CLEANFILES+= obj*.[och] lib*.a # posix1.mk CLEANFILES+= issue* .[ab]* # suffixes.mk CLEANDIRS= dir dummy # posix1.mk clean: rm -f ${CLEANFILES} rm -rf ${CLEANDIRS} 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 .if ${.MAKE.MODE:Unormal:Mmeta} != "" # we don't need the noise _MKMSG_TEST= : .endif MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc # Each test is run in a sub-make, to keep the tests for interfering with # each other, and because they use different environment variables and # command line options. .SUFFIXES: .mk .rawout .out .mk.rawout: @${_MKMSG_TEST:Uecho '# test '} ${.PREFIX} @set -eu; \ cd ${.OBJDIR}; \ env -i PATH="$$PATH" ${MAKE_TEST_ENV} ${ENV.${.PREFIX:T}} \ ${TEST_MAKE} \ -r -C ${.CURDIR} -f ${.IMPSRC} \ ${FLAGS.${.PREFIX:T}:U-k} \ > ${.TARGET}.tmp 2>&1 \ && status=$$? || status=$$?; \ echo $$status > ${.TARGET:R}.status @mv ${.TARGET}.tmp ${.TARGET} # Postprocess the test output so that the results can be compared. # # always pretend .MAKE was called 'make' _SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,' _SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,' +_SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}[][0-9]* warning,make warning,' +_SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,' # replace anything after 'stopped in' with unit-tests _SED_CMDS+= -e '/stopped/s, /.*, unit-tests,' # strip ${.CURDIR}/ from the output _SED_CMDS+= -e 's,${.CURDIR:S,.,\\.,g}/,,g' _SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g' .rawout.out: @${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.PREFIX:T}} \ < ${.IMPSRC} > ${.TARGET}.tmp1 @${POSTPROC.${.PREFIX:T}:Ucat} < ${.TARGET}.tmp1 > ${.TARGET}.tmp2 @rm ${.TARGET}.tmp1 @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp2 @mv ${.TARGET}.tmp2 ${.TARGET} # Compare all output files test: ${OUTFILES} .PHONY @failed= ; \ for test in ${TESTS}; do \ ${TOOL_DIFF} ${DIFF_FLAGS} ${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: @for test in ${TESTS}; 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}) ${TESTS:=.rawout}: ${TEST_MAKE} # in meta mode, we *know* if a target script is impacted # by a makefile change. .if ${.MAKE.MODE:Unormal:Mmeta} == "" ${TESTS:=.rawout}: ${.PARSEDIR}/Makefile .endif .endif .-include Index: head/contrib/bmake/unit-tests/archive-suffix.mk =================================================================== --- head/contrib/bmake/unit-tests/archive-suffix.mk (revision 367862) +++ head/contrib/bmake/unit-tests/archive-suffix.mk (revision 367863) @@ -1,23 +1,23 @@ -# $NetBSD: archive-suffix.mk,v 1.1 2020/08/29 14:47:26 rillig Exp $ +# $NetBSD: archive-suffix.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ # # Between 2020-08-23 and 2020-08-30, the below code produced an assertion -# failure in Var_Set_with_flags, triggered by Compat_Make, when setting the +# failure in Var_SetWithFlags, triggered by Compat_Make, when setting the # .IMPSRC of an archive node to its .TARGET. # # The code assumed that the .TARGET variable of every node would be set, but -# but that is not guaranteed. +# that is not guaranteed. # # Between 2016-03-15 and 2016-03-16 the behavior of the below code changed. # Until 2016-03-15, it remade the target, starting with 2016-03-16 it says # "`all' is up to date". .SUFFIXES: .SUFFIXES: .c .o all: lib.a(obj1.o) .c.o: : making $@ obj1.c: : $@ Index: head/contrib/bmake/unit-tests/archive.mk =================================================================== --- head/contrib/bmake/unit-tests/archive.mk (revision 367862) +++ head/contrib/bmake/unit-tests/archive.mk (revision 367863) @@ -1,62 +1,60 @@ -# $NetBSD: archive.mk,v 1.10 2020/10/09 06:44:42 rillig Exp $ +# $NetBSD: archive.mk,v 1.11 2020/11/15 14:07:53 rillig Exp $ # # Very basic demonstration of handling archives, based on the description # in PSD.doc/tutorial.ms. # # This test aims at covering the code, not at being an introduction to -# archive handling. That's why it is more complicated and detailed than -# strictly necessary. +# archive handling. That's why it deviates from the tutorial style of +# several other tests. ARCHIVE= libprog.a FILES= archive.mk modmisc.mk varmisc.mk -MAKE_CMD= ${.MAKE} -f ${MAKEFILE} -RUN?= @set -eu; - all: .if ${.PARSEDIR:tA} != ${.CURDIR:tA} @cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR} .endif # The following targets create and remove files. The filesystem cache in # dir.c would probably not handle this correctly, therefore each of the # targets is run in its separate sub-make. - ${RUN} ${MAKE_CMD} remove-archive - ${RUN} ${MAKE_CMD} create-archive - ${RUN} ${MAKE_CMD} list-archive - ${RUN} ${MAKE_CMD} list-archive-wildcard - ${RUN} ${MAKE_CMD} depend-on-existing-member - ${RUN} ${MAKE_CMD} depend-on-nonexistent-member - ${RUN} ${MAKE_CMD} remove-archive + @${MAKE} -f ${MAKEFILE} remove-archive + @${MAKE} -f ${MAKEFILE} create-archive + @${MAKE} -f ${MAKEFILE} list-archive + @${MAKE} -f ${MAKEFILE} list-archive-wildcard + @${MAKE} -f ${MAKEFILE} depend-on-existing-member + @${MAKE} -f ${MAKEFILE} depend-on-nonexistent-member + @${MAKE} -f ${MAKEFILE} remove-archive create-archive: ${ARCHIVE} pre post # The indirect references with the $$ cover the code in Arch_ParseArchive # that calls Var_Parse. It's an esoteric scenario since at the point where # Arch_ParseArchive is called, the dependency line is already fully expanded. # ${ARCHIVE}: $${:Ulibprog.a}(archive.mk modmisc.mk $${:Uvarmisc.mk}) pre post ar cru ${.TARGET} ${.OODATE:O} ranlib ${.TARGET} list-archive: ${ARCHIVE} pre post ar t ${.ALLSRC} # XXX: I had expected that this dependency would select all *.mk files from # the archive. Instead, the globbing is done in the current directory. +# # To prevent an overly long file list, the pattern is restricted to [at]*.mk. list-archive-wildcard: ${ARCHIVE}([at]*.mk) pre post - ${RUN} printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} + @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post - ${RUN} echo $@ + @echo $@ depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post - ${RUN} echo $@ + @echo $@ remove-archive: pre post rm -f ${ARCHIVE} pre: .USEBEFORE @echo Making ${.TARGET} ${.OODATE:C,.+,out-of-date,W} ${.OODATE:O} post: .USE @echo Index: head/contrib/bmake/unit-tests/cmd-errors-lint.exp =================================================================== --- head/contrib/bmake/unit-tests/cmd-errors-lint.exp (nonexistent) +++ head/contrib/bmake/unit-tests/cmd-errors-lint.exp (revision 367863) @@ -0,0 +1,9 @@ +: undefined +make: Unclosed variable "UNCLOSED" +: unclosed-variable +make: Unclosed variable expression (expecting '}') for "UNCLOSED" +: unclosed-modifier +make: Unknown modifier 'Z' +: unknown-modifier +: end +exit status 2 Index: head/contrib/bmake/unit-tests/cmd-errors-lint.mk =================================================================== --- head/contrib/bmake/unit-tests/cmd-errors-lint.mk (nonexistent) +++ head/contrib/bmake/unit-tests/cmd-errors-lint.mk (revision 367863) @@ -0,0 +1,32 @@ +# $NetBSD: cmd-errors-lint.mk,v 1.1 2020/11/02 20:43:27 rillig Exp $ +# +# Demonstrate how errors in variable expansions affect whether the commands +# are actually executed. + +.MAKEFLAGS: -dL + +all: undefined unclosed-variable unclosed-modifier unknown-modifier end + +# Undefined variables are not an error. They expand to empty strings. +undefined: + : $@ ${UNDEFINED} + +# XXX: As of 2020-11-01, this obvious syntax error is not detected. +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-variable: + : $@ ${UNCLOSED + +# XXX: As of 2020-11-01, this obvious syntax error is not detected. +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-modifier: + : $@ ${UNCLOSED: + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unknown-modifier: + : $@ ${UNKNOWN:Z} + +end: + : $@ Property changes on: head/contrib/bmake/unit-tests/cmd-errors-lint.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/cmd-errors.exp =================================================================== --- head/contrib/bmake/unit-tests/cmd-errors.exp (nonexistent) +++ head/contrib/bmake/unit-tests/cmd-errors.exp (revision 367863) @@ -0,0 +1,9 @@ +: undefined eol +make: Unclosed variable "UNCLOSED" +: unclosed-variable +make: Unclosed variable expression (expecting '}') for "UNCLOSED" +: unclosed-modifier +make: Unknown modifier 'Z' +: unknown-modifier eol +: end eol +exit status 0 Index: head/contrib/bmake/unit-tests/cmd-errors.mk =================================================================== --- head/contrib/bmake/unit-tests/cmd-errors.mk (nonexistent) +++ head/contrib/bmake/unit-tests/cmd-errors.mk (revision 367863) @@ -0,0 +1,30 @@ +# $NetBSD: cmd-errors.mk,v 1.3 2020/11/09 23:36:34 rillig Exp $ +# +# Demonstrate how errors in variable expansions affect whether the commands +# are actually executed. + +all: undefined unclosed-variable unclosed-modifier unknown-modifier end + +# Undefined variables are not an error. They expand to empty strings. +undefined: + : $@ ${UNDEFINED} eol + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-variable: + : $@ ${UNCLOSED + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-modifier: + : $@ ${UNCLOSED: + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unknown-modifier: + : $@ ${UNKNOWN:Z} eol + +end: + : $@ eol + +# XXX: As of 2020-11-02, despite the parse errors, the exit status is 0. Property changes on: head/contrib/bmake/unit-tests/cmd-errors.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/cmd-interrupt.mk =================================================================== --- head/contrib/bmake/unit-tests/cmd-interrupt.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cmd-interrupt.mk (revision 367863) @@ -1,50 +1,50 @@ -# $NetBSD: cmd-interrupt.mk,v 1.2 2020/08/28 18:16:22 rillig Exp $ +# $NetBSD: cmd-interrupt.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ # # Tests for interrupting a command. # # If a command is interrupted (usually by the user, here by itself), the # target is removed. This is to avoid having an unfinished target that # would be newer than all of its sources and would therefore not be # tried again in the next run. # # This happens for ordinary targets as well as for .PHONY targets, even # though the .PHONY targets usually do not correspond to a file. # # To protect the target from being removed, the target has to be marked with # the special source .PRECIOUS. These targets need to ensure for themselves # that interrupting them does not leave an inconsistent state behind. # # See also: # CompatDeleteTarget all: clean-before interrupt-ordinary interrupt-phony interrupt-precious clean-after clean-before clean-after: .PHONY @rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious -interrupt-ordinary: .PHONY +interrupt-ordinary: @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true # The ././ is necessary to work around the file cache. @echo ${.TARGET}: ${exists(././cmd-interrupt-ordinary) :? error : ok } interrupt-phony: .PHONY @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-phony || true # The ././ is necessary to work around the file cache. @echo ${.TARGET}: ${exists(././cmd-interrupt-phony) :? error : ok } interrupt-precious: .PRECIOUS @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-precious || true # The ././ is necessary to work around the file cache. @echo ${.TARGET}: ${exists(././cmd-interrupt-precious) :? ok : error } cmd-interrupt-ordinary: > ${.TARGET} @kill -INT ${.MAKE.PID} cmd-interrupt-phony: .PHONY > ${.TARGET} @kill -INT ${.MAKE.PID} cmd-interrupt-precious: .PRECIOUS > ${.TARGET} @kill -INT ${.MAKE.PID} Index: head/contrib/bmake/unit-tests/cmdline-undefined.exp =================================================================== --- head/contrib/bmake/unit-tests/cmdline-undefined.exp (nonexistent) +++ head/contrib/bmake/unit-tests/cmdline-undefined.exp (revision 367863) @@ -0,0 +1,17 @@ +The = assignment operator +make: "cmdline-undefined.mk" line 29: From the command line: Undefined is . +make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is . +make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is . +make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined. +make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined. +make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined. + +The := assignment operator +make: "cmdline-undefined.mk" line 29: From the command line: Undefined is . +make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is . +make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is . +make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined. +make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined. +make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined. + +exit status 0 Index: head/contrib/bmake/unit-tests/cmdline-undefined.mk =================================================================== --- head/contrib/bmake/unit-tests/cmdline-undefined.mk (nonexistent) +++ head/contrib/bmake/unit-tests/cmdline-undefined.mk (revision 367863) @@ -0,0 +1,40 @@ +# $NetBSD: cmdline-undefined.mk,v 1.2 2020/11/04 04:49:33 rillig Exp $ +# +# Tests for undefined variable expressions in the command line. + +all: + # When the command line is parsed, variable assignments using the + # '=' assignment operator do get their variable name expanded + # (which probably occurs rarely in practice, if at all), but their + # variable value is not expanded, as usual. + # + @echo 'The = assignment operator' + @${.MAKE} -f ${MAKEFILE} print-undefined \ + CMDLINE='Undefined is $${UNDEFINED}.' + @echo + + # The interesting case is using the ':=' assignment operator, which + # expands its right-hand side. But only those variables that are + # defined. + @echo 'The := assignment operator' + @${.MAKE} -f ${MAKEFILE} print-undefined \ + CMDLINE:='Undefined is $${UNDEFINED}.' + @echo + +.if make(print-undefined) + +.MAKEFLAGS: MAKEFLAGS_ASSIGN='Undefined is $${UNDEFINED}.' +.MAKEFLAGS: MAKEFLAGS_SUBST:='Undefined is $${UNDEFINED}.' + +.info From the command line: ${CMDLINE} +.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN} +.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST} + +UNDEFINED?= now defined + +.info From the command line: ${CMDLINE} +.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN} +.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST} + +print-undefined: +.endif Property changes on: head/contrib/bmake/unit-tests/cmdline-undefined.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/cmdline.mk =================================================================== --- head/contrib/bmake/unit-tests/cmdline.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cmdline.mk (revision 367863) @@ -1,37 +1,36 @@ -# $NetBSD: cmdline.mk,v 1.1 2020/07/28 22:44:44 rillig Exp $ +# $NetBSD: cmdline.mk,v 1.2 2020/11/15 14:07:53 rillig Exp $ # # Tests for command line parsing and related special variables. -RUN?= @set -eu; TMPBASE?= /tmp SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID MAKE_CMD= env TMPBASE=${TMPBASE}/${SUB1} ${.MAKE} -f ${MAKEFILE} -r DIR2= ${TMPBASE}/${SUB2} DIR12= ${TMPBASE}/${SUB1}/${SUB2} all: prepare-dirs all: makeobjdir-direct makeobjdir-indirect prepare-dirs: - ${RUN} rm -rf ${DIR2} ${DIR12} - ${RUN} mkdir -p ${DIR2} ${DIR12} + @rm -rf ${DIR2} ${DIR12} + @mkdir -p ${DIR2} ${DIR12} # The .OBJDIR can be set via the MAKEOBJDIR command line variable. # It must be a command line variable; an environment variable would not work. makeobjdir-direct: @echo $@: - ${RUN} ${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir + @${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir # The .OBJDIR can be set via the MAKEOBJDIR command line variable, # and that variable could even contain the usual modifiers. # Since the .OBJDIR=MAKEOBJDIR assignment happens very early, # the SUB2 variable in the modifier is not defined yet and is therefore empty. # The SUB1 in the resulting path comes from the environment variable TMPBASE, # see MAKE_CMD. makeobjdir-indirect: @echo $@: - ${RUN} ${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir + @${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir show-objdir: @echo $@: ${.OBJDIR:Q} Index: head/contrib/bmake/unit-tests/comment.mk =================================================================== --- head/contrib/bmake/unit-tests/comment.mk (revision 367862) +++ head/contrib/bmake/unit-tests/comment.mk (revision 367863) @@ -1,74 +1,78 @@ -# $NetBSD: comment.mk,v 1.2 2020/09/07 19:17:36 rillig Exp $ +# $NetBSD: comment.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ # # Demonstrate how comments are written in makefiles. # This is a comment. #\ This is a multiline comment. # Another multiline comment \ that \ goes \ on and on. - # Comments can be indented, but that is rather unusual. + # Comments can be indented with spaces, but that is rather unusual. # Comments can be indented with a tab. # These are not shell commands, they are just makefile comments. .if 1 # There can be comments after conditions. .endif # And after the closing directive. VAR= # This comment makes the variable value empty. + # ParseGetLine removes any whitespace before the + # comment. .if ${VAR} != "" . error .endif # The comment does not need to start at the beginning of a word (as in the # shell), it can start anywhere. VAR=# defined but empty # The space before the comment is always trimmed. VAR= value .if ${VAR} != "value" . error .endif -# This is NOT an escaped comment due to the double backslashes \\ +# This comment ends with 2 backslashes. An even number of backslashes does +# not count as a line continuation, therefore the variable assignment that +# follows is actively interpreted. \\ VAR= not part of the comment .if ${VAR} != "not part of the comment" . error .endif # To escape a comment sign, precede it with a backslash. VAR= \# # Both in the assignment. .if ${VAR} != "\#" # And in the comparison. . error .endif # Since 2012-03-24 the variable modifier :[#] does not need to be escaped. # To keep the parsing code simple, any "[#" does not start a comment, even # outside of a variable expression. WORDS= ${VAR:[#]} [# .if ${WORDS} != "1 [#" . error .endif -# An odd number of comment signs makes a line continuation, \\\ +# An odd number of backslashes makes a line continuation, \\\ no matter if it is 3 or 5 \\\\\ or 9 backslashes. \\\\\\\\\ This is the last line of the comment. VAR= no comment anymore .if ${VAR} != "no comment anymore" . error .endif all: # In the commands associated with a target, the '#' does not start a makefile # comment. The '#' is just passed to the shell, like any ordinary character. echo This is a shell comment: # comment # If the '#' were to start a makefile comment, the following shell command # would have unbalanced quotes. echo This is not a shell comment: '# comment' @echo A shell comment can#not start in the middle of a word. Index: head/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp (revision 367863) @@ -1,6 +1,6 @@ -make: "cond-cmp-numeric-eq.mk" line 54: warning: Unknown operator -make: "cond-cmp-numeric-eq.mk" line 54: Malformed conditional (!(12345 = 12345)) -make: "cond-cmp-numeric-eq.mk" line 61: Malformed conditional (!(12345 === 12345)) +make: "cond-cmp-numeric-eq.mk" line 67: warning: Unknown operator +make: "cond-cmp-numeric-eq.mk" line 67: Malformed conditional (!(12345 = 12345)) +make: "cond-cmp-numeric-eq.mk" line 74: Malformed conditional (!(12345 === 12345)) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk (revision 367863) @@ -1,68 +1,81 @@ -# $NetBSD: cond-cmp-numeric-eq.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-cmp-numeric-eq.mk,v 1.5 2020/11/08 21:47:59 rillig Exp $ # # Tests for numeric comparisons with the == operator in .if conditions. # This comparison yields the same result, whether numeric or character-based. .if 1 == 1 .else . error .endif # This comparison yields the same result, whether numeric or character-based. .if 1 == 2 . error .endif .if 2 == 1 . error .endif # Scientific notation is supported, as per strtod. .if 2e7 == 2000e4 .else . error .endif .if 2000e4 == 2e7 .else . error .endif # Trailing zeroes after the decimal point are irrelevant for the numeric # value. .if 3.30000 == 3.3 .else . error .endif .if 3.3 == 3.30000 .else . error .endif # As of 2020-08-23, numeric comparison is implemented as parsing both sides # as double, and then performing a normal comparison. The range of double is # typically 16 or 17 significant digits, therefore these two numbers seem to # be equal. .if 1.000000000000000001 == 1.000000000000000002 .else . error .endif +# Because an IEEE 754 double can only hold integers with a mantissa of 53 +# bits, these two numbers are considered the same. The 993 is rounded down +# to the 992. +.if 9007199254740993 == 9007199254740992 +.else +. error +.endif +# The 995 is rounded up, the 997 is rounded down. +.if 9007199254740995 == 9007199254740997 +.else +. error Probably a misconfiguration in the floating point environment, \ + or maybe a machine without IEEE 754 floating point support. +.endif # There is no = operator for numbers. .if !(12345 = 12345) . error .else . error .endif # There is no === operator for numbers either. .if !(12345 === 12345) . error .else . error .endif all: @:; Index: head/contrib/bmake/unit-tests/cond-cmp-numeric.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-cmp-numeric.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-cmp-numeric.exp (revision 367863) @@ -1,11 +1,15 @@ CondParser_Eval: !(${:UINF} > 1e100) make: "cond-cmp-numeric.mk" line 11: warning: String comparison operator must be either == or != make: "cond-cmp-numeric.mk" line 11: Malformed conditional (!(${:UINF} > 1e100)) CondParser_Eval: ${:UNaN} > NaN make: "cond-cmp-numeric.mk" line 16: warning: String comparison operator must be either == or != make: "cond-cmp-numeric.mk" line 16: Malformed conditional (${:UNaN} > NaN) CondParser_Eval: !(${:UNaN} == NaN) lhs = "NaN", rhs = "NaN", op = == +CondParser_Eval: 123 ! 123 +lhs = 123.000000, rhs = 123.000000, op = ! +make: "cond-cmp-numeric.mk" line 34: warning: Unknown operator +make: "cond-cmp-numeric.mk" line 34: Malformed conditional (123 ! 123) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/cond-cmp-numeric.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-cmp-numeric.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-cmp-numeric.mk (revision 367863) @@ -1,29 +1,41 @@ -# $NetBSD: cond-cmp-numeric.mk,v 1.3 2020/09/12 18:01:51 rillig Exp $ +# $NetBSD: cond-cmp-numeric.mk,v 1.4 2020/11/08 22:56:16 rillig Exp $ # # Tests for numeric comparisons in .if conditions. .MAKEFLAGS: -dc # The ${:U...} on the left-hand side is necessary for the parser. # Even if strtod(3) parses "INF" as +Infinity, make does not accept this # since it is not really a number; see TryParseNumber. .if !(${:UINF} > 1e100) . error .endif # Neither is NaN a number; see TryParseNumber. .if ${:UNaN} > NaN . error .endif # Since NaN is not parsed as a number, both operands are interpreted # as strings and are therefore equal. If they were parsed as numbers, # they would compare unequal, since NaN is unequal to any and everything, # including itself. .if !(${:UNaN} == NaN) +. error +.endif + +# The parsing code in CondParser_Comparison only performs a light check on +# whether the operator is valid, leaving the rest of the work to the +# evaluation functions EvalCompareNum and EvalCompareStr. Ensure that this +# parse error is properly reported. +# +# XXX: The warning message does not mention the actual operator. +.if 123 ! 123 +. error +.else . error .endif all: @:; Index: head/contrib/bmake/unit-tests/cond-cmp-string.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-cmp-string.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-cmp-string.exp (revision 367863) @@ -1,8 +1,8 @@ make: "cond-cmp-string.mk" line 18: Malformed conditional (str != str) -make: "cond-cmp-string.mk" line 37: Malformed conditional ("string" != "str""ing") -make: "cond-cmp-string.mk" line 42: warning: String comparison operator must be either == or != -make: "cond-cmp-string.mk" line 42: Malformed conditional (!("value" = "value")) -make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" === "value")) +make: "cond-cmp-string.mk" line 42: Malformed conditional ("string" != "str""ing") +make: "cond-cmp-string.mk" line 49: warning: String comparison operator must be either == or != +make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" = "value")) +make: "cond-cmp-string.mk" line 56: Malformed conditional (!("value" === "value")) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/cond-cmp-string.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-cmp-string.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-cmp-string.mk (revision 367863) @@ -1,90 +1,110 @@ -# $NetBSD: cond-cmp-string.mk,v 1.11 2020/10/30 14:53:31 rillig Exp $ +# $NetBSD: cond-cmp-string.mk,v 1.13 2020/11/15 14:07:53 rillig Exp $ # # Tests for string comparisons in .if conditions. # This is a simple comparison of string literals. # Nothing surprising here. .if "str" != "str" . error .endif # The right-hand side of the comparison may be written without quotes. .if "str" != str . error .endif # The left-hand side of the comparison must be enclosed in quotes. # This one is not enclosed in quotes and thus generates an error message. .if str != str . error .endif -# The left-hand side of the comparison requires a defined variable. -# The variable named "" is not defined, but applying the :U modifier to it -# makes it "kind of defined" (see VAR_KEEP). Therefore it is ok here. +# The left-hand side of the comparison requires that any variable expression +# is defined. +# +# The variable named "" is never defined, nevertheless it can be used as a +# starting point for variable expressions. Applying the :U modifier to such +# an undefined expression turns it into a defined expression. +# +# See ApplyModifier_Defined and VEF_DEF. .if ${:Ustr} != "str" . error .endif # Any character in a string literal may be escaped using a backslash. # This means that "\n" does not mean a newline but a simple "n". .if "string" != "\s\t\r\i\n\g" . error .endif # It is not possible to concatenate two string literals to form a single -# string. +# string. In C, Python and the shell this is possible, but not in make. .if "string" != "str""ing" . error +.else +. error .endif # There is no = operator for strings. .if !("value" = "value") . error .else . error .endif # There is no === operator for strings either. .if !("value" === "value") . error .else . error .endif # A variable expression can be enclosed in double quotes. .if ${:Uword} != "${:Uword}" . error .endif # Between 2003-01-01 (maybe even earlier) and 2020-10-30, adding one of the # characters " \t!=><" directly after a variable expression resulted in a # "Malformed conditional", even though the string was well-formed. .if ${:Uword } != "${:Uword} " . error .endif # Some other characters worked though, and some didn't. # Those that are mentioned in is_separator didn't work. .if ${:Uword0} != "${:Uword}0" . error .endif .if ${:Uword&} != "${:Uword}&" . error .endif .if ${:Uword!} != "${:Uword}!" . error .endif .if ${:Uword<} != "${:Uword}<" . error .endif # Adding another variable expression to the string literal works though. .if ${:Uword} != "${:Uwo}${:Urd}" . error .endif # Adding a space at the beginning of the quoted variable expression works # though. .if ${:U word } != " ${:Uword} " +. error +.endif + +# If at least one side of the comparison is a string literal, the string +# comparison is performed. +.if 12345 != "12345" +. error +.endif + +# If at least one side of the comparison is a string literal, the string +# comparison is performed. The ".0" in the left-hand side makes the two +# sides of the equation unequal. +.if 12345.0 == "12345" . error .endif Index: head/contrib/bmake/unit-tests/cond-cmp-unary.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-cmp-unary.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-cmp-unary.exp (revision 367863) @@ -1 +1,2 @@ +make: "cond-cmp-unary.mk" line 53: This is only reached because of a bug in EvalNotEmpty. exit status 0 Index: head/contrib/bmake/unit-tests/cond-cmp-unary.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-cmp-unary.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-cmp-unary.mk (revision 367863) @@ -1,43 +1,58 @@ -# $NetBSD: cond-cmp-unary.mk,v 1.1 2020/09/14 06:22:59 rillig Exp $ +# $NetBSD: cond-cmp-unary.mk,v 1.2 2020/11/11 07:30:11 rillig Exp $ # # Tests for unary comparisons in .if conditions, that is, comparisons with # a single operand. If the operand is a number, it is compared to zero, # if it is a string, it is tested for emptiness. # The number 0 evaluates to false. .if 0 . error .endif # Any other number evaluates to true. .if !12345 . error .endif # The empty string evaluates to false. .if "" . error .endif # Any other string evaluates to true. .if !"0" . error .endif # The empty string may come from a variable expression. +# +# XXX: As of 2020-11-11, this empty string is interpreted "as a number" in +# EvalNotEmpty, which is plain wrong. The bug is in TryParseNumber. .if ${:U} . error .endif # A variable expression that is not surrounded by quotes is interpreted # as a number if possible, otherwise as a string. .if ${:U0} . error .endif # A non-zero number from a variable expression evaluates to true. .if !${:U12345} +. error +.endif + +# A string of whitespace should evaluate to false. +# +# XXX: As of 2020-11-11, the implementation in EvalNotEmpty does not skip +# whitespace before testing for the end. This was probably an oversight in +# a commit from 1992-04-15 saying "A variable is empty when it just contains +# spaces". +.if ${:U } +. info This is only reached because of a bug in EvalNotEmpty. +.else . error .endif all: # nothing Index: head/contrib/bmake/unit-tests/cond-func-commands.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-func-commands.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-func-commands.mk (revision 367863) @@ -1,36 +1,37 @@ -# $NetBSD: cond-func-commands.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-func-commands.mk,v 1.5 2020/11/15 14:07:53 rillig Exp $ # # Tests for the commands() function in .if conditions. .MAIN: all -# The target "target" does not exist yet, therefore it cannot have commands. +# At this point, the target 'target' does not exist yet, therefore it cannot +# have commands. Sounds obvious, but good to know that it is really so. .if commands(target) . error .endif target: # Now the target exists, but it still has no commands. .if commands(target) . error .endif target: # not a command # Even after the comment, the target still has no commands. .if commands(target) . error .endif target: @:; # Finally the target has commands. .if !commands(target) . error .endif all: @:; Index: head/contrib/bmake/unit-tests/cond-func-defined.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-func-defined.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-func-defined.exp (revision 367863) @@ -1,5 +1,10 @@ make: "cond-func-defined.mk" line 23: warning: Missing closing parenthesis for defined() make: "cond-func-defined.mk" line 23: Malformed conditional (!defined(A B)) +make: "cond-func-defined.mk" line 33: warning: Missing closing parenthesis for defined() +make: "cond-func-defined.mk" line 33: Malformed conditional (defined(DEF) +make: "cond-func-defined.mk" line 45: In .for loops, variable expressions for the loop variables are +make: "cond-func-defined.mk" line 46: substituted at evaluation time. There is no actual variable +make: "cond-func-defined.mk" line 47: involved, even if it feels like it. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/cond-func-defined.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-func-defined.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-func-defined.mk (revision 367863) @@ -1,33 +1,52 @@ -# $NetBSD: cond-func-defined.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-func-defined.mk,v 1.7 2020/11/15 14:07:53 rillig Exp $ # # Tests for the defined() function in .if conditions. DEF= defined ${:UA B}= variable name with spaces .if !defined(DEF) . error .endif # Horizontal whitespace (space tab) after the opening parenthesis is ignored. .if !defined( DEF) . error .endif # Horizontal whitespace (space tab) before the closing parenthesis is ignored. .if !defined(DEF ) . error .endif # The argument of a function must not directly contain whitespace. .if !defined(A B) . error .endif # If necessary, the whitespace can be generated by a variable expression. .if !defined(${:UA B}) . error .endif + +# Parse error: missing closing parenthesis; see ParseFuncArg. +.if defined(DEF +. error +.else +. error +.endif + +# Variables from .for loops are not defined. +# See directive-for.mk for more details. +.for var in value +. if defined(var) +. error +. else +. info In .for loops, variable expressions for the loop variables are +. info substituted at evaluation time. There is no actual variable +. info involved, even if it feels like it. +. endif +.endfor all: @:; Index: head/contrib/bmake/unit-tests/cond-func-empty.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-func-empty.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-func-empty.exp (revision 367863) @@ -1 +1,5 @@ -exit status 0 +make: "cond-func-empty.mk" line 152: Unclosed variable "WORD" +make: "cond-func-empty.mk" line 152: Malformed conditional (empty(WORD) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/cond-func-empty.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-func-empty.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-func-empty.mk (revision 367863) @@ -1,150 +1,159 @@ -# $NetBSD: cond-func-empty.mk,v 1.8 2020/09/23 08:11:28 rillig Exp $ +# $NetBSD: cond-func-empty.mk,v 1.10 2020/11/15 14:07:53 rillig Exp $ # # Tests for the empty() function in .if conditions, which tests a variable # expression for emptiness. # # Note that the argument in the parentheses is indeed a variable name, -# optionally followed by variable modifiers. This is like the defined() -# function. +# optionally followed by variable modifiers. # .undef UNDEF EMPTY= # empty SPACE= ${:U } WORD= word # An undefined variable is empty. .if !empty(UNDEF) . error .endif # An undefined variable has the empty string as the value, and the :M # variable modifier does not change that. # .if !empty(UNDEF:M*) . error .endif -# The :S modifier replaces the empty value with an actual word, and -# after that the expression is no longer empty. Because the variable -# was undefined in the first place, the expression has the flag VAR_JUNK -# but not VAR_KEEP, therefore it is still considered undefined. -# Only very few variable modifiers turn an undefined variable expression -# into a defined variable expression. The :U and :D modifiers belong to -# that group, but :S doesn't (see VAR_KEEP). +# The :S modifier replaces the empty value with an actual word. The +# expression is now no longer empty, but it is still possible to see whether +# the expression was based on an undefined variable. The expression has the +# flag VEF_UNDEF. # +# The expression does not have the flag VEF_DEF though, therefore it is still +# considered undefined. Yes, indeed, undefined but not empty. There are a +# few variable modifiers that turn an undefined expression into a defined +# expression, among them :U and :D, but not :S. +# # XXX: This is hard to explain to someone who doesn't know these # implementation details. # .if !empty(UNDEF:S,^$,value,W) . error .endif # The :U modifier modifies expressions based on undefined variables # (VAR_JUNK) by adding the VAR_KEEP flag, which marks the expression # as "being interesting enough to be further processed". # .if empty(UNDEF:S,^$,value,W:Ufallback) . error .endif # And now to the surprising part. Applying the following :S modifier to the -# undefined variable makes it non-empty, but the marker VAR_JUNK is preserved -# nevertheless. The :U modifier that follows only looks at VAR_JUNK to decide -# whether the variable is defined or not. This kind of makes sense since the -# :U modifier tests the _variable_, not the _expression_. +# undefined expression makes it non-empty, but the marker VEF_UNDEF is +# preserved nevertheless. The :U modifier that follows only looks at the +# VEF_UNDEF flag to decide whether the variable is defined or not. This kind +# of makes sense since the :U modifier tests the _variable_, not the +# _expression_. # -# But since the variable was undefined to begin with, the fallback value is -# used in this expression. +# But since the variable was undefined to begin with, the fallback value from +# the :U modifier is used in this expression. # .if ${UNDEF:S,^$,value,W:Ufallback} != "fallback" . error .endif # The variable EMPTY is completely empty (0 characters). .if !empty(EMPTY) . error .endif # The variable SPACE has a single space, which counts as being empty. .if !empty(SPACE) . error .endif # The variable .newline has a single newline, which counts as being empty. .if !empty(.newline) . error .endif # The empty variable named "" gets a fallback value of " ", which counts as # empty. # # Contrary to the other functions in conditionals, the trailing space is not # stripped off, as can be seen in the -dv debug log. If the space had been # stripped, it wouldn't make a difference in this case. # .if !empty(:U ) . error .endif # Now the variable named " " gets a non-empty value, which demonstrates that # neither leading nor trailing spaces are trimmed in the argument of the # function. If the spaces were trimmed, the variable name would be "" and # that variable is indeed undefined. Since ParseEmptyArg calls Var_Parse # without VARE_UNDEFERR, the value of the undefined variable is returned as # an empty string. ${:U }= space .if empty( ) . error .endif # The value of the following expression is " word", which is not empty. .if empty(:U word) . error .endif # The :L modifier creates a variable expression that has the same value as # its name, which both are "VAR" in this case. The value is therefore not # empty. .if empty(VAR:L) . error .endif # The variable WORD has the value "word", which does not count as empty. .if empty(WORD) . error .endif # The expression ${} for a variable with the empty name always evaluates # to an empty string (see Var_Parse, varUndefined). .if !empty() . error .endif # Ensure that variable expressions that appear as part of the argument are # properly parsed. Typical use cases for this are .for loops, which are # expanded to exactly these ${:U} expressions. # # If everything goes well, the argument expands to "WORD", and that variable # is defined at the beginning of this file. The surrounding 'W' and 'D' # ensure that the parser in ParseEmptyArg has the correct position, both -# before and after the call to Var_ParsePP. +# before and after the call to Var_Parse. .if empty(W${:UOR}D) . error .endif # There may be spaces at the outside of the parentheses. # Spaces inside the parentheses are interpreted as part of the variable name. .if ! empty ( WORD ) . error .endif ${:U WORD }= variable name with spaces # Now there is a variable named " WORD ", and it is not empty. .if empty ( WORD ) +. error +.endif + +# Parse error: missing closing parenthesis. +.if empty(WORD +. error +.else . error .endif all: @:; Index: head/contrib/bmake/unit-tests/cond-func.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-func.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-func.exp (revision 367863) @@ -1,9 +1,15 @@ -make: "cond-func.mk" line 29: warning: Missing closing parenthesis for defined() -make: "cond-func.mk" line 29: Malformed conditional (!defined(A B)) -make: "cond-func.mk" line 44: warning: Missing closing parenthesis for defined() -make: "cond-func.mk" line 44: Malformed conditional (!defined(A&B)) -make: "cond-func.mk" line 47: warning: Missing closing parenthesis for defined() -make: "cond-func.mk" line 47: Malformed conditional (!defined(A|B)) +make: "cond-func.mk" line 36: warning: Missing closing parenthesis for defined() +make: "cond-func.mk" line 36: Malformed conditional (!defined(A B)) +make: "cond-func.mk" line 51: warning: Missing closing parenthesis for defined() +make: "cond-func.mk" line 51: Malformed conditional (!defined(A&B)) +make: "cond-func.mk" line 54: warning: Missing closing parenthesis for defined() +make: "cond-func.mk" line 54: Malformed conditional (!defined(A|B)) +make: "cond-func.mk" line 94: The empty variable is never defined. +make: "cond-func.mk" line 102: A plain function name is parsed as !empty(...). +make: "cond-func.mk" line 109: A plain function name is parsed as !empty(...). +make: "cond-func.mk" line 119: Symbols may start with a function name. +make: "cond-func.mk" line 124: Symbols may start with a function name. +make: "cond-func.mk" line 130: Malformed conditional (defined() make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/cond-func.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-func.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-func.mk (revision 367863) @@ -1,71 +1,137 @@ -# $NetBSD: cond-func.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-func.mk,v 1.9 2020/11/15 14:07:53 rillig Exp $ # # Tests for those parts of the functions in .if conditions that are common # among several functions. # # The below test uses the function defined(...) since it has no side-effects, -# the other functions (except empty(...)) would work equally well. +# the other functions (except empty(...)) would work equally well. The +# function empty is special because it uses a different parsing algorithm for +# its argument. DEF= defined ${:UA B}= variable name with spaces ${:UVAR(value)}= variable name with parentheses -${:UVAR{value}}= variable name with braces +${:UVAR{value}}= variable name with balanced braces +# Really strange variable names must be given indirectly via another variable, +# so that no unbalanced braces appear in the top-level expression. +VARNAME_UNBALANCED_BRACES= VAR{{{value +${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces + .if !defined(DEF) . error .endif # Horizontal whitespace (space tab) after the opening parenthesis is ignored. .if !defined( DEF) . error .endif # Horizontal whitespace (space tab) before the closing parenthesis is ignored. .if !defined(DEF ) . error .endif # The argument of a function must not directly contain whitespace. .if !defined(A B) . error .endif # If necessary, the whitespace can be generated by a variable expression. .if !defined(${:UA B}) . error .endif # Characters that could be mistaken for operators must not appear directly # in a function argument. As with whitespace, these can be generated # indirectly. # # It's not entirely clear why these characters are forbidden. # The most plausible reason seems to be typo detection. .if !defined(A&B) . error .endif .if !defined(A|B) . error .endif # Even parentheses may appear in variable names. # They must be balanced though. .if !defined(VAR(value)) . error .endif # Braces do not have any special meaning when parsing arguments. .if !defined(VAR{value}) . error .endif +# Braces do not have any special meaning when parsing arguments. +# They don't need to be balanced. +.if !defined(VAR{{{value) +. error +.endif + # There may be spaces around the operators and parentheses, and even # inside the parentheses. The spaces inside the parentheses are not # allowed for the empty() function (see cond-func-empty.mk), therefore # they are typically omitted for the other functions as well. .if ! defined ( DEF ) +. error +.endif + +# The following condition is interpreted as defined(A) && defined(B). +# In lack of a function call expression, each kind of .if directive has a +# default function that is called when a bare word is parsed. For the plain +# .if directive, this function is defined(); see "struct If ifs" in cond.c. +.if A&B +. error +.endif + +.if defined() +. error +.else +. info The empty variable is never defined. +.endif + +# The plain word 'defined' is interpreted as '!empty(defined)'. +# That variable is not defined (yet). +.if defined +. error +.else +. info A plain function name is parsed as !empty(...). +.endif + +# If a variable named 'defined' is actually defined and not empty, the plain +# symbol 'defined' evaluates to true. +defined= non-empty +.if defined +. info A plain function name is parsed as !empty(...). +.else +. error +.endif + +# A plain symbol name may start with one of the function names, in this case +# 'defined'. +.if defined-var +. error +.else +. info Symbols may start with a function name. +.endif + +defined-var= non-empty +.if defined-var +. info Symbols may start with a function name. +.else +. error +.endif + +# Missing closing parenthesis when parsing the function argument. +.if defined( +. error +.else . error .endif all: @:; Index: head/contrib/bmake/unit-tests/cond-late.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-late.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-late.mk (revision 367863) @@ -1,29 +1,31 @@ -# $NetBSD: cond-late.mk,v 1.2 2020/07/25 20:37:46 rillig Exp $ +# $NetBSD: cond-late.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ # # Using the :? modifier, variable expressions can contain conditional -# expressions that are evaluated late. Any variables appearing in these +# expressions that are evaluated late, at expansion time. +# +# Any variables appearing in these # conditions are expanded before parsing the condition. This is # different from many other places. # # Because of this, variables that are used in these lazy conditions # should not contain double-quotes, or the parser will probably fail. # # They should also not contain operators like == or <, since these are # actually interpreted as these operators. This is demonstrated below. # -# If the order of evaluation were to change to first parse the condition -# and then expand the variables, the output would change from the -# current "yes no" to "yes yes", since both variables are non-empty. all: cond-literal COND.true= "yes" == "yes" COND.false= "yes" != "yes" +# If the order of evaluation were to change to first parse the condition +# and then expand the variables, the output would change from the +# current "yes no" to "yes yes", since both variables are non-empty. cond-literal: @echo ${ ${COND.true} :?yes:no} @echo ${ ${COND.false} :?yes:no} VAR+= ${${UNDEF} != "no":?:} .if empty(VAR:Mpattern) .endif Index: head/contrib/bmake/unit-tests/cond-op-and-lint.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-op-and-lint.exp (nonexistent) +++ head/contrib/bmake/unit-tests/cond-op-and-lint.exp (revision 367863) @@ -0,0 +1,4 @@ +make: "cond-op-and-lint.mk" line 9: Unknown operator '&' +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/cond-op-and-lint.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-op-and-lint.mk (nonexistent) +++ head/contrib/bmake/unit-tests/cond-op-and-lint.mk (revision 367863) @@ -0,0 +1,13 @@ +# $NetBSD: cond-op-and-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $ +# +# Tests for the && operator in .if conditions, in lint mode. + +.MAKEFLAGS: -dL + +# The '&' operator is not allowed in lint mode. +# It is not used in practice anyway. +.if 0 & 0 +. error +.else +. error +.endif Property changes on: head/contrib/bmake/unit-tests/cond-op-and-lint.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/cond-op-not.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-op-not.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-op-not.exp (revision 367863) @@ -1 +1,6 @@ +make: "cond-op-not.mk" line 29: Not empty evaluates to true. +make: "cond-op-not.mk" line 37: Not space evaluates to false. +make: "cond-op-not.mk" line 41: Not 0 evaluates to true. +make: "cond-op-not.mk" line 49: Not 1 evaluates to false. +make: "cond-op-not.mk" line 55: Not word evaluates to false. exit status 0 Index: head/contrib/bmake/unit-tests/cond-op-not.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-op-not.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-op-not.mk (revision 367863) @@ -1,22 +1,59 @@ -# $NetBSD: cond-op-not.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-op-not.mk,v 1.6 2020/11/15 14:58:14 rillig Exp $ # -# Tests for the ! operator in .if conditions. +# Tests for the ! operator in .if conditions, which negates its argument. # The exclamation mark negates its operand. .if !1 . error .endif # Exclamation marks can be chained. # This doesn't happen in practice though. .if !!!1 . error .endif # The ! binds more tightly than the &&. .if !!0 && 1 . error +.endif + +# The operator '==' binds more tightly than '!'. +# This is unusual since most other programming languages define the precedence +# to be the other way round. +.if !${:Uexpression} == "expression" +. error +.endif + +.if !${:U} +. info Not empty evaluates to true. +.else +. info Not empty evaluates to false. +.endif + +.if !${:U } +. info Not space evaluates to true. +.else +. info Not space evaluates to false. +.endif + +.if !${:U0} +. info Not 0 evaluates to true. +.else +. info Not 0 evaluates to false. +.endif + +.if !${:U1} +. info Not 1 evaluates to true. +.else +. info Not 1 evaluates to false. +.endif + +.if !${:Uword} +. info Not word evaluates to true. +.else +. info Not word evaluates to false. .endif all: @:; Index: head/contrib/bmake/unit-tests/cond-op-or-lint.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-op-or-lint.exp (nonexistent) +++ head/contrib/bmake/unit-tests/cond-op-or-lint.exp (revision 367863) @@ -0,0 +1,4 @@ +make: "cond-op-or-lint.mk" line 9: Unknown operator '|' +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/cond-op-or-lint.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-op-or-lint.mk (nonexistent) +++ head/contrib/bmake/unit-tests/cond-op-or-lint.mk (revision 367863) @@ -0,0 +1,13 @@ +# $NetBSD: cond-op-or-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $ +# +# Tests for the || operator in .if conditions, in lint mode. + +.MAKEFLAGS: -dL + +# The '|' operator is not allowed in lint mode. +# It is not used in practice anyway. +.if 0 | 0 +. error +.else +. error +.endif Property changes on: head/contrib/bmake/unit-tests/cond-op-or-lint.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/cond-op-parentheses.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-op-parentheses.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-op-parentheses.exp (revision 367863) @@ -1 +1,2 @@ +make: "cond-op-parentheses.mk" line 13: Parentheses can be nested at least to depth 112. exit status 0 Index: head/contrib/bmake/unit-tests/cond-op-parentheses.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-op-parentheses.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-op-parentheses.mk (revision 367863) @@ -1,8 +1,19 @@ -# $NetBSD: cond-op-parentheses.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: cond-op-parentheses.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $ # # Tests for parentheses in .if conditions. # TODO: Implementation + +# Test for deeply nested conditions. +.if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \ + (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \ + 1 \ + )))))))))))))))))))))))))))))))))))))))))))))))))))))))) \ + )))))))))))))))))))))))))))))))))))))))))))))))))))))))) +. info Parentheses can be nested at least to depth 112. +.else +. error +.endif all: @:; Index: head/contrib/bmake/unit-tests/cond-op.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-op.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-op.exp (revision 367863) @@ -1,7 +1,16 @@ -make: "cond-op.mk" line 45: Malformed conditional ("!word" == !word) -make: "cond-op.mk" line 70: Malformed conditional (0 ${ERR::=evaluated}) -make: "cond-op.mk" line 74: warning: After detecting a parse error, the rest is evaluated. -make: "cond-op.mk" line 78: Parsing continues until here. +make: "cond-op.mk" line 50: Malformed conditional ("!word" == !word) +make: "cond-op.mk" line 75: Malformed conditional (0 ${ERR::=evaluated}) +make: "cond-op.mk" line 79: After detecting a parse error, the rest is evaluated. +make: "cond-op.mk" line 83: Parsing continues until here. +make: "cond-op.mk" line 86: A B C => (A || B) && C A || B && C A || (B && C) +make: "cond-op.mk" line 93: 0 0 0 => 0 0 0 +make: "cond-op.mk" line 93: 0 0 1 => 0 0 0 +make: "cond-op.mk" line 93: 0 1 0 => 0 0 0 +make: "cond-op.mk" line 93: 0 1 1 => 1 1 1 +make: "cond-op.mk" line 93: 1 0 0 => 0 1 1 +make: "cond-op.mk" line 93: 1 0 1 => 1 1 1 +make: "cond-op.mk" line 93: 1 1 0 => 0 1 1 +make: "cond-op.mk" line 93: 1 1 1 => 1 1 1 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/cond-op.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-op.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-op.mk (revision 367863) @@ -1,81 +1,102 @@ -# $NetBSD: cond-op.mk,v 1.8 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-op.mk,v 1.10 2020/11/15 14:58:14 rillig Exp $ # # Tests for operators like &&, ||, ! in .if conditions. # # See also: # cond-op-and.mk # cond-op-not.mk # cond-op-or.mk # cond-op-parentheses.mk # In make, && binds more tightly than ||, like in C. -# If make had the same precedence for both && and ||, the result would be -# different. +# If make had the same precedence for both && and ||, like in the shell, +# the result would be different. # If || were to bind more tightly than &&, the result would be different # as well. .if !(1 || 1 && 0) . error .endif # If make were to interpret the && and || operators like the shell, the -# implicit binding would be this: +# previous condition would be interpreted as: .if (1 || 1) && 0 . error .endif # The precedence of the ! operator is different from C though. It has a -# lower precedence than the comparison operators. +# lower precedence than the comparison operators. Negating a condition +# does not need parentheses. +# +# This kind of condition looks so unfamiliar that it doesn't occur in +# practice. .if !"word" == "word" . error .endif # This is how the above condition is actually interpreted. .if !("word" == "word") . error .endif # TODO: Demonstrate that the precedence of the ! and == operators actually # makes a difference. There is a simple example for sure, I just cannot -# wrap my head around it. +# wrap my head around it right now. See the truth table generator below +# for an example that doesn't require much thought. # This condition is malformed because the '!' on the right-hand side must not # appear unquoted. If any, it must be enclosed in quotes. # In any case, it is not interpreted as a negation of an unquoted string. # See CondParser_String. .if "!word" == !word . error .endif # Surprisingly, the ampersand and pipe are allowed in bare strings. # That's another opportunity for writing confusing code. # See CondParser_String, which only has '!' in the list of stop characters. .if "a&&b||c" != a&&b||c . error .endif # As soon as the parser sees the '$', it knows that the condition will # be malformed. Therefore there is no point in evaluating it. # # As of 2020-09-11, that part of the condition is evaluated nevertheless, # since CondParser_Expr just requests the next token, without restricting # the token to the expected tokens. If the parser were to restrict the # valid follow tokens for the token "0" to those that can actually produce # a correct condition (which in this case would be comparison operators, # TOK_AND, TOK_OR or TOK_RPAREN), the variable expression would not have # to be evaluated. # # This would add a good deal of complexity to the code though, for almost # no benefit, especially since most expressions and conditions are side # effect free. .if 0 ${ERR::=evaluated} . error .endif .if ${ERR:Uundefined} == evaluated -. warning After detecting a parse error, the rest is evaluated. +. info After detecting a parse error, the rest is evaluated. .endif # Just in case that parsing should ever stop on the first error. .info Parsing continues until here. + +# Demonstration that '&&' has higher precedence than '||'. +.info A B C => (A || B) && C A || B && C A || (B && C) +.for a in 0 1 +. for b in 0 1 +. for c in 0 1 +. for r1 in ${ ($a || $b) && $c :?1:0} +. for r2 in ${ $a || $b && $c :?1:0} +. for r3 in ${ $a || ($b && $c) :?1:0} +. info $a $b $c => ${r1} ${r2} ${r3} +. endfor +. endfor +. endfor +. endfor +. endfor +.endfor all: @:; Index: head/contrib/bmake/unit-tests/cond-short.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-short.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-short.mk (revision 367863) @@ -1,171 +1,188 @@ -# $NetBSD: cond-short.mk,v 1.11 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: cond-short.mk,v 1.12 2020/11/15 14:58:14 rillig Exp $ # # Demonstrates that in conditions, the right-hand side of an && or || # is only evaluated if it can actually influence the result. +# This is called 'short-circuit evaluation' and is the usual evaluation +# mode in most programming languages. A notable exception is Ada, which +# distinguishes between the operators 'And', 'And Then', 'Or', 'Or Else'. # # Between 2015-10-11 and 2020-06-28, the right-hand side of an && or || # operator was always evaluated, which was wrong. -# +# TODO: Had the evaluation been correct at some time before 2015-11-12? # The && operator. .if 0 && ${echo "unexpected and" 1>&2 :L:sh} .endif .if 1 && ${echo "expected and" 1>&2 :L:sh} .endif .if 0 && exists(nonexistent${echo "unexpected and exists" 1>&2 :L:sh}) .endif .if 1 && exists(nonexistent${echo "expected and exists" 1>&2 :L:sh}) .endif .if 0 && empty(${echo "unexpected and empty" 1>&2 :L:sh}) .endif .if 1 && empty(${echo "expected and empty" 1>&2 :L:sh}) .endif # "VAR U11" is not evaluated; it was evaluated before 2020-07-02. # The whole !empty condition is only parsed and then discarded. VAR= ${VAR${:U11${echo "unexpected VAR U11" 1>&2 :L:sh}}} VAR13= ${VAR${:U12${echo "unexpected VAR13" 1>&2 :L:sh}}} .if 0 && !empty(VAR${:U13${echo "unexpected U13 condition" 1>&2 :L:sh}}) .endif VAR= ${VAR${:U21${echo "unexpected VAR U21" 1>&2 :L:sh}}} VAR23= ${VAR${:U22${echo "expected VAR23" 1>&2 :L:sh}}} .if 1 && !empty(VAR${:U23${echo "expected U23 condition" 1>&2 :L:sh}}) .endif VAR= # empty again, for the following tests # The :M modifier is only parsed, not evaluated. # Before 2020-07-02, it was wrongly evaluated. .if 0 && !empty(VAR:M${:U${echo "unexpected M pattern" 1>&2 :L:sh}}) .endif .if 1 && !empty(VAR:M${:U${echo "expected M pattern" 1>&2 :L:sh}}) .endif .if 0 && !empty(VAR:S,from,${:U${echo "unexpected S modifier" 1>&2 :L:sh}},) .endif .if 0 && !empty(VAR:C,from,${:U${echo "unexpected C modifier" 1>&2 :L:sh}},) .endif .if 0 && !empty("" == "" :? ${:U${echo "unexpected ? modifier" 1>&2 :L:sh}} :) .endif .if 0 && !empty(VAR:old=${:U${echo "unexpected = modifier" 1>&2 :L:sh}}) .endif .if 0 && !empty(1 2 3:L:@var@${:U${echo "unexpected @ modifier" 1>&2 :L:sh}}@) .endif .if 0 && !empty(:U${:!echo "unexpected exclam modifier" 1>&2 !}) .endif # Irrelevant assignment modifiers are skipped as well. .if 0 && ${1 2 3:L:@i@${FIRST::?=$i}@} .endif .if 0 && ${1 2 3:L:@i@${LAST::=$i}@} .endif .if 0 && ${1 2 3:L:@i@${APPENDED::+=$i}@} .endif .if 0 && ${echo.1 echo.2 echo.3:L:@i@${RAN::!=${i:C,.*,&; & 1>\&2,:S,., ,g}}@} .endif .if defined(FIRST) || defined(LAST) || defined(APPENDED) || defined(RAN) . warning first=${FIRST} last=${LAST} appended=${APPENDED} ran=${RAN} .endif # The || operator. .if 1 || ${echo "unexpected or" 1>&2 :L:sh} .endif .if 0 || ${echo "expected or" 1>&2 :L:sh} .endif .if 1 || exists(nonexistent${echo "unexpected or exists" 1>&2 :L:sh}) .endif .if 0 || exists(nonexistent${echo "expected or exists" 1>&2 :L:sh}) .endif .if 1 || empty(${echo "unexpected or empty" 1>&2 :L:sh}) .endif .if 0 || empty(${echo "expected or empty" 1>&2 :L:sh}) .endif # Unreachable nested conditions are skipped completely as well. .if 0 . if ${echo "unexpected nested and" 1>&2 :L:sh} . endif .endif .if 1 .elif ${echo "unexpected nested or" 1>&2 :L:sh} .endif # make sure these do not cause complaint #.MAKEFLAGS: -dc +# TODO: Rewrite this whole section and check all the conditions and variables. +# Several of the assumptions are probably wrong here. +# TODO: replace 'x=' with '.info' or '.error'. V42= 42 iV1= ${V42} iV2= ${V66} .if defined(V42) && ${V42} > 0 x= Ok .else x= Fail .endif x!= echo 'defined(V42) && ${V42} > 0: $x' >&2; echo # this one throws both String comparison operator and # Malformed conditional with cond.c 1.78 # indirect iV2 would expand to "" and treated as 0 .if defined(V66) && ( ${iV2} < ${V42} ) x= Fail .else x= Ok .endif x!= echo 'defined(V66) && ( "${iV2}" < ${V42} ): $x' >&2; echo # next two thow String comparison operator with cond.c 1.78 # indirect iV1 would expand to 42 .if 1 || ${iV1} < ${V42} x= Ok .else x= Fail .endif x!= echo '1 || ${iV1} < ${V42}: $x' >&2; echo .if 1 || ${iV2:U2} < ${V42} x= Ok .else x= Fail .endif x!= echo '1 || ${iV2:U2} < ${V42}: $x' >&2; echo # the same expressions are fine when the lhs is expanded # ${iV1} expands to 42 .if 0 || ${iV1} <= ${V42} x= Ok .else x= Fail .endif x!= echo '0 || ${iV1} <= ${V42}: $x' >&2; echo # ${iV2:U2} expands to 2 .if 0 || ${iV2:U2} < ${V42} x= Ok .else x= Fail .endif x!= echo '0 || ${iV2:U2} < ${V42}: $x' >&2; echo + +# TODO: Has this always worked? There may have been a time, maybe around +# 2000, when make would complain about the "Malformed conditional" because +# UNDEF is not defined. +.if defined(UNDEF) && ${UNDEF} != "undefined" +. error +.endif + +# TODO: Test each modifier to make sure it is skipped when it is irrelevant +# for the result. Since this test is already quite long, do that in another +# test. all: @:;: Index: head/contrib/bmake/unit-tests/cond-token-number.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-token-number.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-token-number.exp (revision 367863) @@ -1,8 +1,8 @@ -make: "cond-token-number.mk" line 13: Malformed conditional (-0) -make: "cond-token-number.mk" line 21: Malformed conditional (+0) -make: "cond-token-number.mk" line 29: Malformed conditional (!-1) -make: "cond-token-number.mk" line 37: Malformed conditional (!+1) -make: "cond-token-number.mk" line 54: End of the tests. +make: "cond-token-number.mk" line 15: Malformed conditional (-0) +make: "cond-token-number.mk" line 25: Malformed conditional (+0) +make: "cond-token-number.mk" line 35: Malformed conditional (!-1) +make: "cond-token-number.mk" line 45: Malformed conditional (!+1) +make: "cond-token-number.mk" line 80: End of the tests. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/cond-token-number.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-token-number.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-token-number.mk (revision 367863) @@ -1,56 +1,82 @@ -# $NetBSD: cond-token-number.mk,v 1.3 2020/09/14 06:22:59 rillig Exp $ +# $NetBSD: cond-token-number.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $ # # Tests for number tokens in .if conditions. +# +# TODO: Add introduction. .if 0 . error .endif # Even though -0 is a number and would be accepted by strtod, it is not # accepted by the condition parser. # # See the ch_isdigit call in CondParser_String. .if -0 . error +.else +. error .endif # Even though +0 is a number and would be accepted by strtod, it is not # accepted by the condition parser. # # See the ch_isdigit call in CondParser_String. .if +0 . error +.else +. error .endif # Even though -1 is a number and would be accepted by strtod, it is not # accepted by the condition parser. # # See the ch_isdigit call in CondParser_String. .if !-1 . error +.else +. error .endif # Even though +1 is a number and would be accepted by strtod, it is not # accepted by the condition parser. # # See the ch_isdigit call in CondParser_String. .if !+1 . error +.else +. error .endif # When the number comes from a variable expression though, it may be signed. # XXX: This is inconsistent. .if ${:U+0} . error .endif # When the number comes from a variable expression though, it may be signed. # XXX: This is inconsistent. .if !${:U+1} +. error +.endif + +# Hexadecimal numbers are accepted. +.if 0x0 +. error +.endif +.if 0x1 +.else +. error +.endif + +# This is not a hexadecimal number, even though it has an x. +# It is interpreted as a string instead, effectively meaning defined(3x4). +.if 3x4 +.else . error .endif # Ensure that parsing continues until here. .info End of the tests. all: # nothing Index: head/contrib/bmake/unit-tests/cond-token-plain.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-token-plain.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-token-plain.mk (revision 367863) @@ -1,94 +1,97 @@ -# $NetBSD: cond-token-plain.mk,v 1.4 2020/09/12 17:47:24 rillig Exp $ +# $NetBSD: cond-token-plain.mk,v 1.6 2020/11/15 14:58:14 rillig Exp $ # # Tests for plain tokens (that is, string literals without quotes) # in .if conditions. .MAKEFLAGS: -dc .if ${:Uvalue} != value . error .endif # Malformed condition since comment parsing is done in an early phase # and removes the '#' and everything behind it long before the condition # parser gets to see it. # # XXX: The error message is missing for this malformed condition. -# The right-hand side of the comparison is just a '"'. +# The right-hand side of the comparison is just a '"', before unescaping. .if ${:U} != "#hash" . error .endif # To get a '#' into a condition, it has to be escaped using a backslash. # This prevents the comment parser from removing it, and in turn, it becomes # visible to CondParser_String. .if ${:U\#hash} != "\#hash" . error .endif # Since 2002-12-30, and still as of 2020-09-11, CondParser_Token handles # the '#' specially, even though at this point, there should be no need for # comment handling anymore. The comments are supposed to be stripped off # in a very early parsing phase. # +# See https://gnats.netbsd.org/19596 for example makefiles demonstrating the +# original problems. This workaround is probably not needed anymore. +# # XXX: Missing error message for the malformed condition. The right-hand -# side is double-quotes, backslash, backslash. -# XXX: It is unexpected that the right-hand side evaluates to a single -# backslash. +# side before unescaping is double-quotes, backslash, backslash. .if ${:U\\} != "\\#hash" . error .endif # The right-hand side of a comparison is not parsed as a token, therefore # the code from CondParser_Token does not apply to it. +# TODO: Explain the consequences. +# TODO: Does this mean that more syntactic variants are allowed here? .if ${:U\#hash} != \#hash . error .endif # XXX: What is the purpose of treating an escaped '#' in the following # condition as a comment? And why only at the beginning of a token, # just as in the shell? .if 0 \# This is treated as a comment, but why? . error .endif # Ah, ok, this can be used to add an end-of-condition comment. But does # anybody really use this? This is neither documented nor obvious since # the '#' is escaped. It's much clearer to write a comment in the line # above the condition. .if ${0 \# comment :?yes:no} != no . error .endif .if ${1 \# comment :?yes:no} != yes . error .endif # Usually there is whitespace around the comparison operator, but this is # not required. .if ${UNDEF:Uundefined}!=undefined . error .endif .if ${UNDEF:U12345}>12345 . error .endif .if ${UNDEF:U12345}<12345 . error .endif .if (${UNDEF:U0})||0 . error .endif # Only the comparison operator terminates the comparison operand, and it's # a coincidence that the '!' is both used in the '!=' comparison operator # as well as for negating a comparison result. # # The boolean operators '&' and '|' don't terminate a comparison operand. .if ${:Uvar}&&name != "var&&name" . error .endif .if ${:Uvar}||name != "var||name" . error .endif all: @:; Index: head/contrib/bmake/unit-tests/cond-token-string.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-token-string.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-token-string.exp (revision 367863) @@ -1 +1,8 @@ -exit status 0 +make: Unknown modifier 'Z' +make: "cond-token-string.mk" line 9: Malformed conditional ("" != "${:Uvalue:Z}") +make: "cond-token-string.mk" line 18: xvalue is not defined. +make: "cond-token-string.mk" line 24: Malformed conditional (x${:Uvalue} == "") +make: "cond-token-string.mk" line 33: Expected. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/cond-token-string.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-token-string.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-token-string.mk (revision 367863) @@ -1,8 +1,39 @@ -# $NetBSD: cond-token-string.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: cond-token-string.mk,v 1.3 2020/11/10 22:23:37 rillig Exp $ # # Tests for quoted and unquoted string literals in .if conditions. # TODO: Implementation + +# Cover the code in CondParser_String that frees the memory after parsing +# a variable expression based on an undefined variable. +.if "" != "${:Uvalue:Z}" +. error +.else +. error +.endif + +.if x${:Uvalue} +. error +.else +. info xvalue is not defined. +.endif + +# The 'x' produces a "Malformed conditional" since the left-hand side of a +# comparison in an .if directive must be either a variable expression, a +# quoted string literal or a number that starts with a digit. +.if x${:Uvalue} == "" +. error +.else +. error +.endif + +# In plain words, a '\' can be used to escape any character, just as in +# double-quoted string literals. See CondParser_String. +.if \x${:Uvalue} == "xvalue" +. info Expected. +.else +. error +.endif all: @:; Index: head/contrib/bmake/unit-tests/cond-token-var.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-token-var.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-token-var.exp (revision 367863) @@ -1,7 +1,7 @@ -make: "cond-token-var.mk" line 9: ok -make: "cond-token-var.mk" line 15: Malformed conditional (${UNDEF} == ${DEF}) -make: "cond-token-var.mk" line 20: Malformed conditional (${DEF} == ${UNDEF}) -make: "cond-token-var.mk" line 29: Malformed conditional (${UNDEF}) +make: "cond-token-var.mk" line 20: ok +make: "cond-token-var.mk" line 27: Malformed conditional (${UNDEF} == ${DEF}) +make: "cond-token-var.mk" line 33: Malformed conditional (${DEF} == ${UNDEF}) +make: "cond-token-var.mk" line 42: Malformed conditional (${UNDEF}) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/cond-token-var.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-token-var.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-token-var.mk (revision 367863) @@ -1,34 +1,48 @@ -# $NetBSD: cond-token-var.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-token-var.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $ # -# Tests for variables in .if conditions. +# Tests for variable expressions in .if conditions. +# +# Note the fine distinction between a variable and a variable expression. +# A variable has a name and a value. To access the value, one writes a +# variable expression of the form ${VAR}. This is a simple variable +# expression. Variable expressions can get more complicated by adding +# variable modifiers such as in ${VAR:Mpattern}. +# +# XXX: Strictly speaking, variable modifiers should be called expression +# modifiers instead since they only modify the expression, not the variable. +# Well, except for the assignment modifiers, these do indeed change the value +# of the variable. DEF= defined # A defined variable may appear on either side of the comparison. .if ${DEF} == ${DEF} . info ok .else . error .endif # A variable that appears on the left-hand side must be defined. +# The following line thus generates a parse error. .if ${UNDEF} == ${DEF} . error .endif # A variable that appears on the right-hand side must be defined. +# The following line thus generates a parse error. .if ${DEF} == ${UNDEF} . error .endif # A defined variable may appear as an expression of its own. .if ${DEF} .endif -# An undefined variable generates a warning. +# An undefined variable on its own generates a parse error. .if ${UNDEF} .endif -# The :U modifier turns an undefined variable into an ordinary expression. +# The :U modifier turns an undefined expression into a defined expression. +# Since the expression is defined now, it doesn't generate any parse error. .if ${UNDEF:U} .endif Index: head/contrib/bmake/unit-tests/cond-undef-lint.exp =================================================================== --- head/contrib/bmake/unit-tests/cond-undef-lint.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond-undef-lint.exp (revision 367863) @@ -1,7 +1,7 @@ make: "cond-undef-lint.mk" line 23: Variable "UNDEF" is undefined make: "cond-undef-lint.mk" line 38: Variable "UNDEF" is undefined make: "cond-undef-lint.mk" line 38: Variable "VAR." is undefined -make: "cond-undef-lint.mk" line 45: Variable "VAR.defined" is undefined +make: "cond-undef-lint.mk" line 49: Variable "VAR.defined" is undefined make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/cond-undef-lint.mk =================================================================== --- head/contrib/bmake/unit-tests/cond-undef-lint.mk (revision 367862) +++ head/contrib/bmake/unit-tests/cond-undef-lint.mk (revision 367863) @@ -1,69 +1,73 @@ -# $NetBSD: cond-undef-lint.mk,v 1.2 2020/09/14 07:13:29 rillig Exp $ +# $NetBSD: cond-undef-lint.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $ # # Tests for defined and undefined variables in .if conditions, in lint mode. # # As of 2020-09-14, lint mode contains experimental code for printing # accurate error messages in case of undefined variables, instead of the # wrong "Malformed condition". # # See also: # opt-debug-lint.mk .MAKEFLAGS: -dL # DEF is defined, UNDEF is not. DEF= defined # An expression based on a defined variable is fine. .if !${DEF} . error .endif # Since the condition fails to evaluate, neither of the branches is taken. .if ${UNDEF} . error .else . error .endif # The variable name depends on the undefined variable, which is probably a # mistake. The variable UNDEF, as used here, can be easily turned into # an expression that is always defined, using the :U modifier. # # The outer expression does not generate an error message since there was # already an error evaluating this variable's name. # # TODO: Suppress the error message "Variable VAR. is undefined". That part # of the expression must not be evaluated at all. .if ${VAR.${UNDEF}} . error .else . error .endif # The variable VAR.defined is not defined and thus generates an error message. +# +# TODO: This pattern looks a lot like CFLAGS.${OPSYS}, which is at least +# debatable. Or would any practical use of CFLAGS.${OPSYS} be via an indirect +# expression, as in the next example? .if ${VAR.${DEF}} . error .else . error .endif # Variables that are referenced indirectly may be undefined in a condition. # # A practical example for this is CFLAGS, which consists of CWARNS, COPTS # and a few others. Just because these nested variables are not defined, # this does not make the condition invalid. # # The crucial point is that at the point where the variable appears in the # condition, there is no way to influence the definedness of the nested # variables. In particular, there is no modifier that would turn undefined # nested variables into empty strings, as an equivalent to the :U modifier. INDIRECT= ${NESTED_UNDEF} ${NESTED_DEF} NESTED_DEF= nested-defined # Since NESTED_UNDEF is not controllable at this point, it must not generate # an error message, and it doesn't do so, since 2020-09-14. .if !${INDIRECT} . error .endif Index: head/contrib/bmake/unit-tests/cond1.exp =================================================================== --- head/contrib/bmake/unit-tests/cond1.exp (revision 367862) +++ head/contrib/bmake/unit-tests/cond1.exp (revision 367863) @@ -1,23 +1,23 @@ -make: "cond1.mk" line 75: warning: extra else -make: "cond1.mk" line 85: warning: extra else +make: "cond1.mk" line 80: warning: extra else +make: "cond1.mk" line 90: 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 must 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 (revision 367862) +++ head/contrib/bmake/unit-tests/cond1.mk (revision 367863) @@ -1,109 +1,114 @@ -# $NetBSD: cond1.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ +# $NetBSD: cond1.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $ + +# TODO: Convert these tests into tutorial form. +# TODO: Split these tests by topic. +# TODO: Use better variable names and expression values that actually express +# the intended behavior. uname(1) has nothing to do with conditions. # 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/dep-double-colon.mk =================================================================== --- head/contrib/bmake/unit-tests/dep-double-colon.mk (revision 367862) +++ head/contrib/bmake/unit-tests/dep-double-colon.mk (revision 367863) @@ -1,20 +1,23 @@ -# $NetBSD: dep-double-colon.mk,v 1.4 2020/09/26 15:41:53 rillig Exp $ +# $NetBSD: dep-double-colon.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the :: operator in dependency declarations. +# Tests for the '::' operator in dependency declarations, which allows +# several dependency groups for a single node, each having its own attributes +# and dependencies. In the code, the additional dependency groups are called +# cohorts. all:: @echo 'command 1a' @echo 'command 1b' all:: @echo 'command 2a' @echo 'command 2b' # When there are multiple command groups for a '::' target, each of these # groups is added separately to the .ALLTARGETS variable. # # XXX: What is this good for? # XXX: Where does the leading space come from? .if ${.ALLTARGETS} != " all all" . error .endif Index: head/contrib/bmake/unit-tests/dep-exclam.mk =================================================================== --- head/contrib/bmake/unit-tests/dep-exclam.mk (revision 367862) +++ head/contrib/bmake/unit-tests/dep-exclam.mk (revision 367863) @@ -1,8 +1,13 @@ -# $NetBSD: dep-exclam.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: dep-exclam.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the ! operator in dependency declarations. +# Tests for the ! operator in dependency declarations, which always re-creates +# the target, whether or not it is out of date. +# +# TODO: Is this related to OP_PHONY? +# TODO: Is this related to OP_EXEC? +# TODO: Is this related to OP_MAKE? # TODO: Implementation all: @:; Index: head/contrib/bmake/unit-tests/depsrc-ignore.mk =================================================================== --- head/contrib/bmake/unit-tests/depsrc-ignore.mk (revision 367862) +++ head/contrib/bmake/unit-tests/depsrc-ignore.mk (revision 367863) @@ -1,67 +1,66 @@ -# $NetBSD: depsrc-ignore.mk,v 1.4 2020/08/29 16:13:27 rillig Exp $ +# $NetBSD: depsrc-ignore.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # # Tests for the special source .IGNORE in dependency declarations, # which ignores any command failures for that target. # -# Even though ignore-errors fails, the all target is still made. -# Since the all target is not marked with .IGNORE, it stops at the -# first failing command. +# Even though 'ignore-errors' fails, 'all' is still made. Since 'all' is +# not marked with .IGNORE, it stops at the first failing command. # # XXX: The ordering of the messages in the output is confusing. # The "ignored" comes much too late to be related to the "false # ignore-errors". This is due to stdout being buffered. # # The "continuing" message comes from the -k option. If there had been # other targets independent of "all", these would be built as well. # # Enabling the debugging option -de changes the order in which the messages # appear. Now the "ignored" message is issued in the correct position. # The explanation for the output reordering is that the output is buffered. # As the manual page says, in debugging mode stdout is line buffered. # In these tests the output is redirected to a file, therefore stdout is # fully buffered. # # This is what actually happens, as of 2020-08-29. To verify it, set the # following breakpoints in CompatRunCommand: # -# * the "!silent" line, to see all commands. -# * the "fflush" line, to see stdout being flushed. +# * the "!silent" line, to see all commands +# * the "fflush" line, to see stdout being flushed # * the "status = WEXITSTATUS" line # * the "(continuing)" line # * the "(ignored)" line # # The breakpoints are visited in the following order: # # "ignore-errors begin" # Goes directly to STDOUT_FILENO since it is run in a child process. # "false ignore-errors" # Goes to the stdout buffer (CompatRunCommand, keyword "!silent") and # the immediate call to fflush(stdout) copies it to STDOUT_FILENO. # "*** Error code 1 (ignored)" # Goes to the stdout buffer but is not flushed (CompatRunCommand, near # the end). # "ignore-errors end" # Goes directly to STDOUT_FILENO. # "all begin" # Goes directly to STDOUT_FILENO. # "false all" # Goes to the stdout buffer, where the "*** Error code 1 (ignored)" is # still waiting to be flushed. These two lines are flushed now. # "*** Error code 1 (continuing)" # Goes to the stdout buffer. # "Stop." # Goes to the stdout buffer. # exit(1) # Flushes the stdout buffer to STDOUT_FILENO. all: ignore-errors ignore-errors: .IGNORE @echo $@ begin false $@ @echo $@ end all: @echo $@ begin false $@ @echo $@ end Index: head/contrib/bmake/unit-tests/depsrc-make.mk =================================================================== --- head/contrib/bmake/unit-tests/depsrc-make.mk (revision 367862) +++ head/contrib/bmake/unit-tests/depsrc-make.mk (revision 367863) @@ -1,16 +1,18 @@ -# $NetBSD: depsrc-make.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ +# $NetBSD: depsrc-make.mk,v 1.4 2020/11/15 20:20:58 rillig Exp $ # # Tests for the special source .MAKE in dependency declarations, which # executes the commands of the target even if the -n or -t command line # options are given. + +# TODO: Add a test for the -t command line option. .MAKEFLAGS: -n all: this-is-made all: this-is-not-made this-is-made: .MAKE @echo ${.TARGET} is made. this-is-not-made: @echo ${.TARGET} is just echoed. Index: head/contrib/bmake/unit-tests/depsrc-optional.exp =================================================================== --- head/contrib/bmake/unit-tests/depsrc-optional.exp (revision 367862) +++ head/contrib/bmake/unit-tests/depsrc-optional.exp (revision 367863) @@ -1,2 +1,20 @@ -`all' is up to date. +Make_ExpandUse: examine all +ExamineLater: need to examine "important" +Make_ExpandUse: examine important +ExamineLater: need to examine "optional" +ExamineLater: need to examine "optional-cohort" +Make_ExpandUse: examine optional +Make_ExpandUse: examine optional-cohort +Examining optional...non-existent...up-to-date. +Examining optional-cohort...non-existent...:: operator and no sources...out-of-date. +: A leaf node using '::' is considered out-of-date. + recheck(optional-cohort): update time from 0:00:00 Jan 01, 1970 to now +Examining important...non-existent...modified before source "optional-cohort"...out-of-date. +: important is made. + recheck(important): update time from 0:00:00 Jan 01, 1970 to now +Examining all...non-existent...modified before source "important"...out-of-date. +: all is made. + recheck(all): update time from 0:00:00 Jan 01, 1970 to now +Examining .END...non-existent...non-existent and no sources...out-of-date. + recheck(.END): update time from 0:00:00 Jan 01, 1970 to now exit status 0 Index: head/contrib/bmake/unit-tests/depsrc-optional.mk =================================================================== --- head/contrib/bmake/unit-tests/depsrc-optional.mk (revision 367862) +++ head/contrib/bmake/unit-tests/depsrc-optional.mk (revision 367863) @@ -1,18 +1,21 @@ -# $NetBSD: depsrc-optional.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ +# $NetBSD: depsrc-optional.mk,v 1.5 2020/11/08 10:33:47 rillig Exp $ # # Tests for the special source .OPTIONAL in dependency declarations, # which ignores the target if make cannot find out how to create it. # # TODO: Describe practical use cases for this feature. -# TODO: Explain why the commands for "important" are not executed. -# I had thought that only the "optional" commands were skipped. - all: important : ${.TARGET} is made. -important: optional +important: optional optional-cohort : ${.TARGET} is made. optional: .OPTIONAL - : This is not executed. + : An optional leaf node is not executed. + +# See IsOODateRegular. +optional-cohort:: .OPTIONAL + : A leaf node using '::' is considered out-of-date. + +.MAKEFLAGS: -dm Index: head/contrib/bmake/unit-tests/depsrc-precious.mk =================================================================== --- head/contrib/bmake/unit-tests/depsrc-precious.mk (revision 367862) +++ head/contrib/bmake/unit-tests/depsrc-precious.mk (revision 367863) @@ -1,8 +1,16 @@ -# $NetBSD: depsrc-precious.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-precious.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special source .PRECIOUS in dependency declarations. +# Tests for the special source .PRECIOUS in dependency declarations, which +# is only relevant if the commands for the target fail or are interrupted. +# In such a case, the target file is usually removed, to avoid having +# half-finished files with a timestamp suggesting the file were up-to-date. +# +# For targets marked with .PRECIOUS, the target file is not removed. +# The author of the makefile is then responsible for avoiding the above +# situation, in which the target would be wrongly considered up-to-date, +# just because its timestamp says so. # TODO: Implementation all: @:; Index: head/contrib/bmake/unit-tests/depsrc-usebefore.mk =================================================================== --- head/contrib/bmake/unit-tests/depsrc-usebefore.mk (revision 367862) +++ head/contrib/bmake/unit-tests/depsrc-usebefore.mk (revision 367863) @@ -1,24 +1,28 @@ -# $NetBSD: depsrc-usebefore.mk,v 1.5 2020/08/22 11:53:18 rillig Exp $ +# $NetBSD: depsrc-usebefore.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ # # Tests for the special source .USEBEFORE in dependency declarations, # which allows to prepend common commands to other targets. +# +# See also: +# .USE +# depsrc-use.mk all: action directly first: .USEBEFORE @echo first 1 # Using ${.TARGET} here would expand to "action" @echo first 2 # Using ${.TARGET} here would expand to "action" second: .USEBEFORE @echo second 1 @echo second 2 # It is possible but uncommon to have a .USEBEFORE target with no commands. # This may happen as the result of expanding a .for loop. empty: .USEBEFORE # It is possible but uncommon to directly make a .USEBEFORE target. directly: .USEBEFORE @echo directly action: second first empty Index: head/contrib/bmake/unit-tests/depsrc.mk =================================================================== --- head/contrib/bmake/unit-tests/depsrc.mk (revision 367862) +++ head/contrib/bmake/unit-tests/depsrc.mk (revision 367863) @@ -1,9 +1,11 @@ -# $NetBSD: depsrc.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # # Tests for special sources (those starting with a dot, followed by # uppercase letters) in dependency declarations, such as .PHONY. # TODO: Implementation + +# TODO: Test 'target: ${:U.SILENT}' all: @:; Index: head/contrib/bmake/unit-tests/deptgt-begin.exp =================================================================== --- head/contrib/bmake/unit-tests/deptgt-begin.exp (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-begin.exp (revision 367863) @@ -1,4 +1,7 @@ +make: "deptgt-begin.mk" line 17: warning: duplicate script for target ".BEGIN" ignored +make: "deptgt-begin.mk" line 8: warning: using previous script for ".BEGIN" defined here : parse time +: Making before-begin before .BEGIN. : .BEGIN : all exit status 0 Index: head/contrib/bmake/unit-tests/deptgt-begin.mk =================================================================== --- head/contrib/bmake/unit-tests/deptgt-begin.mk (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-begin.mk (revision 367863) @@ -1,13 +1,47 @@ -# $NetBSD: deptgt-begin.mk,v 1.3 2020/08/29 17:34:21 rillig Exp $ +# $NetBSD: deptgt-begin.mk,v 1.5 2020/11/15 22:28:08 rillig Exp $ # # Tests for the special target .BEGIN in dependency declarations, # which is a container for commands that are run before any other # commands from the shell lines. .BEGIN: : $@ + +# To register a custom action to be run at the beginning, the simplest way is +# to directly place some commands on the '.BEGIN' target. This doesn't scale +# though, since the ':' dependency operator prevents that any other place may +# add its commands after this. +# +# There are several ways to resolve this situation, which are detailed below. +.BEGIN: + : Making another $@. + +# One way to run commands at the beginning is to define a custom target and +# make the .BEGIN depend on that target. This way, the commands from the +# custom target are run even before the .BEGIN target. +.BEGIN: before-begin +before-begin: .PHONY .NOTMAIN + : Making $@ before .BEGIN. + +# Another way is to define a custom target and make that a .USE dependency. +# For the .BEGIN target, .USE dependencies do not work though, since in +# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the +# .BEGIN target has been run, which is too late. +.BEGIN: use +use: .USE .NOTMAIN + : Making $@ from a .USE dependency. + +# Same as with .USE, but run the commands before the main commands from the +# .BEGIN target. +# +# For the .BEGIN target, .USEBEFORE dependencies do not work though, since in +# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the +# .BEGIN target has been run, which is too late. +.BEGIN: use-before +use-before: .USEBEFORE .NOTMAIN + : Making $@ from a .USEBEFORE dependency. all: : $@ _!= echo : parse time 1>&2 Index: head/contrib/bmake/unit-tests/deptgt-error.mk =================================================================== --- head/contrib/bmake/unit-tests/deptgt-error.mk (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-error.mk (revision 367863) @@ -1,8 +1,9 @@ -# $NetBSD: deptgt-error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-error.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special target .ERROR in dependency declarations. +# Tests for the special target .ERROR in dependency declarations, which +# collects commands that are run when another target fails. # TODO: Implementation all: @:; Index: head/contrib/bmake/unit-tests/deptgt-ignore.mk =================================================================== --- head/contrib/bmake/unit-tests/deptgt-ignore.mk (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-ignore.mk (revision 367863) @@ -1,8 +1,9 @@ -# $NetBSD: deptgt-ignore.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-ignore.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special target .IGNORE in dependency declarations. +# Tests for the special target .IGNORE in dependency declarations, which +# does not stop if a command from this target exits with a non-zero status. # TODO: Implementation all: @:; Index: head/contrib/bmake/unit-tests/deptgt-interrupt.mk =================================================================== --- head/contrib/bmake/unit-tests/deptgt-interrupt.mk (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-interrupt.mk (revision 367863) @@ -1,8 +1,10 @@ -# $NetBSD: deptgt-interrupt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-interrupt.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special target .INTERRUPT in dependency declarations. +# Tests for the special target .INTERRUPT in dependency declarations, which +# collects commands to be run when make is interrupted while building another +# target. # TODO: Implementation all: @:; Index: head/contrib/bmake/unit-tests/deptgt-main.mk =================================================================== --- head/contrib/bmake/unit-tests/deptgt-main.mk (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-main.mk (revision 367863) @@ -1,8 +1,10 @@ -# $NetBSD: deptgt-main.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-main.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special target .MAIN in dependency declarations. +# Tests for the special target .MAIN in dependency declarations, which defines +# the main target. This main target is built if no target has been specified +# on the command line or via MAKEFLAGS. # TODO: Implementation all: @:; Index: head/contrib/bmake/unit-tests/deptgt-makeflags.exp =================================================================== --- head/contrib/bmake/unit-tests/deptgt-makeflags.exp (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-makeflags.exp (revision 367863) @@ -1,9 +1,10 @@ Global:delete DOLLAR (not found) Command:DOLLAR = $$$$ Global:.MAKEOVERRIDES = VAR DOLLAR CondParser_Eval: ${DOLLAR} != "\$\$" Var_Parse: ${DOLLAR} != "\$\$" with VARE_UNDEFERR|VARE_WANTRES lhs = "$$", rhs = "$$", op = != Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d 0 +make: Unterminated quoted string [make VAR=initial UNBALANCED='] exit status 0 Index: head/contrib/bmake/unit-tests/deptgt-makeflags.mk =================================================================== --- head/contrib/bmake/unit-tests/deptgt-makeflags.mk (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-makeflags.mk (revision 367863) @@ -1,51 +1,93 @@ -# $NetBSD: deptgt-makeflags.mk,v 1.4 2020/10/23 14:48:49 rillig Exp $ +# $NetBSD: deptgt-makeflags.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ # # Tests for the special target .MAKEFLAGS in dependency declarations, # which adds command line options later, at parse time. +# +# In these unit tests, it is often used to temporarily toggle the debug log +# during parsing. # The -D option sets a variable in the "Global" scope and thus can be # undefined later. .MAKEFLAGS: -D VAR - .if ${VAR} != 1 . error .endif +# Variables that are set via the -D command line option are normal global +# variables and can thus be undefined later. .undef VAR - .if defined(VAR) . error .endif +# The -D command line option can define a variable again, after it has been +# undefined. .MAKEFLAGS: -D VAR - .if ${VAR} != 1 . error .endif +# The "dependency" for .MAKEFLAGS is split into words, interpreting the usual +# quotes and escape sequences from the backslash. .MAKEFLAGS: VAR="value"' with'\ spaces - .if ${VAR} != "value with spaces" . error .endif # Variables set on the command line as VAR=value are placed in the # "Command" scope and thus cannot be undefined. .undef VAR - .if ${VAR} != "value with spaces" . error .endif # When parsing this line, each '$$' becomes '$', resulting in '$$$$'. # This is assigned to the variable DOLLAR. # In the condition, that variable is expanded, and at that point, each '$$' # becomes '$' again, the final expression is thus '$$'. .MAKEFLAGS: -dcv .MAKEFLAGS: DOLLAR=$$$$$$$$ .if ${DOLLAR} != "\$\$" .endif .MAKEFLAGS: -d0 + +# An empty command line is skipped. +.MAKEFLAGS: # none + +# Escape sequences like \n are interpreted. +# The following line looks as if it assigned a newline to nl, but it doesn't. +# Instead, the \n ends up as a line that is then interpreted as a variable +# assignment. At that point, the line is simply "nl=\n", and the \n is +# skipped since it is whitespace (see Parse_IsVar). +.MAKEFLAGS: nl="\n" +.if ${nl} != "" +. error +.endif + +# Next try at defining another newline variable. Since whitespace around the +# variable value is trimmed, two empty variable expressions surround the +# literal newline now. This prevents the newline from being skipped during +# parsing. The ':=' assignment operator expands the empty variable +# expressions, leaving only the newline as the variable value. +# +# This is one of the very few ways (maybe even the only one) to inject literal +# newlines into a line that is being parsed. This may confuse the parser. +# For example, in cond.c the parser only expects horizontal whitespace (' ' +# and '\t'), but no newlines. +#.MAKEFLAGS: -dcpv +.MAKEFLAGS: nl:="$${:U}\n$${:U}" +.if ${nl} != ${.newline} +. error +.endif +#.MAKEFLAGS: -d0 + +# Unbalanced quotes produce an error message. If they occur anywhere in the +# command line, the whole command line is skipped. +.MAKEFLAGS: VAR=previous +.MAKEFLAGS: VAR=initial UNBALANCED=' +.if ${VAR} != "previous" +. error +.endif all: @:; Index: head/contrib/bmake/unit-tests/deptgt-silent.exp =================================================================== --- head/contrib/bmake/unit-tests/deptgt-silent.exp (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-silent.exp (revision 367863) @@ -1,3 +1,5 @@ +echo 'This is a loud command.' +This is a loud command. This is not echoed because of the @. This is not echoed because of the .SILENT. exit status 0 Index: head/contrib/bmake/unit-tests/deptgt-silent.mk =================================================================== --- head/contrib/bmake/unit-tests/deptgt-silent.mk (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt-silent.mk (revision 367863) @@ -1,10 +1,15 @@ -# $NetBSD: deptgt-silent.mk,v 1.3 2020/09/10 21:40:50 rillig Exp $ +# $NetBSD: deptgt-silent.mk,v 1.4 2020/11/15 20:49:20 rillig Exp $ # # Tests for the special target .SILENT in dependency declarations. .SILENT: all -all: +all: loud @echo 'This is not echoed because of the @.' # Without the .SILENT, the following command would be echoed. echo 'This is not echoed because of the .SILENT.' + +# Demonstrate that the .SILENT only applies to the particular target. +# This is unlike .DELETE_ON_ERROR, which is a global setting. +loud: .PHONY + echo 'This is a loud command.' Index: head/contrib/bmake/unit-tests/deptgt.exp =================================================================== --- head/contrib/bmake/unit-tests/deptgt.exp (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt.exp (revision 367863) @@ -1,5 +1,13 @@ make: "deptgt.mk" line 10: warning: Extra target ignored make: "deptgt.mk" line 28: Unassociated shell command ": command3 # parse error, since targets == NULL" +ParseReadLine (34): '${:U}: empty-source' +ParseDoDependency(: empty-source) +ParseReadLine (35): ' : command for empty targets list' +ParseReadLine (36): ': empty-source' +ParseDoDependency(: empty-source) +ParseReadLine (37): ' : command for empty targets list' +ParseReadLine (38): '.MAKEFLAGS: -d0' +ParseDoDependency(.MAKEFLAGS: -d0) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/deptgt.mk =================================================================== --- head/contrib/bmake/unit-tests/deptgt.mk (revision 367862) +++ head/contrib/bmake/unit-tests/deptgt.mk (revision 367863) @@ -1,31 +1,41 @@ -# $NetBSD: deptgt.mk,v 1.8 2020/10/18 13:02:10 rillig Exp $ +# $NetBSD: deptgt.mk,v 1.9 2020/11/15 11:57:00 rillig Exp $ # # Tests for special targets like .BEGIN or .SUFFIXES in dependency # declarations. # TODO: Implementation # Just in case anyone tries to compile several special targets in a single # dependency line: That doesn't work, and make immediately rejects it. .SUFFIXES .PHONY: .c.o # The following lines demonstrate how 'targets' is set and reset during # parsing of dependencies. To see it in action, set breakpoints in: # # ParseDoDependency at the beginning # FinishDependencyGroup at "targets = NULL" # Parse_File at "Lst_Free(targets)" # Parse_File at "targets = Lst_New()" # ParseLine_ShellCommand at "targets == NULL" # # Keywords: # parse.c:targets target1 target2: sources # targets := [target1, target2] : command1 # targets == [target1, target2] : command2 # targets == [target1, target2] VAR=value # targets := NULL : command3 # parse error, since targets == NULL + +# In a dependency declaration, the list of targets can be empty. +# It doesn't matter whether the empty string is generated by a variable +# expression or whether it is just omitted. +.MAKEFLAGS: -dp +${:U}: empty-source + : command for empty targets list +: empty-source + : command for empty targets list +.MAKEFLAGS: -d0 all: @:; Index: head/contrib/bmake/unit-tests/dir.mk =================================================================== --- head/contrib/bmake/unit-tests/dir.mk (revision 367862) +++ head/contrib/bmake/unit-tests/dir.mk (revision 367863) @@ -1,93 +1,94 @@ -# $NetBSD: dir.mk,v 1.7 2020/10/31 21:30:03 rillig Exp $ +# $NetBSD: dir.mk,v 1.8 2020/11/03 18:42:33 rillig Exp $ # # Tests for dir.c. .MAKEFLAGS: -m / # hide /usr/share/mk from the debug log # Dependency lines may use braces for expansion. # See DirExpandCurly for the implementation. all: {one,two,three} # XXX: The above dependency line is parsed as a single node named # "{one,two,three}". There are no individual targets "one", "two", "three" # yet. The node exists but is not a target since it never appeared # on the left-hand side of a dependency operator. However, it is listed # in .ALLTARGETS (which lists all nodes, not only targets). .if target(one) . error .endif .if target({one,two,three}) . error .endif .if ${.ALLTARGETS:M{one,two,three}} != "{one,two,three}" . error .endif one: : 1 two: : 2 three: : 3 # The braces may start in the middle of a word. all: f{our,ive} four: : 4 five: : 5 six: : 6 # Nested braces work as expected since 2020-07-31 19:06 UTC. # They had been broken at least since 2003-01-01, probably even longer. all: {{thi,fou}r,fif}teen thirteen: : 13 fourteen: : 14 fifteen: : 15 # There may be multiple brace groups side by side. all: {pre-,}{patch,configure} pre-patch patch pre-configure configure: : $@ # Empty pieces are allowed in the braces. all: {fetch,extract}{,-post} fetch fetch-post extract extract-post: : $@ # The expansions may have duplicates. -# These are merged together because of the dependency line. +# When the source of the dependency line is expanded later, each of the +# expanded words will be the same. all: dup-{1,1,1,1,1,1,1} dup-1: : $@ # Other than in Bash, the braces are also expanded if there is no comma. all: {{{{{{{{{{single-word}}}}}}}}}} single-word: : $@ # Demonstrate debug logging for filename expansion, especially curly braces. .MAKEFLAGS: -dd # The below line does not call Dir_Expand yet. # It is expanded only when necessary, that is, when the 'debug' target is # indeed made. debug: {{thi,fou}r,fif}twen # Therefore, keep the debug logging active. .PHONY: one two three four five six .PHONY: thirteen fourteen fifteen .PHONY: single-word .PHONY: pre-patch patch pre-configure configure .PHONY: fetch fetch-post extract extract-post .PHONY: dup-1 single-word .PHONY: all Index: head/contrib/bmake/unit-tests/directive-elif.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-elif.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-elif.exp (revision 367863) @@ -1 +1,17 @@ -exit status 0 +make: "directive-elif.mk" line 7: begin .elif misspellings tests, part 1 +make: "directive-elif.mk" line 9: 1-then +make: "directive-elif.mk" line 18: begin .elif misspellings tests, part 2 +make: "directive-elif.mk" line 29: begin .elif misspellings tests, part 3 +make: "directive-elif.mk" line 41: which branch is taken on misspelling after false? +make: "directive-elif.mk" line 49: else +make: "directive-elif.mk" line 52: which branch is taken on misspelling after true? +make: "directive-elif.mk" line 54: 1-then +make: "directive-elif.mk" line 55: Unknown directive "elsif" +make: "directive-elif.mk" line 56: 1-elsif +make: "directive-elif.mk" line 57: Unknown directive "elsif" +make: "directive-elif.mk" line 58: 2-elsif +make: "directive-elif.mk" line 64: if-less elif +make: "directive-elif.mk" line 69: warning: extra elif +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/directive-elif.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-elif.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-elif.mk (revision 367863) @@ -1,8 +1,72 @@ -# $NetBSD: directive-elif.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-elif.mk,v 1.6 2020/11/12 19:46:36 rillig Exp $ # # Tests for the .elif directive. # TODO: Implementation +.info begin .elif misspellings tests, part 1 +.if 1 +. info 1-then +.elif 1 # ok +. info 1-elif +.elsif 1 # oops: misspelled +. info 1-elsif +.elseif 1 # oops: misspelled +. info 1-elseif +.endif + +.info begin .elif misspellings tests, part 2 +.if 0 +. info 0-then +.elif 0 # ok +. info 0-elif +.elsif 0 # oops: misspelled +. info 0-elsif +.elseif 0 # oops: misspelled +. info 0-elseif +.endif + +.info begin .elif misspellings tests, part 3 +.if 0 +. info 0-then +.elsif 0 # oops: misspelled +. info 0-elsif +.endif +.if 0 +. info 0-then +.elseif 0 # oops: misspelled +. info 0-elseif +.endif + +.info which branch is taken on misspelling after false? +.if 0 +. info 0-then +.elsif 1 +. info 1-elsif +.elsif 2 +. info 2-elsif +.else +. info else +.endif + +.info which branch is taken on misspelling after true? +.if 1 +. info 1-then +.elsif 1 +. info 1-elsif +.elsif 2 +. info 2-elsif +.else +. info else +.endif + +# Expect: "if-less elif" +.elif 0 + +.if 1 +.else +# Expect: "warning: if-less elif" +.elif +.endif + all: - @:; Index: head/contrib/bmake/unit-tests/directive-else.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-else.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-else.exp (revision 367863) @@ -1,8 +1,11 @@ -make: "directive-else.mk" line 10: ok -make: "directive-else.mk" line 14: ok -make: "directive-else.mk" line 20: if-less else -make: "directive-else.mk" line 26: ok -make: "directive-else.mk" line 27: warning: extra else +make: "directive-else.mk" line 11: The .else directive does not take arguments. +make: "directive-else.mk" line 12: ok +make: "directive-else.mk" line 16: ok +make: "directive-else.mk" line 17: The .else directive does not take arguments. +make: "directive-else.mk" line 22: if-less else +make: "directive-else.mk" line 28: ok +make: "directive-else.mk" line 29: warning: extra else +make: "directive-else.mk" line 42: The .else directive does not take arguments. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/directive-else.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-else.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-else.mk (revision 367863) @@ -1,32 +1,46 @@ -# $NetBSD: directive-else.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: directive-else.mk,v 1.6 2020/11/13 09:01:59 rillig Exp $ # # Tests for the .else directive. +.MAKEFLAGS: -dL # To enable the check for ".else " + # The .else directive does not take any arguments. # As of 2020-08-29, make doesn't warn about this. .if 0 . warning must not be reached .else 123 . info ok .endif .if 1 . info ok .else 123 . warning must not be reached .endif # An .else without a corresponding .if is an error. .else # Accidental extra .else directives are detected too. .if 0 . warning must not be reached .else . info ok .else . info After an extra .else, everything is skipped. +.endif + +# An .else may have a comment. This comment does not count as an argument, +# therefore no parse error. +.if 0 +.else # comment +.endif + +# A variable expression does count as an argument, even if it is empty. +# XXX: This should be a parse error. +.if 0 +.else ${:U} .endif all: @:; Index: head/contrib/bmake/unit-tests/directive-endif.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-endif.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-endif.mk (revision 367863) @@ -1,8 +1,27 @@ -# $NetBSD: directive-endif.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-endif.mk,v 1.3 2020/11/12 22:40:11 rillig Exp $ # # Tests for the .endif directive. +# +# See also: +# Cond_EvalLine # TODO: Implementation + +.MAKEFLAGS: -dL + +# Error: .endif does not take arguments +# XXX: Missing error message +.if 0 +.endif 0 + +# Error: .endif does not take arguments +# XXX: Missing error message +.if 1 +.endif 1 + +# Comments are allowed after an '.endif'. +.if 2 +.endif # comment all: @:; Index: head/contrib/bmake/unit-tests/directive-export-env.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-export-env.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-export-env.mk (revision 367863) @@ -1,8 +1,12 @@ -# $NetBSD: directive-export-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-export-env.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .export-env directive. # TODO: Implementation + +.export-en # oops: misspelled +.export-env +.export-environment # oops: misspelled all: @:; Index: head/contrib/bmake/unit-tests/directive-export-gmake.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-export-gmake.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-export-gmake.mk (revision 367863) @@ -1,64 +1,64 @@ -# $NetBSD: directive-export-gmake.mk,v 1.2 2020/10/19 18:59:53 rillig Exp $ +# $NetBSD: directive-export-gmake.mk,v 1.3 2020/11/17 20:16:44 rillig Exp $ # # Tests for the export directive (without leading dot), as in GNU make. # The "export" directive only affects the environment of the make process # and its child processes. It does not affect the global variables or any # other variables. VAR= before export VAR=exported .if ${VAR} != "before" . error .endif # Ensure that the name-value pair is actually exported. .if ${:!echo "\$VAR"!} != "exported" . error .endif # This line looks like it would export 2 variables, but it doesn't. # It only exports VAR and appends everything else as the variable value. export VAR=exported VAR2=exported-as-well .if ${:!echo "\$VAR"!} != "exported VAR2=exported-as-well" . error ${:!echo "\$VAR"!} .endif # Contrary to the usual variable assignments, spaces are significant # after the '=' sign and are prepended to the value of the environment # variable. export VAR= leading spaces .if ${:!echo "\$VAR"!} != " leading spaces" . error .endif # Contrary to the usual variable assignments, spaces are significant # before the '=' sign and are appended to the name of the environment # variable. # # Depending on the shell, environment variables with such exotic names # may be silently discarded. One such shell is dash, which is the default # shell on Ubuntu and Debian. export VAR =trailing space in varname -.if ${:!env | grep trailing!} != "VAR =trailing space in varname" -. if ${:!env | grep trailing!} != "" # for dash +.if ${:!env | grep trailing || true!} != "VAR =trailing space in varname" +. if ${:!env | grep trailing || true!} != "" # for dash . error . endif .endif # The right-hand side of the exported variable is expanded exactly once. TWICE= expanded twice ONCE= expanded once, leaving $${TWICE} as-is export VAR=${ONCE} .if ${:!echo "\$VAR"!} != "expanded once, leaving \${TWICE} as-is" . error .endif # Undefined variables are allowed on the right-hand side, they expand # to an empty string, as usual. export VAR=an ${UNDEF} variable .if ${:!echo "\$VAR"!} != "an variable" . error .endif all: @:; Index: head/contrib/bmake/unit-tests/directive-export-literal.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-export-literal.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-export-literal.mk (revision 367863) @@ -1,11 +1,15 @@ -# $NetBSD: directive-export-literal.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $ +# $NetBSD: directive-export-literal.mk,v 1.6 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .export-literal directive, which exports a variable value # without expanding it. UT_VAR= value with ${UNEXPANDED} expression .export-literal UT_VAR + +.export-litera # oops: misspelled +.export-literal # oops: missing argument +.export-literally # oops: misspelled all: @echo "$$UT_VAR" Index: head/contrib/bmake/unit-tests/directive-export.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-export.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-export.exp (revision 367863) @@ -1 +1,4 @@ -exit status 0 +make: "directive-export.mk" line 25: Unknown directive "expor" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/directive-export.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-export.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-export.mk (revision 367863) @@ -1,25 +1,31 @@ -# $NetBSD: directive-export.mk,v 1.3 2020/10/29 17:27:12 rillig Exp $ +# $NetBSD: directive-export.mk,v 1.4 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .export directive. # TODO: Implementation INDIRECT= indirect VAR= value $$ ${INDIRECT} # A variable is exported using the .export directive. # During that, its value is expanded, just like almost everywhere else. .export VAR .if ${:!env | grep '^VAR'!} != "VAR=value \$ indirect" . error .endif # Undefining a variable that has been exported implicitly removes it from # the environment of all child processes. .undef VAR .if ${:!env | grep '^VAR' || true!} != "" . error .endif + +# Tests for parsing the .export directive. +.expor # misspelled +.export # oops: missing argument +.export VARNAME +.exporting works # oops: misspelled all: @:; Index: head/contrib/bmake/unit-tests/directive-for.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-for.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-for.exp (revision 367863) @@ -1,19 +1,19 @@ -make: "directive-for.mk" line 100: outer -make: "directive-for.mk" line 125: a:\ a:\file.txt -make: "directive-for.mk" line 125: d:\\ -make: "directive-for.mk" line 125: d:\\file.txt -make: "directive-for.mk" line 132: ( ( ( -make: "directive-for.mk" line 132: [ [ [ -make: "directive-for.mk" line 132: { { { -make: "directive-for.mk" line 132: ) ) ) -make: "directive-for.mk" line 132: ] ] ] -make: "directive-for.mk" line 132: } } } -make: "directive-for.mk" line 132: (()) (()) (()) -make: "directive-for.mk" line 132: [[]] [[]] [[]] -make: "directive-for.mk" line 132: {{}} {{}} {{}} -make: "directive-for.mk" line 132: )( )( )( -make: "directive-for.mk" line 132: ][ ][ ][ -make: "directive-for.mk" line 132: }{ }{ }{ -make: "directive-for.mk" line 140: outer value value -make: "directive-for.mk" line 140: outer "quoted" \"quoted\" +make: "directive-for.mk" line 108: outer +make: "directive-for.mk" line 133: a:\ a:\file.txt +make: "directive-for.mk" line 133: d:\\ +make: "directive-for.mk" line 133: d:\\file.txt +make: "directive-for.mk" line 140: ( ( ( +make: "directive-for.mk" line 140: [ [ [ +make: "directive-for.mk" line 140: { { { +make: "directive-for.mk" line 140: ) ) ) +make: "directive-for.mk" line 140: ] ] ] +make: "directive-for.mk" line 140: } } } +make: "directive-for.mk" line 140: (()) (()) (()) +make: "directive-for.mk" line 140: [[]] [[]] [[]] +make: "directive-for.mk" line 140: {{}} {{}} {{}} +make: "directive-for.mk" line 140: )( )( )( +make: "directive-for.mk" line 140: ][ ][ ][ +make: "directive-for.mk" line 140: }{ }{ }{ +make: "directive-for.mk" line 148: outer value value +make: "directive-for.mk" line 148: outer "quoted" \"quoted\" exit status 0 Index: head/contrib/bmake/unit-tests/directive-for.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-for.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-for.mk (revision 367863) @@ -1,144 +1,152 @@ -# $NetBSD: directive-for.mk,v 1.8 2020/10/25 15:49:03 rillig Exp $ +# $NetBSD: directive-for.mk,v 1.9 2020/11/15 20:20:58 rillig Exp $ # # Tests for the .for directive. +# +# TODO: Describe naming conventions for the loop variables. +# .for f in values +# .for file in values +# .for _FILE_ in values +# .for .FILE. in values +# .for _f_ in values # Using the .for loop, lists of values can be produced. # In simple cases, the :@var@${var}@ variable modifier can be used to # reach the same effects. # .undef NUMBERS .for num in 1 2 3 NUMBERS+= ${num} .endfor .if ${NUMBERS} != "1 2 3" . error .endif # The .for loop also works for multiple iteration variables. +# This is something that the variable modifier :@ cannot do. .for name value in VARNAME value NAME2 value2 ${name}= ${value} .endfor .if ${VARNAME} != "value" || ${NAME2} != "value2" . error .endif # The .for loop splits the items at whitespace, taking quotes into account, # just like the :M or :S variable modifiers. # # Until 2012-06-03, it had split the items exactly at whitespace, without -# taking the quotes into account. +# taking the quotes into account. This had resulted in 10 words. # .undef WORDS .for var in one t\ w\ o "three three" 'four four' `five six` WORDS+= counted .endfor .if ${WORDS:[#]} != 6 . error .endif # In the body of the .for loop, the iteration variables can be accessed # like normal variables, even though they are not really variables. # # Instead, the expression ${var} is transformed into ${:U1}, ${:U2} and so # on, before the loop body is evaluated. # # A notable effect of this implementation technique is that the .for # iteration variables and the normal global variables live in separate # namespaces and do not influence each other. # var= value before var2= value before .for var var2 in 1 2 3 4 .endfor .if ${var} != "value before" . warning After the .for loop, var must still have its original value. .endif .if ${var2} != "value before" . warning After the .for loop, var2 must still have its original value. .endif # Everything from the paragraph above also applies if the loop body is # empty, even if there is no actual iteration since the loop items are # also empty. # var= value before var2= value before .for var var2 in ${:U} .endfor .if ${var} != "value before" . warning After the .for loop, var must still have its original value. .endif .if ${var2} != "value before" . warning After the .for loop, var2 must still have its original value. .endif # Until 2008-12-21, the values of the iteration variables were simply # inserted as plain text and then parsed as usual, which made it possible # to achieve all kinds of strange effects. # # Before that date, the .for loop expanded to: # EXPANSION+= value # Since that date, the .for loop expands to: # EXPANSION${:U+}= value # EXPANSION= before EXPANSION+ = before .for plus in + EXPANSION${plus}= value .endfor .if ${EXPANSION} != "before" . error This must be a make from before 2009. .endif .if ${EXPANSION+} != "value" . error This must be a make from before 2009. .endif # When the outer .for loop is expanded, it sees the expression ${i} and # expands it. The inner loop then has nothing more to expand. .for i in outer . for i in inner . info ${i} . endfor .endfor # From https://gnats.netbsd.org/29985. # # Until 2008-12-21, the .for loop was expanded by replacing the variable # value literally in the body. This could lead to situations where the # characters from the variable value were interpreted as markup rather than # plain text. # # Until 2012-06-03, the .for loop had split the words at whitespace, without # taking quotes into account. This made it possible to have variable values # like "a:\ a:\file.txt" that ended in a single backslash. Since then, the # variable values have been replaced with expressions of the form ${:U...}, # which are not interpreted as code anymore. # # As of 2020-09-22, a comment in for.c says that it may be possible to # produce an "unwanted substitution", but there is no demonstration code yet. # # The above changes prevent a backslash at the end of a word from being # interpreted as part of the code. Because of this, the trailingBackslash # hack in Var_Subst is no longer needed and as of 2020-09-22, has been # removed. .for path in a:\ a:\file.txt d:\\ d:\\file.txt . info ${path} .endfor # Ensure that braces and parentheses are properly escaped by the .for loop. # Each line must print the same word 3 times. # See GetEscapes. .for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{ . info $v ${v} $(v) .endfor # As of 2020-10-25, the variable names may contain arbitrary characters, # except for whitespace. This allows for creative side effects. Hopefully # nobody is misusing this "feature". var= outer .for var:Q in value "quoted" . info ${var} ${var:Q} ${var:Q:Q} .endfor all: @:; Index: head/contrib/bmake/unit-tests/directive-if-nested.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-if-nested.exp (nonexistent) +++ head/contrib/bmake/unit-tests/directive-if-nested.exp (revision 367863) @@ -0,0 +1,2 @@ +make: "directive-if-nested.inc" line 1001: deeply nested .if directives +exit status 0 Index: head/contrib/bmake/unit-tests/directive-if-nested.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-if-nested.mk (nonexistent) +++ head/contrib/bmake/unit-tests/directive-if-nested.mk (revision 367863) @@ -0,0 +1,25 @@ +# $NetBSD: directive-if-nested.mk,v 1.1 2020/11/10 22:23:37 rillig Exp $ +# +# Tests for deeply nested .if directives. By default, memory for 128 nested +# .if directives is pre-allocated, any deeper nesting is reallocated. +# +# See also: +# Cond_EvalLine + +GEN= directive-if-nested.inc + +all: set-up test tear-down + +set-up: .PHONY + @{ printf '.if %s\n' ${:U:range=1000}; \ + printf '.info deeply nested .if directives\n'; \ + printf '.endif # %s\n' ${:U:range=1000}; \ + printf '\n'; \ + printf 'all:\n'; \ + } > ${GEN} + +test: .PHONY + @${MAKE} -f ${GEN} + +tear-down: .PHONY + @rm -f ${GEN} Property changes on: head/contrib/bmake/unit-tests/directive-if-nested.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/directive-if.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-if.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-if.exp (revision 367863) @@ -1 +1,15 @@ -exit status 0 +make: "directive-if.mk" line 13: 0 evaluates to false. +make: "directive-if.mk" line 17: 1 evaluates to true. +make: "directive-if.mk" line 40: Unknown directive "ifx" +make: "directive-if.mk" line 41: Unknown directive "error" +make: "directive-if.mk" line 42: if-less else +make: "directive-if.mk" line 43: Unknown directive "error" +make: "directive-if.mk" line 44: if-less endif +make: "directive-if.mk" line 47: Malformed conditional () +make: "directive-if.mk" line 57: Quotes in plain words are probably a mistake. +make: "directive-if.mk" line 66: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 70: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 76: Don't do this, always put a space around comparison operators. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/directive-if.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-if.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-if.mk (revision 367863) @@ -1,8 +1,81 @@ -# $NetBSD: directive-if.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-if.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ # # Tests for the .if directive. +# +# See also: +# cond-*.mk # TODO: Implementation +.if 0 +. error +.else +. info 0 evaluates to false. +.endif + +.if 1 +. info 1 evaluates to true. +.else +. error +.endif + +# There is no '.ifx'. +# +# The commit from 2005-05-01 intended to detect this situation, but it failed +# to do this since the call to is_token had its arguments switched. They were +# expected as (str, token, token_len) but were actually passed as (token, str, +# token_len). This made is_token return true even if the directive was +# directly followed by alphanumerical characters, which was wrong. The +# typical cases produced an error message such as "Malformed conditional +# (x 123)", while the intended error message was "Unknown directive". +# +# Back at that time, the commits only modified the main code but did not add +# the corresponding unit tests. This allowed the bug to hide for more than +# 15 years. +# +# Since 2020-11-10, the correct error message is produced. The '.ifx' is no +# longer interpreted as a variant of '.if', therefore the '.error' and '.else' +# are interpreted as ordinary directives, producing the error messages +# "if-less else" and "if-less endif". +.ifx 123 +. error +.else +. error +.endif + +# Missing condition. +.if +. error +.else +. error +.endif + +# A plain word must not start with a '"'. It may contain a embedded quotes +# though, which are kept. The quotes need not be balanced. The next space +# ends the word, and the remaining " || 1" is parsed as "or true". +.if ${:Uplain"""""} == plain""""" || 1 +. info Quotes in plain words are probably a mistake. +# XXX: Accepting quotes in plain words is probably a mistake as well. +.else +. error +.endif + +.if0 +. error +.else +. info Don't do this, always put a space after a directive. +.endif + +.if${:U-3} +. info Don't do this, always put a space after a directive. +.else +. error +.endif + +.if${:U-3}>-4 +. info Don't do this, always put a space around comparison operators. +.else +. error +.endif + all: - @:; Index: head/contrib/bmake/unit-tests/directive-ifdef.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-ifdef.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-ifdef.exp (revision 367863) @@ -1 +1,2 @@ +make: "directive-ifdef.mk" line 12: Function calls in .ifdef are possible. exit status 0 Index: head/contrib/bmake/unit-tests/directive-ifdef.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-ifdef.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-ifdef.mk (revision 367863) @@ -1,8 +1,18 @@ -# $NetBSD: directive-ifdef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-ifdef.mk,v 1.3 2020/11/08 22:38:28 rillig Exp $ # # Tests for the .ifdef directive. # TODO: Implementation + +DEFINED= defined + +# It looks redundant to have a call to defined() in an .ifdef, but it's +# possible. The .ifdef only affects plain symbols, not function calls. +.ifdef defined(DEFINED) +. info Function calls in .ifdef are possible. +.else +. error +.endif all: @:; Index: head/contrib/bmake/unit-tests/directive-ifmake.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-ifmake.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-ifmake.exp (revision 367863) @@ -1,10 +1,11 @@ -make: "directive-ifmake.mk" line 8: ok: positive condition works -make: "directive-ifmake.mk" line 19: ok: negation works -make: "directive-ifmake.mk" line 25: ok: double negation works -make: "directive-ifmake.mk" line 32: ok: both mentioned -make: "directive-ifmake.mk" line 39: ok: only those mentioned -make: "directive-ifmake.mk" line 49: Targets can even be added at parse time. +make: "directive-ifmake.mk" line 13: ok: positive condition works +make: "directive-ifmake.mk" line 24: ok: negation works +make: "directive-ifmake.mk" line 33: ok: double negation works +make: "directive-ifmake.mk" line 40: ok: both mentioned +make: "directive-ifmake.mk" line 47: ok: only those mentioned +make: "directive-ifmake.mk" line 57: Targets can even be added at parse time. +make: "directive-ifmake.mk" line 75: ok : first : second : late-target exit status 0 Index: head/contrib/bmake/unit-tests/directive-ifmake.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-ifmake.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-ifmake.mk (revision 367863) @@ -1,55 +1,82 @@ -# $NetBSD: directive-ifmake.mk,v 1.4 2020/08/30 14:25:45 rillig Exp $ +# $NetBSD: directive-ifmake.mk,v 1.8 2020/11/15 20:20:58 rillig Exp $ # # Tests for the .ifmake directive, which provides a shortcut for asking # whether a certain target is requested to be made from the command line. +# +# TODO: Describe why the shortcut may be useful (if it's useful at all), +# instead of sticking to the simple '.if' only. +# The targets 'first' and 'second' are passed in on the command line. + # This is the most basic form. .ifmake first -.info ok: positive condition works +. info ok: positive condition works .else -.warning positive condition fails +. warning positive condition fails .endif # The not operator works as expected. # An alternative interpretation were that this condition is asking whether # the target "!first" was requested. To distinguish this, see the next test. .ifmake !first -.warning unexpected +. warning unexpected .else -.info ok: negation works +. info ok: negation works .endif # See if the exclamation mark really means "not", or if it is just part of -# the target name. +# the target name. Since it means 'not', the two exclamation marks are +# effectively ignored, and 'first' is indeed a requested target. If the +# exclamation mark were part of the name instead, the name would be '!!first', +# and such a target was not requested to be made. .ifmake !!first -.info ok: double negation works +. info ok: double negation works .else -.warning double negation fails +. warning double negation fails .endif # Multiple targets can be combined using the && and || operators. .ifmake first && second -.info ok: both mentioned +. info ok: both mentioned .else -.warning && does not work as expected +. warning && does not work as expected .endif # Negation also works in complex conditions. .ifmake first && !unmentioned -.info ok: only those mentioned +. info ok: only those mentioned .else -.warning && with ! does not work as expected +. warning && with ! does not work as expected .endif # Using the .MAKEFLAGS special dependency target, arbitrary command # line options can be added at parse time. This means that it is # possible to extend the targets to be made. .MAKEFLAGS: late-target .ifmake late-target -.info Targets can even be added at parse time. +. info Targets can even be added at parse time. .else -.info No, targets cannot be added at parse time anymore. +. info No, targets cannot be added at parse time anymore. .endif + +# Numbers are interpreted as numbers, no matter whether the directive is +# a plain .if or an .ifmake. +.ifmake 0 +. error +.endif +.ifmake 1 +.else +. error +.endif + +# A condition that consists of a variable expression only (without any +# comparison operator) can be used with .if and the other .ifxxx directives. +.ifmake ${:Ufirst} +. info ok +.else +. error +.endif + first second unmentioned late-target: : $@ Index: head/contrib/bmake/unit-tests/directive-include.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-include.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-include.exp (revision 367863) @@ -1,5 +1,8 @@ CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = != CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = != -exit status 0 +make: "directive-include.mk" line 25: Could not find nonexistent.mk +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/directive-include.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-include.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-include.mk (revision 367863) @@ -1,26 +1,31 @@ -# $NetBSD: directive-include.mk,v 1.3 2020/10/31 23:01:23 rillig Exp $ +# $NetBSD: directive-include.mk,v 1.4 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .include directive, which includes another file. # TODO: Implementation .MAKEFLAGS: -dc # All included files are recorded in the variable .MAKE.MAKEFILES. # In this test, only the basenames of the files are compared since # the directories can differ. .include "/dev/null" .if ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" . error .endif # Each file is recorded only once in the variable .MAKE.MAKEFILES. # Between 2015-11-26 and 2020-10-31, the very last file could be repeated, # due to an off-by-one bug in ParseTrackInput. .include "/dev/null" .if ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" . error .endif + +.include "nonexistent.mk" +.include "/dev/null" # size 0 +# including a directory technically succeeds, but shouldn't. +#.include "." # directory all: @:; Index: head/contrib/bmake/unit-tests/directive-info.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-info.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-info.exp (revision 367863) @@ -1 +1,14 @@ -exit status 0 +make: "directive-info.mk" line 7: begin .info tests +make: "directive-info.mk" line 8: Unknown directive "inf" +make: "directive-info.mk" line 9: Unknown directive "info" +make: "directive-info.mk" line 10: message +make: "directive-info.mk" line 11: indented message +make: "directive-info.mk" line 12: Unknown directive "information" +make: "directive-info.mk" line 13: message +make: "directive-info.mk" line 18: Unknown directive "info" +make: "directive-info.mk" line 19: Unknown directive "info" +make: "directive-info.mk" line 22: Unknown directive "info-message" +make: "directive-info.mk" line 23: no-target: no-source +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/directive-info.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-info.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-info.mk (revision 367863) @@ -1,8 +1,27 @@ -# $NetBSD: directive-info.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-info.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $ # # Tests for the .info directive. # TODO: Implementation + +.info begin .info tests +.inf # misspelled +.info # oops: message should be "missing parameter" +.info message +.info indented message +.information +.information message # oops: misspelled +.info.man: # not a message, but possibly a suffix rule + +# Even if lines would have trailing whitespace, this would be trimmed by +# ParseGetLine. +.info +.info # comment + +.info: message # This is a dependency declaration. +.info-message # This is an unknown directive. +.info no-target: no-source # This is a .info directive, not a dependency. +# See directive.mk for more tests of this kind. all: @:; Index: head/contrib/bmake/unit-tests/directive-sinclude.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-sinclude.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-sinclude.mk (revision 367863) @@ -1,9 +1,13 @@ -# $NetBSD: directive-sinclude.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $ +# $NetBSD: directive-sinclude.mk,v 1.2 2020/11/15 20:20:58 rillig Exp $ # # Tests for the .sinclude directive, which includes another file, # silently skipping it if it cannot be opened. +# +# The 'silently skipping' only applies to the case where the file cannot be +# opened. Parse errors and other errors are handled the same way as in the +# other .include directives. # TODO: Implementation all: @:; Index: head/contrib/bmake/unit-tests/directive-undef.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-undef.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-undef.exp (revision 367863) @@ -1 +1,4 @@ -exit status 0 +make: "directive-undef.mk" line 16: Unknown directive "unde" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/directive-undef.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-undef.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-undef.mk (revision 367863) @@ -1,17 +1,21 @@ -# $NetBSD: directive-undef.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: directive-undef.mk,v 1.5 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .undef directive. # As of 2020-07-28, .undef only undefines the first variable. # All further variable names are silently ignored. # See parse.c, string literal "undef". 1= 1 2= 2 3= 3 .undef 1 2 3 .if ${1:U_}${2:U_}${3:U_} != _23 . warning $1$2$3 .endif + +.unde # misspelled +.undef # oops: missing argument +.undefined # oops: misspelled all: @:; Index: head/contrib/bmake/unit-tests/directive-unexport-env.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-unexport-env.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-unexport-env.mk (revision 367863) @@ -1,8 +1,12 @@ -# $NetBSD: directive-unexport-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-unexport-env.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .unexport-env directive. # TODO: Implementation + +.unexport-en # oops: misspelled +.unexport-env # ok +.unexport-environment # oops: misspelled all: @:; Index: head/contrib/bmake/unit-tests/directive-unexport.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-unexport.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-unexport.exp (revision 367863) @@ -1,5 +1,8 @@ make: "directive-unexport.mk" line 14: UT_A=a UT_B=b UT_C=c make: "directive-unexport.mk" line 15: UT_A UT_B UT_C make: "directive-unexport.mk" line 23: UT_A=a UT_B=b UT_C=c make: "directive-unexport.mk" line 24: -exit status 0 +make: "directive-unexport.mk" line 26: Unknown directive "unexpor" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/directive-unexport.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-unexport.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-unexport.mk (revision 367863) @@ -1,27 +1,31 @@ -# $NetBSD: directive-unexport.mk,v 1.4 2020/10/30 23:54:42 sjg Exp $ +# $NetBSD: directive-unexport.mk,v 1.5 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .unexport directive. # TODO: Implementation # First, export 3 variables. UT_A= a UT_B= b UT_C= c .export UT_A UT_B UT_C # Show the exported variables and their values. .info ${:!env|sort|grep '^UT_'!} .info ${.MAKE.EXPORTED} # XXX: Now try to unexport all of them. The variables are still exported # but not mentioned in .MAKE.EXPORTED anymore. # See the ":N" in Var_UnExport for the implementation. *= asterisk .unexport * .info ${:!env|sort|grep '^UT_'!} .info ${.MAKE.EXPORTED} + +.unexpor # misspelled +.unexport # oops: missing argument +.unexporting works # oops: misspelled all: @:; Index: head/contrib/bmake/unit-tests/directive-warning.exp =================================================================== --- head/contrib/bmake/unit-tests/directive-warning.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive-warning.exp (revision 367863) @@ -1 +1,11 @@ -exit status 0 +make: "directive-warning.mk" line 7: Unknown directive "warn" +make: "directive-warning.mk" line 8: Unknown directive "warn" +make: "directive-warning.mk" line 9: Unknown directive "warnin" +make: "directive-warning.mk" line 10: Unknown directive "warnin" +make: "directive-warning.mk" line 11: Unknown directive "warning" +make: "directive-warning.mk" line 12: warning: message +make: "directive-warning.mk" line 13: Unknown directive "warnings" +make: "directive-warning.mk" line 14: warning: messages +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/directive-warning.mk =================================================================== --- head/contrib/bmake/unit-tests/directive-warning.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive-warning.mk (revision 367863) @@ -1,8 +1,17 @@ -# $NetBSD: directive-warning.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-warning.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .warning directive. # TODO: Implementation + +.warn # misspelled +.warn message # misspelled +.warnin # misspelled +.warnin message # misspelled +.warning # oops: should be "missing argument" +.warning message # ok +.warnings # misspelled +.warnings messages # oops all: @:; Index: head/contrib/bmake/unit-tests/directive.exp =================================================================== --- head/contrib/bmake/unit-tests/directive.exp (revision 367862) +++ head/contrib/bmake/unit-tests/directive.exp (revision 367863) @@ -1 +1,12 @@ -exit status 0 +make: "directive.mk" line 9: Unknown directive "indented" +make: "directive.mk" line 10: Unknown directive "indented" +make: "directive.mk" line 11: Unknown directive "indented" +make: "directive.mk" line 15: Unknown directive "info" +Global:.info = +Global:.info = value +make: "directive.mk" line 26: := value +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/directive.mk =================================================================== --- head/contrib/bmake/unit-tests/directive.mk (revision 367862) +++ head/contrib/bmake/unit-tests/directive.mk (revision 367863) @@ -1,8 +1,35 @@ -# $NetBSD: directive.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $ # # Tests for the preprocessing directives, such as .if or .info. # TODO: Implementation + +# Unknown directives are correctly named in the error messages, +# even if they are indented. +.indented none +. indented 2 spaces +. indented tab + +# Directives must be written directly, not indirectly via variable +# expressions. +.${:Uinfo} directives cannot be indirect + +# There is no directive called '.target', therefore this is parsed as a +# dependency declaration with 2 targets and 1 source. +.target target: source + +# This looks ambiguous. It could be either an .info message or a variable +# assignment. It is a variable assignment. +.MAKEFLAGS: -dv +.info:= value +.info?= value # This is a variable assignment as well. +.info := value # The space after the '.info' makes this + # a directive. +.MAKEFLAGS: -d0 + +# This is a dependency since directives must be given directly. +# Not even the space after the '.info' can change anything about this. +.${:Uinfo} : source all: @:; Index: head/contrib/bmake/unit-tests/dollar.exp =================================================================== --- head/contrib/bmake/unit-tests/dollar.exp (revision 367862) +++ head/contrib/bmake/unit-tests/dollar.exp (revision 367863) @@ -1,51 +1,51 @@ Printing dollar from literals and variables -To survive the parser, a dollar character must be doubled. +To survive the parser, a dollar sign must be doubled. 1 dollar literal => 1 dollar literal eol => <> 2 dollar literal => <$> 4 dollar literal => <$$> Some hungry part of make eats all the dollars after a :U modifier. 1 dollar default => <> 2 dollar default => <> 4 dollar default => <> This works as expected. 1 dollar variable => <> 2 dollar variable => <$> 4 dollar variable => <$$> Some hungry part of make eats all the dollars after a :U modifier. 1 dollar var-default => <> 2 dollar var-default => <$> 4 dollar var-default => <$$> Dollar in :S pattern S,$,word, => <$XYword> S,$X,word, => <$XY> S,$$X,word, => <$XY> S,$$$X,word, => <$XY> S,$X,replaced, => S,$$X,replaced, => S,$$$X,replaced, => Dollar in :C character class The A is replaced because the $$ is reduced to a single $, which is then resolved to the variable X with the value VAR_X. The effective character class becomes [VAR_XY]. C,[$$XY],<&>,g => <$> Dollar in :C pattern For some reason, multiple dollars are folded into one. C,$,dollar,g => <> C,$$,dollar,g => <> Dollar in :S replacement For some reason, multiple dollars are folded into one. S,word,a$Xo, => S,word,a$$Xo, => S,word,a$$$Xo, => exit status 0 Index: head/contrib/bmake/unit-tests/dollar.mk =================================================================== --- head/contrib/bmake/unit-tests/dollar.mk (revision 367862) +++ head/contrib/bmake/unit-tests/dollar.mk (revision 367863) @@ -1,81 +1,81 @@ -# $NetBSD: dollar.mk,v 1.3 2020/05/17 09:37:48 rillig Exp $ +# $NetBSD: dollar.mk,v 1.4 2020/11/03 18:21:36 rillig Exp $ # -# Test the various places where a dollar character can appear and +# Test the various places where a dollar sign can appear and # see what happens. There are lots of surprises here. # LIST= plain 'single' "double" 'mix'"ed" back\ slashed WORD= word DOLLAR1= $ DOLLAR2= $$ DOLLAR4= $$$$ X= VAR_X DOLLAR_XY= $$XY DOLLAR_AXY= $$AXY H= @header() { printf '\n%s\n\n' "$$*"; }; header T= @testcase() { printf '%23s => <%s>\n' "$$@"; }; testcase C= @comment() { printf '%s\n' "$$*"; }; comment # These variable values are not accessed. # The trailing dollar in the '1 dollar literal eol' test case accesses # the empty variable instead, which is always guaranteed to be empty. ${:U }= space-var-value ${:U${.newline}}= newline-var-value # But this one is accessed. ${:U'}= single-quote-var-value' all: $H 'Printing dollar from literals and variables' - $C 'To survive the parser, a dollar character must be doubled.' + $C 'To survive the parser, a dollar sign must be doubled.' $T '1 dollar literal' '$' $T '1 dollar literal eol' ''$ $T '2 dollar literal' '$$' $T '4 dollar literal' '$$$$' $C 'Some hungry part of make eats all the dollars after a :U modifier.' $T '1 dollar default' ''${:U$:Q} $T '2 dollar default' ''${:U$$:Q} $T '4 dollar default' ''${:U$$$$:Q} $C 'This works as expected.' $T '1 dollar variable' ''${DOLLAR1:Q} $T '2 dollar variable' ''${DOLLAR2:Q} $T '4 dollar variable' ''${DOLLAR4:Q} $C 'Some hungry part of make eats all the dollars after a :U modifier.' $T '1 dollar var-default' ''${:U${DOLLAR1}:Q} $T '2 dollar var-default' ''${:U${DOLLAR2}:Q} $T '4 dollar var-default' ''${:U${DOLLAR4}:Q} $H 'Dollar in :S pattern' $T 'S,$$,word,' ''${DOLLAR_XY:S,$,word,:Q} $T 'S,$$X,word,' ''${DOLLAR_XY:S,$X,word,:Q} $T 'S,$$$$X,word,' ''${DOLLAR_XY:S,$$X,word,:Q} $T 'S,$$$$$$X,word,' ''${DOLLAR_XY:S,$$$X,word,:Q} $T 'S,$$X,replaced,' ''${X:S,$X,replaced,:Q} $T 'S,$$$$X,replaced,' ''${X:S,$$X,replaced,:Q} $T 'S,$$$$$$X,replaced,' ''${X:S,$$$X,replaced,:Q} $H 'Dollar in :C character class' $C 'The A is replaced because the $$$$ is reduced to a single $$,' $C 'which is then resolved to the variable X with the value VAR_X.' $C 'The effective character class becomes [VAR_XY].' $T 'C,[$$$$XY],<&>,g' ''${DOLLAR_AXY:C,[$$XY],<&>,g:Q} $H 'Dollar in :C pattern' $C 'For some reason, multiple dollars are folded into one.' $T 'C,$$,dollar,g' ''${DOLLAR:C,$,dollar,g:Q} $T 'C,$$$$,dollar,g' ''${DOLLAR:C,$$,dollar,g:Q} $H 'Dollar in :S replacement' $C 'For some reason, multiple dollars are folded into one.' $T 'S,word,a$$Xo,' ''${WORD:S,word,a$Xo,:Q} $T 'S,word,a$$$$Xo,' ''${WORD:S,word,a$$Xo,:Q} $T 'S,word,a$$$$$$Xo,' ''${WORD:S,word,a$$$Xo,:Q} Index: head/contrib/bmake/unit-tests/envfirst.mk =================================================================== --- head/contrib/bmake/unit-tests/envfirst.mk (revision 367862) +++ head/contrib/bmake/unit-tests/envfirst.mk (revision 367863) @@ -1,42 +1,44 @@ -# $NetBSD: envfirst.mk,v 1.3 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: envfirst.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # The -e option makes environment variables stronger than global variables. + +.MAKEFLAGS: -e .if ${FROM_ENV} != value-from-env . error ${FROM_ENV} .endif # Try to override the variable; this does not have any effect. FROM_ENV= value-from-mk .if ${FROM_ENV} != value-from-env . error ${FROM_ENV} .endif # Try to append to the variable; this also doesn't have any effect. FROM_ENV+= appended .if ${FROM_ENV} != value-from-env . error ${FROM_ENV} .endif # The default assignment also cannot change the variable. FROM_ENV?= default .if ${FROM_ENV} != value-from-env . error ${FROM_ENV} .endif # Neither can the assignment modifiers. .if ${FROM_ENV::=from-condition} .endif .if ${FROM_ENV} != value-from-env . error ${FROM_ENV} .endif # Even .undef doesn't work since it only affects the global context, # which is independent from the environment variables. .undef FROM_ENV .if ${FROM_ENV} != value-from-env . error ${FROM_ENV} .endif all: @: nothing Index: head/contrib/bmake/unit-tests/error.exp =================================================================== --- head/contrib/bmake/unit-tests/error.exp (revision 367862) +++ head/contrib/bmake/unit-tests/error.exp (revision 367863) @@ -1,6 +1,6 @@ -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 +make: "error.mk" line 6: just FYI +make: "error.mk" line 7: warning: this could be serious +make: "error.mk" line 8: this is fatal make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/error.mk =================================================================== --- head/contrib/bmake/unit-tests/error.mk (revision 367862) +++ head/contrib/bmake/unit-tests/error.mk (revision 367863) @@ -1,10 +1,12 @@ -# $NetBSD: error.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ +# $NetBSD: error.mk,v 1.3 2020/11/03 17:38:45 rillig Exp $ +# +# Demonstrate that the .error directive exits immediately, without +# continuing parsing until the end of the file. .info just FYI .warning this could be serious .error this is fatal +.info this is not reached because of the .error above all: - -.info.html: - @echo this should be ignored + : this is not reached because of the .error Index: head/contrib/bmake/unit-tests/escape.mk =================================================================== --- head/contrib/bmake/unit-tests/escape.mk (revision 367862) +++ head/contrib/bmake/unit-tests/escape.mk (revision 367863) @@ -1,246 +1,246 @@ -# $NetBSD: escape.mk,v 1.13 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: escape.mk,v 1.14 2020/11/03 17:38:45 rillig 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, an even number of backslashes before a newline in a # command simply uses the backslashes as part of the command, 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. +# of a syntax error if the preceding line is parsed incorrectly. # 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. +# backslash to the echo command. # 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' Index: head/contrib/bmake/unit-tests/forloop.exp =================================================================== --- head/contrib/bmake/unit-tests/forloop.exp (revision 367862) +++ head/contrib/bmake/unit-tests/forloop.exp (revision 367863) @@ -1,20 +1,20 @@ -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\"" -newline-item=(a) -a=one b="two and three" -a=four b="five" -a=ONE b="TWO AND THREE" -a=FOUR b="FIVE" +make: "forloop.mk" line 14: x=one +make: "forloop.mk" line 14: x="two and three" +make: "forloop.mk" line 14: x=four +make: "forloop.mk" line 14: x="five" +make: "forloop.mk" line 20: x=-I/this +make: "forloop.mk" line 20: x=-I"This or that" +make: "forloop.mk" line 20: x=-Ithat +make: "forloop.mk" line 20: x="-DTHIS=\"this and that\"" +make: "forloop.mk" line 27: cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" +make: "forloop.mk" line 41: newline-item=(a) +make: "forloop.mk" line 47: a=one b="two and three" +make: "forloop.mk" line 47: a=four b="five" +make: "forloop.mk" line 47: a=ONE b="TWO AND THREE" +make: "forloop.mk" line 47: a=FOUR b="FIVE" We expect an error next: make: "forloop.mk" line 46: Wrong number of words (9) in .for substitution list with 2 variables 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 (revision 367862) +++ head/contrib/bmake/unit-tests/forloop.mk (revision 367863) @@ -1,53 +1,53 @@ -# $NetBSD: forloop.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: forloop.mk,v 1.7 2020/11/03 17:37:57 rillig 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 +. info x=$x . endfor CFL= -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" cfl= . for x in ${CFL} -X!= echo 'x=$x' >&2; echo +. info x=$x . if empty(cfl) cfl= $x . else cfl+= $x . endif . endfor -X!= echo 'cfl=${cfl}' >&2; echo +. info cfl=${cfl} . if ${cfl} != ${CFL} -. error ${.newline}'${cfl}' != ${.newline}'${CFL}' +. error ${.newline}${cfl} != ${.newline}${CFL} . endif . for a b in ${EMPTY} -X!= echo 'a=$a b=$b' >&2; echo +. info a=$a b=$b . endfor # Since at least 1993, iteration stops at the first newline. # Back then, the .newline variable didn't exist, therefore it was unlikely # that a newline ever occurred. . for var in a${.newline}b${.newline}c -X!= echo 'newline-item=('${var:Q}')' 1>&2; echo +. info newline-item=(${var}) . endfor .endif # for-fail .for a b in ${LIST} ${LIST:tu} ${XTRA_LIST} -X!= echo 'a=$a b=$b' >&2; echo +. info a=$a b=$b .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/forsubst.mk =================================================================== --- head/contrib/bmake/unit-tests/forsubst.mk (revision 367862) +++ head/contrib/bmake/unit-tests/forsubst.mk (revision 367863) @@ -1,10 +1,22 @@ -# $NetBSD: forsubst.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ +# $NetBSD: forsubst.mk,v 1.3 2020/11/03 17:59:27 rillig Exp $ +# +# The parser used to break dependency lines at ';' without regard for +# substitution patterns. Back then, the first ';' was interpreted as the +# separator between the dependency and its commands. This (perhaps coupled +# with the new handling of .for variables in ${:U...) caused +# interesting results for lines like: +# +# .for file in ${LIST} +# for-subst: ${file:S;^;${here}/;g} +# .endfor +# +# See the commit to unit-tests/forsubst (without the .mk) from 2009-10-07. 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/gnode-submake.exp =================================================================== --- head/contrib/bmake/unit-tests/gnode-submake.exp (nonexistent) +++ head/contrib/bmake/unit-tests/gnode-submake.exp (revision 367863) @@ -0,0 +1,11 @@ +#*** Input graph: +# all, made UNMADE, type OP_DEPENDS, flags none +# makeinfo, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none +# make-index, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# braces-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# braces-no-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# braces-no-dot-modifier, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none +# parentheses-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# parentheses-no-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none + +exit status 0 Index: head/contrib/bmake/unit-tests/gnode-submake.mk =================================================================== --- head/contrib/bmake/unit-tests/gnode-submake.mk (nonexistent) +++ head/contrib/bmake/unit-tests/gnode-submake.mk (revision 367863) @@ -0,0 +1,42 @@ +# $NetBSD: gnode-submake.mk,v 1.1 2020/11/07 23:25:06 rillig Exp $ +# +# Test whether OP_SUBMAKE is determined correctly. If it is, this node's +# shell commands are connected to the make process via pipes, to coordinate +# the number of running jobs. +# +# Determining whether a node is a sub-make node happens when the node is +# parsed. This information is only used in parallel mode, but the result +# from parsing is available in compat mode as well. + +.MAKEFLAGS: -n -dg1 + +all: makeinfo make-index +all: braces-dot braces-no-dot +all: braces-no-dot-modifier +all: parentheses-dot parentheses-no-dot + +makeinfo: + # The command contains the substring "make", but not as a whole word. + : makeinfo submake + +make-index: + # The command contains the word "make", therefore it is considered a + # possible sub-make. It isn't really, but that doesn't hurt. + : make-index + +braces-dot: + : ${.MAKE} + +braces-no-dot: + : ${MAKE} + +braces-no-dot-modifier: + # The command refers to MAKE, but not in its pure form. Therefore it + # is not considered a sub-make. + : ${MAKE:T} + +parentheses-dot: + : $(.MAKE) + +parentheses-no-dot: + : $(MAKE) Property changes on: head/contrib/bmake/unit-tests/gnode-submake.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/include-sub.mk =================================================================== --- head/contrib/bmake/unit-tests/include-sub.mk (revision 367862) +++ head/contrib/bmake/unit-tests/include-sub.mk (revision 367863) @@ -1,49 +1,49 @@ -# $NetBSD: include-sub.mk,v 1.6 2020/10/25 12:08:53 rillig Exp $ +# $NetBSD: include-sub.mk,v 1.7 2020/11/02 19:07:09 rillig Exp $ .if ${.INCLUDEDFROMFILE} == "include-main.mk" . info sub-before-ok .else . warning sub-before-fail(${.INCLUDEDFROMFILE}) .endif # As of 2020-09-05, the .for loop is implemented as "including a file" # with a custom buffer. Therefore this loop has side effects on these # variables. .for i in once . if ${.INCLUDEDFROMFILE} == "include-main.mk" . info sub-before-for-ok . else . warning sub-before-for-fail(${.INCLUDEDFROMFILE}) . endif .endfor # To see the variable 'includes' in action: # # Breakpoints: -# Parse_File at "PtrVector_Push(&includes, curFile)" +# Parse_File at "Vector_Push(&includes)" # ParseMessage at entry # Watches: # ((const IFile *[10])(*includes.items)) # *curFile .for i in deeply . for i in nested . for i in include .include "include-subsub.mk" . endfor . endfor .endfor .if ${.INCLUDEDFROMFILE} == "include-main.mk" . info sub-after-ok .else . warning sub-after-fail(${.INCLUDEDFROMFILE}) .endif .for i in once . if ${.INCLUDEDFROMFILE} == "include-main.mk" . info sub-after-for-ok . else . warning sub-after-for-fail(${.INCLUDEDFROMFILE}) . endif .endfor Index: head/contrib/bmake/unit-tests/job-flags.exp =================================================================== --- head/contrib/bmake/unit-tests/job-flags.exp (nonexistent) +++ head/contrib/bmake/unit-tests/job-flags.exp (revision 367863) @@ -0,0 +1,12 @@ +.BEGIN +silent +ignore +true in ignore +false in ignore +*** [ignore] Error code 1 (ignored) +false without indentation +false space +false tab +*** [ignore-cmds] Error code 1 (ignored) +.END +exit status 0 Index: head/contrib/bmake/unit-tests/job-flags.mk =================================================================== --- head/contrib/bmake/unit-tests/job-flags.mk (nonexistent) +++ head/contrib/bmake/unit-tests/job-flags.mk (revision 367863) @@ -0,0 +1,32 @@ +# $NetBSD: job-flags.mk,v 1.2 2020/11/14 13:17:47 rillig Exp $ +# +# Tests for Job.flags, which are controlled by special source dependencies +# like .SILENT or .IGNORE, as well as the command line options -s or -i. + +.MAKEFLAGS: -j1 + +all: silent .WAIT ignore .WAIT ignore-cmds + +.BEGIN: + @echo $@ + +silent: .SILENT .PHONY + echo $@ + +ignore: .IGNORE .PHONY + @echo $@ + true in $@ + false in $@ + @echo 'Still there in $@' + +ignore-cmds: .PHONY + # This node is not marked .IGNORE; individual commands can be switched + # to ignore mode by prefixing them with a '-'. + -false without indentation + # This also works if the '-' is indented by a space or a tab. + # Leading whitespace is stripped off by ParseLine_ShellCommand. + -false space + -false tab + +.END: + @echo $@ Property changes on: head/contrib/bmake/unit-tests/job-flags.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.mk =================================================================== --- head/contrib/bmake/unit-tests/moderrs.mk (revision 367862) +++ head/contrib/bmake/unit-tests/moderrs.mk (revision 367863) @@ -1,171 +1,171 @@ -# $NetBSD: moderrs.mk,v 1.24 2020/11/01 14:36:25 rillig Exp $ +# $NetBSD: moderrs.mk,v 1.25 2020/11/15 20:20:58 rillig Exp $ # # various modifier error tests '= '\'' VAR= TheVariable # in case we have to change it ;-) MOD_UNKN= Z MOD_TERM= S,V,v MOD_S:= ${MOD_TERM}, FIB= 1 1 2 3 5 8 13 21 34 all: mod-unknown-direct mod-unknown-indirect all: unclosed-direct unclosed-indirect all: unfinished-indirect unfinished-loop all: loop-close all: words all: exclam all: mod-subst-delimiter all: mod-regex-delimiter all: mod-regex-undefined-subexpression all: mod-ts-parse all: mod-t-parse all: mod-ifelse-parse all: mod-remember-parse all: mod-sysv-parse mod-unknown-direct: print-header print-footer @echo 'want: Unknown modifier $'Z$'' @echo 'VAR:Z=before-${VAR:Z}-after' mod-unknown-indirect: print-header print-footer @echo 'want: Unknown modifier $'Z$'' @echo 'VAR:${MOD_UNKN}=before-${VAR:${MOD_UNKN}:inner}-after' unclosed-direct: print-header print-footer @echo 'want: Unclosed variable specification (expecting $'}$') for "VAR" (value "Thevariable") modifier S' @echo VAR:S,V,v,=${VAR:S,V,v, unclosed-indirect: print-header print-footer @echo 'want: Unclosed variable specification after complex modifier (expecting $'}$') for VAR' @echo VAR:${MOD_TERM},=${VAR:${MOD_S} unfinished-indirect: print-header print-footer @echo 'want: Unfinished modifier for VAR ($',$' missing)' -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}" unfinished-loop: print-header print-footer @echo 'want: Unfinished modifier for UNDEF ($'@$' missing)' @echo ${UNDEF:U1 2 3:@var} @echo 'want: Unfinished modifier for UNDEF ($'@$' missing)' @echo ${UNDEF:U1 2 3:@var@...} @echo ${UNDEF:U1 2 3:@var@${var}@} # The closing brace after the ${var} is part of the replacement string. # In ParseModifierPart, braces and parentheses don't have to be balanced. # This is contrary to the :M, :N modifiers, where both parentheses and # braces must be balanced. # This is also contrary to the SysV modifier, where only the actually # used delimiter (either braces or parentheses) must be balanced. loop-close: print-header print-footer @echo ${UNDEF:U1 2 3:@var@${var}}...@ @echo ${UNDEF:U1 2 3:@var@${var}}...@} words: print-header print-footer @echo 'want: Unfinished modifier for UNDEF ($']$' missing)' @echo ${UNDEF:U1 2 3:[} @echo 'want: Unfinished modifier for UNDEF ($']$' missing)' @echo ${UNDEF:U1 2 3:[#} # out of bounds => empty @echo 13=${UNDEF:U1 2 3:[13]} # Word index out of bounds. # # Until 2020-11-01, the behavior in this case depended upon the size # of unsigned long. # # On LP64I32, strtol returns LONG_MAX, which was then truncated to # int (undefined behavior), typically resulting in -1. This -1 was # interpreted as "the last word". # # On ILP32, strtol returns LONG_MAX, which is a large number. This # resulted in a range from LONG_MAX - 1 to 3, which was empty. # # Since 2020-11-01, the numeric overflow is detected and generates an # error. In the remainder of the text, the '$,' is no longer parsed # as part of a variable modifier, where it would have been interpreted # as an anchor to the :S modifier, but as a normal variable named ','. # That variable is undefined, resulting in an empty string. @echo 12345=${UNDEF:U1 2 3:[123451234512345123451234512345]:S,^$,ok,:S,^3$,ok,} exclam: print-header print-footer @echo 'want: Unfinished modifier for VARNAME ($'!$' missing)' @echo ${VARNAME:!echo} # When the final exclamation mark is missing, there is no # fallback to the SysV substitution modifier. # If there were a fallback, the output would be "exclam", # and the above would have produced an "Unknown modifier '!'". @echo 'want: Unfinished modifier for ! ($'!$' missing)' @echo ${!:L:!=exclam} mod-subst-delimiter: print-header print-footer @echo 1: ${VAR:S @echo 2: ${VAR:S, @echo 3: ${VAR:S,from @echo 4: ${VAR:S,from, @echo 5: ${VAR:S,from,to @echo 6: ${VAR:S,from,to, @echo 7: ${VAR:S,from,to,} mod-regex-delimiter: print-header print-footer @echo 1: ${VAR:C @echo 2: ${VAR:C, @echo 3: ${VAR:C,from @echo 4: ${VAR:C,from, @echo 5: ${VAR:C,from,to @echo 6: ${VAR:C,from,to, @echo 7: ${VAR:C,from,to,} # In regular expressions with alternatives, not all capturing groups are # always set; some may be missing. Warn about these. # # Since there is no way to turn off this warning, the combination of -# alternative matches and capturing groups is not widely used. +# alternative matches and capturing groups is seldom used, if at all. # # A newly added modifier 'U' such as in :C,(a.)|(b.),\1\2,U might be added # for treating undefined capturing groups as empty, but that would create a # syntactical ambiguity since the :S and :C modifiers are open-ended (see # mod-subst-chain). Luckily the modifier :U does not make sense after :C, # therefore this case does not happen in practice. # The sub-modifier for the :S and :C modifiers would have to be chosen # wisely, to not create ambiguities while parsing. mod-regex-undefined-subexpression: print-header print-footer @echo ${FIB:C,1(.*),one\1,} # all ok @echo ${FIB:C,1(.*)|2(.*),(\1)+(\2),:Q} # no match for subexpression mod-ts-parse: print-header print-footer @echo ${FIB:ts} @echo ${FIB:ts\65} # octal 065 == U+0035 == '5' @echo ${FIB:ts\65oct} # bad modifier @echo ${FIB:tsxy} # modifier too long mod-t-parse: print-header print-footer @echo ${FIB:t @echo ${FIB:txy} @echo ${FIB:t} @echo ${FIB:t:M*} mod-ifelse-parse: print-header print-footer @echo ${FIB:? @echo ${FIB:?then @echo ${FIB:?then: @echo ${FIB:?then:else @echo ${FIB:?then:else} mod-remember-parse: print-header print-footer @echo ${FIB:_} # ok @echo ${FIB:__} # modifier name too long mod-sysv-parse: print-header print-footer @echo ${FIB:3 @echo ${FIB:3= @echo ${FIB:3=x3 @echo ${FIB:3=x3} # ok print-header: .USEBEFORE @echo $@: print-footer: .USE @echo Index: head/contrib/bmake/unit-tests/modmisc.mk =================================================================== --- head/contrib/bmake/unit-tests/modmisc.mk (revision 367862) +++ head/contrib/bmake/unit-tests/modmisc.mk (revision 367863) @@ -1,92 +1,92 @@ -# $NetBSD: modmisc.mk,v 1.49 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: modmisc.mk,v 1.51 2020/11/15 20:20:58 rillig 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 emptyvar undefvar all: mod-quote all: mod-break-many-words # See also sysv.mk. modsysv: @echo "The answer is ${libfoo.a:L:libfoo.a=42}" # Demonstrates modifiers that are given indirectly from a variable. 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}" # When a modifier is applied to the "" variable, the result is discarded. emptyvar: @echo S:${:S,^$,empty,} @echo C:${:C,^$,empty,} @echo @:${:@var@${var}@} # The :U modifier turns even the "" variable into something that has a value. -# The resulting variable is empty, but is still considered to contain a -# single empty word. This word can be accessed by the :S and :C modifiers, -# but not by the :@ modifier since it explicitly skips empty words. +# The value of the resulting expression is empty, but is still considered to +# contain a single empty word. This word can be accessed by the :S and :C +# modifiers, but not by the :@ modifier since it explicitly skips empty words. undefvar: @echo S:${:U:S,^$,empty,} @echo C:${:U:C,^$,empty,} @echo @:${:U:@var@empty@} mod-quote: @echo $@: new${.newline:Q}${.newline:Q}line -# Cover the bmake_realloc in brk_string. +# Cover the bmake_realloc in Str_Words. mod-break-many-words: @echo $@: ${UNDEF:U:range=500:[#]} # To apply a modifier indirectly via another variable, the whole -# modifier must be put into a single variable. +# modifier must be put into a single variable expression. .if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}" . warning unexpected .endif # Adding another level of indirection (the 2 nested :U expressions) helps. .if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement" . warning unexpected .endif # Multiple indirect modifiers can be applied one after another as long as # they are separated with colons. .if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE" . warning unexpected .endif # An indirect variable that evaluates to the empty string is allowed though. # This makes it possible to define conditional modifiers, like this: # # M.little-endian= S,1234,4321, # M.big-endian= # none .if ${value:L:${:Dempty}S,a,A,} != "vAlue" . warning unexpected .endif Index: head/contrib/bmake/unit-tests/modts.mk =================================================================== --- head/contrib/bmake/unit-tests/modts.mk (revision 367862) +++ head/contrib/bmake/unit-tests/modts.mk (revision 367863) @@ -1,48 +1,47 @@ -# $NetBSD: modts.mk,v 1.7 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: modts.mk,v 1.8 2020/11/03 18:42:33 rillig Exp $ -LIST= one two three -LIST+= four five six +LIST= one two three four five six FU_mod-ts= a / b / cool AAA= a a a B.aaa= Baaa all: mod-ts mod-ts-space # 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: @${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?' mod-ts-space: # After the :ts modifier, the whole string is interpreted as a single # word since all spaces have been replaced with x. @${PRINT} ':ts :S => '${aa bb aa bb aa bb:L:tsx:S,b,B,:Q} # The :ts modifier also applies to word separators that are added # afterwards. @${PRINT} ':ts :S space => '${a ababa c:L:tsx:S,b, ,g:Q} @${PRINT} ':ts :S space :M => '${a ababa c:L:tsx:S,b, ,g:M*:Q} # Not all modifiers behave this way though. Some of them always use # a space as word separator instead of the :ts separator. # This seems like an oversight during implementation. @${PRINT} ':ts :S => '${a ababa c:L:tsx:S,b, ,g:Q} @${PRINT} ':ts :S :@ => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:Q} # A final :M* modifier applies the :ts separator again, though. @${PRINT} ':ts :S :@ :M => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:M*:Q} Index: head/contrib/bmake/unit-tests/modword.mk =================================================================== --- head/contrib/bmake/unit-tests/modword.mk (revision 367862) +++ head/contrib/bmake/unit-tests/modword.mk (revision 367863) @@ -1,159 +1,160 @@ -# $NetBSD: modword.mk,v 1.4 2020/11/01 13:55:31 rillig Exp $ +# $NetBSD: modword.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # # Test behaviour of new :[] modifier +# TODO: When was this modifier new? 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-space 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]}"' @echo 'LONGLIST:[012..0x12]="${LONGLIST:[012..0x12]}"' 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-squarebrackets-space: # As of 2020-11-01, it is possible to have spaces before the numbers # but not after them. This is an unintended side-effect of using # strtol for parsing the numbers. @echo 'LIST:[ -1.. +3]="${LIST:[ -1.. +3]}"' 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/objdir-writable.exp =================================================================== --- head/contrib/bmake/unit-tests/objdir-writable.exp (nonexistent) +++ head/contrib/bmake/unit-tests/objdir-writable.exp (revision 367863) @@ -0,0 +1,5 @@ +make warning: OBJDIR/roobj: Permission denied. +/tmp +OBJDIR/roobj +OBJDIR/roobj +exit status 0 Index: head/contrib/bmake/unit-tests/objdir-writable.mk =================================================================== --- head/contrib/bmake/unit-tests/objdir-writable.mk (nonexistent) +++ head/contrib/bmake/unit-tests/objdir-writable.mk (revision 367863) @@ -0,0 +1,31 @@ +# $NetBSD: objdir-writable.mk,v 1.4 2020/11/14 07:36:00 sjg Exp $ + +# test checking for writable objdir + +RO_OBJDIR?= ${TMPDIR:U/tmp}/roobj + +.if make(do-objdir) +# this should succeed +.OBJDIR: ${RO_OBJDIR} + +do-objdir: +.else +all: no-objdir ro-objdir explicit-objdir + +# make it now +x!= echo; mkdir -p ${RO_OBJDIR}; chmod 555 ${RO_OBJDIR} + +.END: rm-objdir +rm-objdir: + @rmdir ${RO_OBJDIR} + +no-objdir: + @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR + +ro-objdir: + @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR MAKE_OBJDIR_CHECK_WRITABLE=no + +explicit-objdir: + @MAKEOBJDIR=/tmp ${.MAKE} -r -f ${MAKEFILE:tA} -C /tmp do-objdir -V .OBJDIR +.endif + Property changes on: head/contrib/bmake/unit-tests/objdir-writable.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/opt-chdir.exp =================================================================== --- head/contrib/bmake/unit-tests/opt-chdir.exp (revision 367862) +++ head/contrib/bmake/unit-tests/opt-chdir.exp (revision 367863) @@ -1 +1,6 @@ +make: chdir /./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././: File name too long +*** Error code 1 (ignored) +cwd: / +make: chdir /nonexistent: No such file or directory +*** Error code 1 (ignored) exit status 0 Index: head/contrib/bmake/unit-tests/opt-chdir.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-chdir.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-chdir.mk (revision 367863) @@ -1,8 +1,27 @@ -# $NetBSD: opt-chdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-chdir.mk,v 1.5 2020/11/15 05:43:56 sjg Exp $ # -# Tests for the -C command line option. +# Tests for the -C command line option, which changes the directory at the +# beginning. +# +# This option has been available since 2009-08-27. -# TODO: Implementation +.MAKEFLAGS: -d0 # switch stdout to line-buffered -all: - @:; +all: chdir-filename-too-long +all: chdir-root +all: chdir-nonexistent + +# Try to overflow the internal buffer for .CURDIR, which is curdir. +chdir-filename-too-long: .PHONY .IGNORE + # 5000 slashes, separated by dots: /./././.../././ + @${MAKE} -C ${:U:range=5000:@@/@:ts.} + +# Changing to another directory is possible via the command line. +# In this test, it is the root directory since almost any other directory +# is not guaranteed to exist on every platform. +chdir-root: .PHONY .IGNORE + @MAKE_OBJDIR_CHECK_WRITABLE=no ${MAKE} -C / -V 'cwd: $${.CURDIR}' + +# Trying to change to a nonexistent directory exits immediately. +chdir-nonexistent: .PHONY .IGNORE + @${MAKE} -C /nonexistent Index: head/contrib/bmake/unit-tests/opt-debug-jobs.exp =================================================================== --- head/contrib/bmake/unit-tests/opt-debug-jobs.exp (revision 367862) +++ head/contrib/bmake/unit-tests/opt-debug-jobs.exp (revision 367863) @@ -1,25 +1,27 @@ job_pipe -1 -1, maxjobs 1, tokens 1, compat 0 Job_TokenWithdraw(): aborting 0, running 0 () withdrew token echo ": expanded expression" { : expanded expression } || exit $? echo ": variable" { : variable } || exit $? echo ": 'single' and \"double\" quotes" { : 'single' and "double" quotes } || exit $? -Running all locally +{ sleep 1 +} || exit $? +Running all Command: sh JobExec(all): pid added to jobs table job table @ job started job 0, status 3, flags 0, pid : expanded expression : variable : 'single' and "double" quotes Process exited/stopped status 0. JobFinish: [all], status 0 Job_TokenWithdraw(): aborting 0, running 0 () withdrew token exit status 0 Index: head/contrib/bmake/unit-tests/opt-debug-jobs.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-debug-jobs.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-debug-jobs.mk (revision 367863) @@ -1,26 +1,33 @@ -# $NetBSD: opt-debug-jobs.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# $NetBSD: opt-debug-jobs.mk,v 1.5 2020/11/12 21:54:52 rillig Exp $ # # Tests for the -dj command line option, which adds debug logging about # running jobs in multiple shells. .MAKEFLAGS: -dj # Run in parallel mode since the debug logging is more interesting there # than in compat mode. .MAKEFLAGS: -j1 all: # Only the actual command is logged. # To see the evaluation of the variable expressions, use -dv. : ${:Uexpanded} expression # Undefined variables expand to empty strings. # Multiple spaces are preserved in the command, as they might be # significant. : ${UNDEF} variable # In the debug output, single quotes are not escaped, even though # the whole command is enclosed in single quotes as well. # This allows to copy and paste the whole command, without having # to unescape anything. : 'single' and "double" quotes + + # Avoid a race condition in the debug output. Without sleeping, + # it is not guaranteed that the two lines "exited/stopped" and + # "JobFinish" are output earlier than the stdout of the actual shell + # commands. The '@' prefix avoids that this final command gets into + # another race condition with the "exited/stopped" line. + @sleep 1 Index: head/contrib/bmake/unit-tests/opt-ignore.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-ignore.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-ignore.mk (revision 367863) @@ -1,32 +1,33 @@ -# $NetBSD: opt-ignore.mk,v 1.4 2020/10/18 18:12:42 rillig Exp $ +# $NetBSD: opt-ignore.mk,v 1.5 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -i command line option, which ignores the exit status of the # shell commands, and just continues with the next command, even from the same # target. # # Is there a situation in which this option is useful? # # Why are the "Error code" lines all collected at the bottom of the output # file, where they cannot be related to the individual shell commands that # failed? .MAKEFLAGS: -d0 # switch stdout to being line-buffered +.MAKEFLAGS: -i all: dependency other dependency: @echo dependency 1 @false @echo dependency 2 @:; exit 7 @echo dependency 3 other: @echo other 1 @false @echo other 2 all: @echo main 1 @false @echo main 2 Index: head/contrib/bmake/unit-tests/opt-keep-going.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-keep-going.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-keep-going.mk (revision 367863) @@ -1,26 +1,27 @@ -# $NetBSD: opt-keep-going.mk,v 1.4 2020/10/18 18:12:42 rillig Exp $ +# $NetBSD: opt-keep-going.mk,v 1.5 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -k command line option, which stops building a target as soon # as an error is detected, but continues building the other, independent # targets, as far as possible. .MAKEFLAGS: -d0 # switch stdout to being line-buffered +.MAKEFLAGS: -k all: dependency other dependency: @echo dependency 1 @false @echo dependency 2 @:; exit 7 @echo dependency 3 other: @echo other 1 @false @echo other 2 all: @echo main 1 @false @echo main 2 Index: head/contrib/bmake/unit-tests/opt-no-action.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-no-action.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-no-action.mk (revision 367863) @@ -1,33 +1,35 @@ -# $NetBSD: opt-no-action.mk,v 1.3 2020/08/19 05:25:26 rillig Exp $ +# $NetBSD: opt-no-action.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -n command line option, which runs almost no commands. # It just outputs them, to be inspected by human readers. # Only commands that are in a .MAKE target or prefixed by '+' are run. + +.MAKEFLAGS: -n # This command cannot be prevented from being run since it is used at parse # time, and any later variable assignments may depend on its result. != echo 'command during parsing' 1>&2; echo all: main all: run-always # Both of these commands are printed, but only the '+' command is run. .BEGIN: @echo '$@: hidden command' @+echo '$@: run always' # Both of these commands are printed, but only the '+' command is run. main: @echo '$@: hidden command' @+echo '$@: run always' # None of these commands is printed, but both are run, because this target # depends on the special source ".MAKE". run-always: .MAKE @echo '$@: hidden command' @+echo '$@: run always' # Both of these commands are printed, but only the '+' command is run. .END: @echo '$@: hidden command' @+echo '$@: run always' Index: head/contrib/bmake/unit-tests/opt-query.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-query.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-query.mk (revision 367863) @@ -1,24 +1,26 @@ -# $NetBSD: opt-query.mk,v 1.3 2020/08/19 05:13:18 rillig Exp $ +# $NetBSD: opt-query.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -q command line option. # # The -q option only looks at the dependencies between the targets. # None of the commands in the targets are run, not even those that are # prefixed with '+'. + +.MAKEFLAGS: -q # This command cannot be prevented from being run since it is used at parse # time, and any later variable assignments may depend on its result. != echo 'command during parsing' 1>&2; echo # None of these commands are run. .BEGIN: @echo '$@: hidden command' @+echo '$@: run always' # None of these commands are run. all: @echo '$@: hidden command' @+echo '$@: run always' # The exit status 1 is because the "all" target has to be made, that is, # it is not up-to-date. Index: head/contrib/bmake/unit-tests/opt-touch-jobs.exp =================================================================== --- head/contrib/bmake/unit-tests/opt-touch-jobs.exp (nonexistent) +++ head/contrib/bmake/unit-tests/opt-touch-jobs.exp (revision 367863) @@ -0,0 +1,4 @@ +: Making opt-touch-make. +`opt-touch-join' is up to date. +`opt-touch-use' is up to date. +exit status 0 Index: head/contrib/bmake/unit-tests/opt-touch-jobs.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-touch-jobs.mk (nonexistent) +++ head/contrib/bmake/unit-tests/opt-touch-jobs.mk (revision 367863) @@ -0,0 +1,30 @@ +# $NetBSD: opt-touch-jobs.mk,v 1.1 2020/11/14 15:35:20 rillig Exp $ +# +# Tests for the -t command line option in jobs mode. + +.MAKEFLAGS: -j1 +.MAKEFLAGS: -t +.MAKEFLAGS: opt-touch-phony +.MAKEFLAGS: opt-touch-join +.MAKEFLAGS: opt-touch-use +.MAKEFLAGS: opt-touch-make + +opt-touch-phony: .PHONY + : Making $@. + +opt-touch-join: .JOIN + : Making $@. + +opt-touch-use: .USE + : Making use of $@. + +# Even though it is listed last, in the output it appears first. +# This is because it is the only node that actually needs to be run. +# The "is up to date" of the other nodes happens after all jobs have +# finished, by Make_Run > MakePrintStatusList > MakePrintStatus. +opt-touch-make: .MAKE + : Making $@. + +.END: + @files=$$(ls opt-touch-* 2>/dev/null | grep -v -e '\.' -e '\*'); \ + [ -z "$$files" ] || { echo "created files: $$files" 1>&2; exit 1; } Property changes on: head/contrib/bmake/unit-tests/opt-touch-jobs.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/opt-touch.exp =================================================================== --- head/contrib/bmake/unit-tests/opt-touch.exp (revision 367862) +++ head/contrib/bmake/unit-tests/opt-touch.exp (revision 367863) @@ -1 +1,4 @@ +`opt-touch-join' is up to date. +`opt-touch-use' is up to date. +: Making opt-touch-make. exit status 0 Index: head/contrib/bmake/unit-tests/opt-touch.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-touch.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-touch.mk (revision 367863) @@ -1,8 +1,21 @@ -# $NetBSD: opt-touch.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-touch.mk,v 1.4 2020/11/14 14:13:09 rillig Exp $ # # Tests for the -t command line option. -# TODO: Implementation +.MAKEFLAGS: -t opt-touch-phony opt-touch-join opt-touch-use opt-touch-make -all: - @:; +opt-touch-phony: .PHONY + : Making $@. + +opt-touch-join: .JOIN + : Making $@. + +opt-touch-use: .USE + : Making use of $@. + +opt-touch-make: .MAKE + : Making $@. + +.END: + @files=$$(ls opt-touch-* 2>/dev/null | grep -v -e '\.' -e '\*'); \ + [ -z "$$files" ] || { echo "created files: $$files" 1>&2; exit 1; } Index: head/contrib/bmake/unit-tests/opt-var-expanded.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-var-expanded.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-var-expanded.mk (revision 367863) @@ -1,6 +1,8 @@ -# $NetBSD: opt-var-expanded.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $ +# $NetBSD: opt-var-expanded.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -v command line option. + +.MAKEFLAGS: -v VAR -v VALUE VAR= other ${VALUE} $$$$ VALUE= value Index: head/contrib/bmake/unit-tests/opt-var-literal.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-var-literal.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-var-literal.mk (revision 367863) @@ -1,6 +1,8 @@ -# $NetBSD: opt-var-literal.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $ +# $NetBSD: opt-var-literal.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -V command line option. + +.MAKEFLAGS: -V VAR -V VALUE VAR= other ${VALUE} $$$$ VALUE= value Index: head/contrib/bmake/unit-tests/opt-warnings-as-errors.exp =================================================================== --- head/contrib/bmake/unit-tests/opt-warnings-as-errors.exp (revision 367862) +++ head/contrib/bmake/unit-tests/opt-warnings-as-errors.exp (revision 367863) @@ -1,7 +1,7 @@ -make: "opt-warnings-as-errors.mk" line 5: warning: message 1 +make: "opt-warnings-as-errors.mk" line 7: warning: message 1 make: parsing warnings being treated as errors -make: "opt-warnings-as-errors.mk" line 6: warning: message 2 +make: "opt-warnings-as-errors.mk" line 8: warning: message 2 parsing continues make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/opt-warnings-as-errors.mk =================================================================== --- head/contrib/bmake/unit-tests/opt-warnings-as-errors.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt-warnings-as-errors.mk (revision 367863) @@ -1,11 +1,13 @@ -# $NetBSD: opt-warnings-as-errors.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $ +# $NetBSD: opt-warnings-as-errors.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -W command line option, which turns warnings into errors. + +.MAKEFLAGS: -W .warning message 1 .warning message 2 _!= echo 'parsing continues' 1>&2 all: @:; Index: head/contrib/bmake/unit-tests/opt.exp =================================================================== --- head/contrib/bmake/unit-tests/opt.exp (revision 367862) +++ head/contrib/bmake/unit-tests/opt.exp (revision 367863) @@ -1 +1,22 @@ +make -r -f /dev/null -V MAKEFLAGS + -r -k -d 0 + +make -: +usage: make [-BeikNnqrSstWwX] + [-C directory] [-D variable] [-d flags] [-f makefile] + [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file] + [-V variable] [-v variable] [variable=value] [target ...] +*** Error code 2 (ignored) + +make -r -f /dev/null -- -VAR=value -f /dev/null +make: don't know how to make -f (continuing) +`/dev/null' is up to date. + +make -? +usage: make [-BeikNnqrSstWwX] + [-C directory] [-D variable] [-d flags] [-f makefile] + [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file] + [-V variable] [-v variable] [variable=value] [target ...] +*** Error code 2 (ignored) + exit status 0 Index: head/contrib/bmake/unit-tests/opt.mk =================================================================== --- head/contrib/bmake/unit-tests/opt.mk (revision 367862) +++ head/contrib/bmake/unit-tests/opt.mk (revision 367863) @@ -1,8 +1,28 @@ -# $NetBSD: opt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt.mk,v 1.6 2020/11/18 01:06:59 sjg Exp $ # # Tests for the command line options. -# TODO: Implementation +.MAKEFLAGS: -d0 # make stdout line-buffered -all: - @:; +all: .IGNORE + # The options from the top-level make are passed to the sub-makes via + # the environment variable MAKEFLAGS. This is where the " -r -k -d 0" + # comes from. See MainParseArg. + ${MAKE} -r -f /dev/null -V MAKEFLAGS + @echo + + # Just to see how the custom argument parsing code reacts to a syntax + # error. The colon is used in the options string, marking an option + # that takes arguments. It is not an option by itself, though. + ${MAKE} -: + @echo + + # See whether a '--' stops handling of command line options, like in + # standard getopt programs. Yes, it does, and it treats the + # second '-f' as a target to be created. + ${MAKE} -r -f /dev/null -- -VAR=value -f /dev/null + @echo + + # This is the normal way to print the usage of a command. + ${MAKE} -? + @echo Index: head/contrib/bmake/unit-tests/order.mk =================================================================== --- head/contrib/bmake/unit-tests/order.mk (revision 367862) +++ head/contrib/bmake/unit-tests/order.mk (revision 367863) @@ -1,20 +1,22 @@ -# $NetBSD: order.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: order.mk,v 1.2 2020/11/09 20:50:56 rillig 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. + +.MAKEFLAGS: -j1 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/recursive.exp =================================================================== --- head/contrib/bmake/unit-tests/recursive.exp (revision 367862) +++ head/contrib/bmake/unit-tests/recursive.exp (revision 367863) @@ -1,5 +1,5 @@ -make: "recursive.mk" line 34: Unclosed variable "MISSING_PAREN" -make: "recursive.mk" line 35: Unclosed variable "MISSING_BRACE" +make: "recursive.mk" line 36: Unclosed variable "MISSING_PAREN" +make: "recursive.mk" line 37: Unclosed variable "MISSING_BRACE" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/recursive.mk =================================================================== --- head/contrib/bmake/unit-tests/recursive.mk (revision 367862) +++ head/contrib/bmake/unit-tests/recursive.mk (revision 367863) @@ -1,37 +1,39 @@ -# $NetBSD: recursive.mk,v 1.3 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: recursive.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # In -dL mode, a variable may get expanded before it makes sense. # This would stop make from doing anything since the "recursive" error # is fatal and exits immediately. # # The purpose of evaluating that variable early was just to detect # whether there are unclosed variables. It might be enough to parse the # variable value without VARE_WANTRES for that purpose. # # Seen in pkgsrc/x11/libXfixes, and probably many more package that use # GNU Automake. + +.MAKEFLAGS: -dL AM_V_lt= ${am__v_lt_${V}} am__v_lt_= ${am__v_lt_${AM_DEFAULT_VERBOSITY}} am__v_lt_0= --silent am__v_lt_1= # On 2020-08-06, make reported: "Variable am__v_lt_ is recursive." libXfixes_la_LINK= ... ${AM_V_lt} ... # somewhere later ... AM_DEFAULT_VERBOSITY= 1 # The purpose of the -dL flag is to detect unclosed variables. This # can be achieved by just parsing the variable and not evaluating it. # # When the variable is only parsed but not evaluated, bugs in nested # variables are not discovered. But these are hard to produce anyway, # therefore that's acceptable. In most practical cases, the missing # brace would be detected directly in the line where it is produced. MISSING_BRACE_INDIRECT:= ${:U\${MISSING_BRACE} UNCLOSED= $(MISSING_PAREN UNCLOSED= ${MISSING_BRACE UNCLOSED= ${MISSING_BRACE_INDIRECT} Index: head/contrib/bmake/unit-tests/sh-leading-at.exp =================================================================== --- head/contrib/bmake/unit-tests/sh-leading-at.exp (revision 367862) +++ head/contrib/bmake/unit-tests/sh-leading-at.exp (revision 367863) @@ -1,5 +1,6 @@ ok space after @ echo 'echoed' echoed +3 exit status 0 Index: head/contrib/bmake/unit-tests/sh-leading-at.mk =================================================================== --- head/contrib/bmake/unit-tests/sh-leading-at.mk (revision 367862) +++ head/contrib/bmake/unit-tests/sh-leading-at.mk (revision 367863) @@ -1,10 +1,18 @@ -# $NetBSD: sh-leading-at.mk,v 1.3 2020/08/22 09:16:08 rillig Exp $ +# $NetBSD: sh-leading-at.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # # Tests for shell commands preceded by an '@', to suppress printing # the command to stdout. +# +# See also: +# .SILENT +# depsrc-silent.mk +# opt-silent.mk all: @ @echo 'ok' @ echo 'space after @' echo 'echoed' + # The leading '@' can be repeated. + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@echo '3' Index: head/contrib/bmake/unit-tests/sh-leading-hyphen.mk =================================================================== --- head/contrib/bmake/unit-tests/sh-leading-hyphen.mk (revision 367862) +++ head/contrib/bmake/unit-tests/sh-leading-hyphen.mk (revision 367863) @@ -1,9 +1,14 @@ -# $NetBSD: sh-leading-hyphen.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: sh-leading-hyphen.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # # Tests for shell commands preceded by a '-', to ignore the exit status of # the command line. +# +# See also: +# .IGNORE +# depsrc-ignore.mk +# opt-ignore.mk # TODO: Implementation all: @:; Index: head/contrib/bmake/unit-tests/sh-leading-plus.mk =================================================================== --- head/contrib/bmake/unit-tests/sh-leading-plus.mk (revision 367862) +++ head/contrib/bmake/unit-tests/sh-leading-plus.mk (revision 367863) @@ -1,8 +1,10 @@ -# $NetBSD: sh-leading-plus.mk,v 1.3 2020/08/23 14:46:33 rillig Exp $ +# $NetBSD: sh-leading-plus.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for shell commands preceded by a '+', to run them even if # the command line option -n is given. + +.MAKEFLAGS: -n all: @echo 'this command is not run' @+echo 'this command is run' Index: head/contrib/bmake/unit-tests/sh-meta-chars.mk =================================================================== --- head/contrib/bmake/unit-tests/sh-meta-chars.mk (revision 367862) +++ head/contrib/bmake/unit-tests/sh-meta-chars.mk (revision 367863) @@ -1,11 +1,15 @@ -# $NetBSD: sh-meta-chars.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: sh-meta-chars.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # # Tests for running shell commands that contain meta-characters. # # These meta-characters decide whether the command is run by the shell -# or executed directly via execv. See Cmd_Exec for details. +# or executed directly via execv, but only in compatibility mode, not +# in jobs mode, and only if MAKE_NATIVE is defined during compilation. +# +# See also: +# Compat_RunCommand, useShell # TODO: Implementation all: @:; Index: head/contrib/bmake/unit-tests/suff-self.exp =================================================================== --- head/contrib/bmake/unit-tests/suff-self.exp (nonexistent) +++ head/contrib/bmake/unit-tests/suff-self.exp (revision 367863) @@ -0,0 +1,3 @@ +make: Graph cycles through suff-self.suff +`all' not remade because of errors. +exit status 0 Index: head/contrib/bmake/unit-tests/suff-self.mk =================================================================== --- head/contrib/bmake/unit-tests/suff-self.mk (nonexistent) +++ head/contrib/bmake/unit-tests/suff-self.mk (revision 367863) @@ -0,0 +1,11 @@ +# $NetBSD: suff-self.mk,v 1.1 2020/11/16 15:12:16 rillig Exp $ +# +# See what happens if someone defines a self-referencing suffix +# transformation rule. + +.SUFFIXES: .suff + +.suff.suff: + : Making ${.TARGET} out of ${.IMPSRC}. + +all: suff-self.suff Property changes on: head/contrib/bmake/unit-tests/suff-self.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/use-inference.mk =================================================================== --- head/contrib/bmake/unit-tests/use-inference.mk (revision 367862) +++ head/contrib/bmake/unit-tests/use-inference.mk (revision 367863) @@ -1,35 +1,38 @@ -# $NetBSD: use-inference.mk,v 1.1 2020/08/09 16:32:28 rillig Exp $ +# $NetBSD: use-inference.mk,v 1.2 2020/11/05 00:41:04 rillig Exp $ # # Demonstrate that .USE rules do not have an effect on inference rules. # At least not in the special case where the inference rule does not # have any associated commands. .SUFFIXES: .SUFFIXES: .from .to all: use-inference.to verbose: .USE @echo 'Verbosely making $@ out of $>' .from.to: verbose # Since this inference rule does not have any associated commands, it # is ignored. # # @echo 'Building $@ from $<' use-inference.from: # assume it exists @echo 'Building $@ from nothing' # Possible but unproven explanation: # # The main target is "all", which depends on "use-inference.to". # The inference connects the .from to the .to file, otherwise make # would not know that the .from file would need to be built. # # The .from file is then built. # # After this, make stops since it doesn't know how to make the .to file. # This is strange since make definitely knows about the .from.to suffix # inference rule. But it seems to ignore it, maybe because it doesn't # have any associated commands. + +# XXX: Despite the error message "don't know how to make", the exit status +# is 0. This is inconsistent. Index: head/contrib/bmake/unit-tests/var-class-local.exp =================================================================== --- head/contrib/bmake/unit-tests/var-class-local.exp (revision 367862) +++ head/contrib/bmake/unit-tests/var-class-local.exp (revision 367863) @@ -1,2 +1,5 @@ +: Making var-class-local.c out of nothing. +: Making var-class-local.o from var-class-local.c. +: Making basename "var-class-local.o" in "." from "var-class-local.c" in ".". : all overwritten exit status 0 Index: head/contrib/bmake/unit-tests/var-class-local.mk =================================================================== --- head/contrib/bmake/unit-tests/var-class-local.mk (revision 367862) +++ head/contrib/bmake/unit-tests/var-class-local.mk (revision 367863) @@ -1,31 +1,45 @@ -# $NetBSD: var-class-local.mk,v 1.4 2020/10/25 09:46:25 rillig Exp $ +# $NetBSD: var-class-local.mk,v 1.5 2020/11/05 18:08:39 rillig Exp $ # # Tests for target-local variables, such as ${.TARGET} or $@. # TODO: Implementation # Ensure that the name of the variable is exactly the given one. # The variable "@" is an alias for ".TARGET", so the implementation might # canonicalize these aliases at some point, and that might be surprising. # This aliasing happens for single-character variable names like $@ or $< # (see VarFind, CanonicalVarname), but not for braced or parenthesized # expressions like ${@}, ${.TARGET} ${VAR:Mpattern} (see Var_Parse, # ParseVarname). .if ${@:L} != "@" . error .endif .if ${.TARGET:L} != ".TARGET" . error .endif .if ${@F:L} != "@F" . error .endif .if ${@D:L} != "@D" . error .endif all: + +.SUFFIXES: .c .o + +var-class-local.c: + : Making ${.TARGET} out of nothing. + +.c.o: + : Making ${.TARGET} from ${.IMPSRC}. + + # The local variables @F, @D, &2 !} VAR= ${:! echo 'this will be evaluated later' 1>&2 !} # Now force the variable to be evaluated. # This outputs the line to stderr. .if ${VAR} .endif # In a variable assignment, the variable name must consist of a single word. -# +# The following line therefore generates a parse error. VARIABLE NAME= variable value # But if the whitespace appears inside parentheses or braces, everything is # fine. # # XXX: This was not an intentional decision, as variable names typically # neither contain parentheses nor braces. This is only a side-effect from # the implementation of the parser, which cheats when parsing a variable # name. It only counts parentheses and braces instead of properly parsing # nested variable expressions such as VAR.${param}. # VAR(spaces in parentheses)= () VAR{spaces in braces}= {} # Be careful and use indirect variable names here, to prevent accidentally # accepting the test in case the parser just uses "VAR" as the variable name, # ignoring all the rest. # VARNAME_PAREN= VAR(spaces in parentheses) VARNAME_BRACES= VAR{spaces in braces} .if ${${VARNAME_PAREN}} != "()" . error .endif .if ${${VARNAME_BRACES}} != "{}" . error .endif # In safe mode, parsing would stop immediately after the "VARIABLE NAME=" # line, since any commands run after that are probably working with # unexpected variable values. # # Therefore, just output an info message. .info Parsing still continues until here. all: @:; Index: head/contrib/bmake/unit-tests/var-op-expand.exp =================================================================== --- head/contrib/bmake/unit-tests/var-op-expand.exp (revision 367862) +++ head/contrib/bmake/unit-tests/var-op-expand.exp (revision 367863) @@ -1 +1,10 @@ +Var_Parse: ${UNDEF} with VARE_WANTRES +Global:VAR_ASSIGN_ = undef value +Var_Parse: ${UNDEF} with VARE_WANTRES +Var_Parse: ${UNDEF} with VARE_WANTRES +Global:VAR_SUBST_${UNDEF} = +Var_Parse: ${UNDEF} with VARE_WANTRES +Global:VAR_SUBST_ = undef value +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 exit status 0 Index: head/contrib/bmake/unit-tests/var-op-expand.mk =================================================================== --- head/contrib/bmake/unit-tests/var-op-expand.mk (revision 367862) +++ head/contrib/bmake/unit-tests/var-op-expand.mk (revision 367863) @@ -1,9 +1,27 @@ -# $NetBSD: var-op-expand.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-op-expand.mk,v 1.4 2020/11/08 14:00:52 rillig Exp $ # # Tests for the := variable assignment operator, which expands its # right-hand side. # TODO: Implementation + +# XXX: edge case: When a variable name refers to an undefined variable, the +# behavior differs between the '=' and the ':=' assignment operators. +# This bug exists since var.c 1.42 from 2000-05-11. +# +# The '=' operator expands the undefined variable to an empty string, thus +# assigning to VAR_ASSIGN_. In the name of variables to be set, it should +# really be forbidden to refer to undefined variables. +# +# The ':=' operator expands the variable name twice. In one of these +# expansions, the undefined variable expression is preserved (controlled by +# preserveUndefined in VarAssign_EvalSubst), in the other expansion it expands +# to an empty string. This way, 2 variables are created using a single +# variable assignment. It's magic. :-/ +.MAKEFLAGS: -dv +VAR_ASSIGN_${UNDEF}= undef value +VAR_SUBST_${UNDEF}:= undef value +.MAKEFLAGS: -d0 all: @:; Index: head/contrib/bmake/unit-tests/var-op-shell.exp =================================================================== --- head/contrib/bmake/unit-tests/var-op-shell.exp (revision 367862) +++ head/contrib/bmake/unit-tests/var-op-shell.exp (revision 367863) @@ -1 +1,7 @@ +make: "var-op-shell.mk" line 28: warning: "echo "failed"; false" returned non-zero status +make: "var-op-shell.mk" line 34: warning: "false" returned non-zero status +make: "var-op-shell.mk" line 59: warning: "kill -14 $$" exited on a signal +/bin/no/such/command: not found +make: "var-op-shell.mk" line 65: warning: "/bin/no/such/command" returned non-zero status +stderr exit status 0 Index: head/contrib/bmake/unit-tests/var-op-shell.mk =================================================================== --- head/contrib/bmake/unit-tests/var-op-shell.mk (revision 367862) +++ head/contrib/bmake/unit-tests/var-op-shell.mk (revision 367863) @@ -1,9 +1,84 @@ -# $NetBSD: var-op-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-op-shell.mk,v 1.3 2020/11/09 20:39:46 rillig Exp $ # # Tests for the != variable assignment operator, which runs its right-hand # side through the shell. -# TODO: Implementation +# The variable OUTPUT gets the output from running the shell command. +OUTPUT!= echo "success"'ful' +.if ${OUTPUT} != "successful" +. error +.endif +# Since 2014-08-20, the output of the shell command may be empty. +# +# On 1996-05-29, when the '!=' assignment operator and Cmd_Exec were added, +# an empty output produced the error message "Couldn't read shell's output +# for \"%s\"". +# +# The error message is still there but reserved for technical errors. +# It may be possible to trigger the error message by killing the shell after +# reading part of its output. +OUTPUT!= true +.if ${OUTPUT} != "" +. error +.endif + +# The output of a shell command that failed is processed nevertheless. +# TODO: Make this an error in lint mode. +OUTPUT!= echo "failed"; false +.if ${OUTPUT} != "failed" +. error +.endif + +# A command with empty output may fail as well. +OUTPUT!= false +.if ${OUTPUT} != "" +. error +.endif + +# In the output of the command, each newline is replaced with a space. +# Except for the very last one, which is discarded. +OUTPUT!= echo "line 1"; echo "line 2" +.if ${OUTPUT} != "line 1 line 2" +. error +.endif + +# A failing command in the middle results in the exit status 0, which in the +# end means that the whole sequence of commands succeeded. +OUTPUT!= echo "before"; false; echo "after" +.if ${OUTPUT} != "before after" +. error +.endif + +# NB: The signal number must be numeric since some shells (which ones?) don't +# accept symbolic signal names. 14 is typically SIGALRM. +# +# XXX: The number of the signal is not mentioned in the warning since that +# would have been difficult to implement; currently the errfmt is a format +# string containing a single %s conversion. +OUTPUT!= kill -14 $$$$ +.if ${OUTPUT} != "" +. error +.endif + +# A nonexistent command produces a non-zero exit status. +OUTPUT!= /bin/no/such/command +.if ${OUTPUT} != "" +. error +.endif + +# The output from the shell's stderr is not captured, it just passes through. +OUTPUT!= echo "stdout"; echo "stderr" 1>&2 +.if ${OUTPUT} != "stdout" +. error +.endif + +# The 8 dollar signs end up as 4 dollar signs when expanded. The shell sees +# the command "echo '$$$$'". The 4 dollar signs are stored in OUTPUT, and +# when that variable is expanded, they expand to 2 dollar signs. +OUTPUT!= echo '$$$$$$$$' +.if ${OUTPUT} != "\$\$" +. error +.endif + all: - @:; Index: head/contrib/bmake/unit-tests/var-op-sunsh.mk =================================================================== --- head/contrib/bmake/unit-tests/var-op-sunsh.mk (revision 367862) +++ head/contrib/bmake/unit-tests/var-op-sunsh.mk (revision 367863) @@ -1,122 +1,124 @@ -# $NetBSD: var-op-sunsh.mk,v 1.5 2020/10/04 08:32:52 rillig Exp $ +# $NetBSD: var-op-sunsh.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :sh= variable assignment operator, which runs its right-hand # side through the shell. It is a seldom-used alternative to the != # assignment operator, adopted from Sun make. .MAKEFLAGS: -dL # Enable sane error messages # This is the idiomatic form of the Sun shell assignment operator. # The assignment operator is directly preceded by the ':sh'. VAR:sh= echo colon-sh .if ${VAR} != "colon-sh" . error .endif # It is also possible to have whitespace around the :sh assignment # operator modifier. VAR :sh = echo colon-sh-spaced .if ${VAR} != "colon-sh-spaced" . error .endif # Until 2020-10-04, the ':sh' could even be followed by other characters. # This was neither documented by NetBSD make nor by Solaris make and was # an implementation error. # # Since 2020-10-04, this is a normal variable assignment using the '=' # assignment operator. VAR:shell= echo colon-shell .if ${${:UVAR\:shell}} != "echo colon-shell" . error .endif # Several colons can syntactically appear in a variable name. # Until 2020-10-04, the last of them was interpreted as the ':sh' # assignment operator. # # Since 2020-10-04, the colons are part of the variable name. VAR:shoe:shore= echo two-colons .if ${${:UVAR\:shoe\:shore}} != "echo two-colons" . error .endif # Until 2020-10-04, the following expression was wrongly marked as # a parse error. This was because the parser for variable assignments # just looked for the previous ":sh", without taking any contextual # information into account. # # There are two different syntactical elements that look exactly the same: # The variable modifier ':sh' and the assignment operator modifier ':sh'. # Intuitively this variable name contains the variable modifier, but until # 2020-10-04, the parser regarded it as an assignment operator modifier, in # Parse_DoVar. VAR.${:Uecho 123:sh}= ok-123 .if ${VAR.123} != "ok-123" . error .endif # Same pattern here. Until 2020-10-04, the ':sh' inside the nested expression # was taken for the :sh assignment operator modifier, even though it was # escaped by a backslash. VAR.${:U echo\:shell}= ok-shell .if ${VAR.${:U echo\:shell}} != "ok-shell" . error .endif # Until 2020-10-04, the word 'shift' was also affected since it starts with # ':sh'. VAR.key:shift= Shift .if ${${:UVAR.key\:shift}} != "Shift" . error .endif # Just for fun: The code in Parse_IsVar allows for multiple appearances of # the ':sh' assignment operator modifier. Let's see what happens ... # # Well, the end result is correct but the way until there is rather # adventurous. This only works because the parser replaces each an every # whitespace character that is not nested with '\0' (see Parse_DoVar). # The variable name therefore ends before the first ':sh', and the last # ':sh' turns the assignment operator into the shell command evaluation. # Parse_DoVar completely trusts Parse_IsVar to properly verify the syntax. # # The ':sh' is the only word that may occur between the variable name and # the assignment operator at nesting level 0. All other words would lead # to a parse error since the left-hand side of an assignment must be # exactly one word. VAR :sh :sh :sh :sh= echo multiple .if ${VAR} != "multiple" . error .endif # The word ':sh' is not the only thing that can occur after a variable name. # Since the parser just counts braces and parentheses instead of properly # expanding nested expressions, the token ' :sh' can be used to add arbitrary # text between the variable name and the assignment operator, it just has to # be enclosed in braces or parentheses. VAR :sh(Put a comment here)= comment in parentheses .if ${VAR} != "comment in parentheses" . error .endif # The unintended comment can include multiple levels of nested braces and # parentheses, they don't even need to be balanced since they are only # counted by Parse_IsVar and ignored by Parse_DoVar. VAR :sh{Put}((((a}{comment}}}}{here}= comment in braces .if ${VAR} != "comment in braces" . error .endif # Syntactically, the ':sh' modifier can be combined with the '+=' assignment # operator. In such a case the ':sh' modifier is silently ignored. # # XXX: This combination should not be allowed at all. VAR= one VAR :sh += echo two .if ${VAR} != "one echo two" . error ${VAR} .endif + +# TODO: test VAR:sh!=command all: @:; Index: head/contrib/bmake/unit-tests/vardebug.exp =================================================================== --- head/contrib/bmake/unit-tests/vardebug.exp (revision 367862) +++ head/contrib/bmake/unit-tests/vardebug.exp (revision 367863) @@ -1,86 +1,86 @@ Global:delete FROM_CMDLINE (not found) Command:FROM_CMDLINE = Global:.MAKEOVERRIDES = FROM_CMDLINE Global:VAR = added Global:VAR = overwritten Global:delete VAR Global:delete VAR (not found) Var_Parse: ${:U} with VARE_WANTRES Applying ${:U} to "" (VARE_WANTRES, none, VEF_UNDEF) Result of ${:U} is "" (VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Var_Set("${:U}", "empty name", ...) name expands to empty string - ignored Var_Parse: ${:U} with VARE_WANTRES Applying ${:U} to "" (VARE_WANTRES, none, VEF_UNDEF) Result of ${:U} is "" (VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Var_Append("${:U}", "empty name", ...) name expands to empty string - ignored Global:FROM_CMDLINE = overwritten ignored! Global:VAR = 1 Global:VAR = 1 2 Global:VAR = 1 2 3 Var_Parse: ${VAR:M[2]} with VARE_UNDEFERR|VARE_WANTRES Applying ${VAR:M...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Pattern[VAR] for [1 2 3] is [[2]] ModifyWords: split "1 2 3" into 3 words VarMatch [1] [[2]] VarMatch [2] [[2]] VarMatch [3] [[2]] Result of ${VAR:M[2]} is "2" (VARE_UNDEFERR|VARE_WANTRES, none, none) Var_Parse: ${VAR:N[2]} with VARE_UNDEFERR|VARE_WANTRES Applying ${VAR:N...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Pattern[VAR] for [1 2 3] is [[2]] ModifyWords: split "1 2 3" into 3 words Result of ${VAR:N[2]} is "1 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Var_Parse: ${VAR:S,2,two,} with VARE_UNDEFERR|VARE_WANTRES Applying ${VAR:S...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Modifier part: "2" Modifier part: "two" ModifyWords: split "1 2 3" into 3 words Result of ${VAR:S,2,two,} is "1 two 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Var_Parse: ${VAR:Q} with VARE_UNDEFERR|VARE_WANTRES Applying ${VAR:Q} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Result of ${VAR:Q} is "1\ 2\ 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Var_Parse: ${VAR:tu:tl:Q} with VARE_UNDEFERR|VARE_WANTRES Applying ${VAR:t...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Result of ${VAR:tu} is "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Applying ${VAR:t...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Result of ${VAR:tl} is "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Applying ${VAR:Q} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Result of ${VAR:Q} is "1\ 2\ 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Var_Parse: ${:Uvalue:${:UM*e}:Mvalu[e]} with VARE_UNDEFERR|VARE_WANTRES Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) Result of ${:Uvalue} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Var_Parse: ${:UM*e}:Mvalu[e]} with VARE_UNDEFERR|VARE_WANTRES Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) Result of ${:UM*e} is "M*e" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Indirect modifier "M*e" from "${:UM*e}" Applying ${:M...} to "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Pattern[] for [value] is [*e] ModifyWords: split "value" into 1 words VarMatch [value] [*e] Result of ${:M*e} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Applying ${:M...} to "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Pattern[] for [value] is [valu[e]] ModifyWords: split "value" into 1 words VarMatch [value] [valu[e]] Result of ${:Mvalu[e]} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Var_Parse: ${:UVAR} with VARE_WANTRES Applying ${:U...} to "" (VARE_WANTRES, none, VEF_UNDEF) Result of ${:UVAR} is "VAR" (VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Global:delete VAR Var_Parse: ${:Uvariable:unknown} with VARE_UNDEFERR|VARE_WANTRES Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) Result of ${:Uvariable} is "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Applying ${:u...} to "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) make: Unknown modifier 'u' Result of ${:unknown} is error (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) make: "vardebug.mk" line 44: Malformed conditional (${:Uvariable:unknown}) Var_Parse: ${UNDEFINED} with VARE_UNDEFERR|VARE_WANTRES make: "vardebug.mk" line 53: Malformed conditional (${UNDEFINED}) Global:delete .SHELL (not found) -Command:.SHELL = /bin/sh +Command:.SHELL = Command:.SHELL = overwritten ignored (read-only) Global:.MAKEFLAGS = -r -k -d v -d Global:.MAKEFLAGS = -r -k -d v -d 0 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/varmisc.mk =================================================================== --- head/contrib/bmake/unit-tests/varmisc.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmisc.mk (revision 367863) @@ -1,228 +1,228 @@ -# $Id: varmisc.mk,v 1.20 2020/10/26 17:43:57 sjg Exp $ -# $NetBSD: varmisc.mk,v 1.26 2020/10/24 08:50:17 rillig Exp $ +# $Id: varmisc.mk,v 1.21 2020/11/11 23:08:50 sjg Exp $ +# $NetBSD: varmisc.mk,v 1.28 2020/11/07 00:07:02 rillig Exp $ # # Miscellaneous variable tests. all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none \ strftime cmpv manok all: save-dollars all: export-appended all: parse-dynamic all: varerror-unclosed unmatched_var_paren: @echo ${foo::=foo-text} True= ${echo true >&2:L:sh}TRUE False= ${echo false >&2:L:sh}FALSE VSET= is set .undef UNDEF U_false: @echo :U skipped when var set @echo ${VSET:U${False}} D_false: @echo :D skipped if var undef @echo ${UNDEF:D${False}} U_true: @echo :U expanded when var undef @echo ${UNDEF:U${True}} D_true: @echo :D expanded when var set @echo ${VSET:D${True}} Q_lhs: @echo :? only lhs when value true @echo ${1:L:?${True}:${False}} Q_rhs: @echo :? only rhs when value false @echo ${0:L:?${True}:${False}} NQ_none: @echo do not evaluate or expand :? if discarding @echo ${VSET:U${1:L:?${True}:${False}}} April1= 1459494000 # slightly contorted syntax to use utc via variable strftime: @echo ${year=%Y month=%m day=%d:L:gmtime=1459494000} @echo date=${%Y%m%d:L:${gmtime=${April1}:L}} # big jumps to handle 3 digits per step M_cmpv.units= 1 1000 1000000 M_cmpv= S,., ,g:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh Version= 123.456.789 cmpv.only= target specific vars cmpv: @echo Version=${Version} == ${Version:${M_cmpv}} @echo Literal=3.4.5 == ${3.4.5:L:${M_cmpv}} @echo We have ${${.TARGET:T}.only} # catch misshandling of nested vars in .for loop MAN= MAN1= make.1 .for s in 1 2 . if defined(MAN$s) && !empty(MAN$s) MAN+= ${MAN$s} . endif .endfor manok: @echo MAN=${MAN} # This is an expanded variant of the above .for loop. # Between 2020-06-28 and 2020-07-02 this paragraph generated a wrong # error message "Variable VARNAME is recursive". # When evaluating the !empty expression, the ${:U1} was not expanded and # thus resulted in the seeming definition VARNAME=${VARNAME}, which is # obviously recursive. VARNAME= ${VARNAME${:U1}} .if defined(VARNAME${:U2}) && !empty(VARNAME${:U2}) .endif -# begin .MAKE.SAVE_DOLLARS; see Var_Set_with_flags and s2Boolean. +# begin .MAKE.SAVE_DOLLARS; see Var_SetWithFlags and ParseBoolean. SD_VALUES= 0 1 2 False True false true Yes No yes no On Off ON OFF on off SD_4_DOLLARS= $$$$ .for val in ${SD_VALUES} .MAKE.SAVE_DOLLARS:= ${val} # Must be := since a simple = has no effect. SD.${val}:= ${SD_4_DOLLARS} .endfor .MAKE.SAVE_DOLLARS:= yes save-dollars: .for val in ${SD_VALUES} @printf '%s: %-8s = %s\n' $@ ${val} ${SD.${val}:Q} .endfor # Appending to an undefined variable does not add a space in front. .undef APPENDED APPENDED+= value .if ${APPENDED} != "value" . error "${APPENDED}" .endif # Appending to an empty variable adds a space between the old value # and the additional value. APPENDED= # empty APPENDED+= value .if ${APPENDED} != " value" . error "${APPENDED}" .endif # Appending to parameterized variables works as well. PARAM= param VAR.${PARAM}= 1 VAR.${PARAM}+= 2 .if ${VAR.param} != "1 2" . error "${VAR.param}" .endif # The variable name can contain arbitrary characters. # If the expanded variable name ends in a +, this still does not influence # the parser. The assignment operator is still a simple assignment. # Therefore, there is no need to add a space between the variable name # and the assignment operator. PARAM= + VAR.${PARAM}= 1 VAR.${PARAM}+= 2 .if ${VAR.+} != "1 2" . error "${VAR.+}" .endif .for param in + ! ? VAR.${param}= ${param} .endfor .if ${VAR.+} != "+" || ${VAR.!} != "!" || ${VAR.?} != "?" . error "${VAR.+}" "${VAR.!}" "${VAR.?}" .endif # Appending to a variable from the environment creates a copy of that variable # in the global context. # The appended value is not exported automatically. # When a variable is exported, the exported value is taken at the time of the # .export directive. Later changes to the variable have no effect. .export FROM_ENV_BEFORE FROM_ENV+= mk FROM_ENV_BEFORE+= mk FROM_ENV_AFTER+= mk .export FROM_ENV_AFTER export-appended: @echo $@: "$$FROM_ENV" @echo $@: "$$FROM_ENV_BEFORE" @echo $@: "$$FROM_ENV_AFTER" # begin parse-dynamic # # Demonstrate that the target-specific variables are not evaluated in # the global context. They are preserved until there is a local context # in which resolving them makes sense. # There are different code paths for short names ... ${:U>}= before GS_TARGET:= $@ GS_MEMBER:= $% GS_PREFIX:= $* GS_ARCHIVE:= $! GS_ALLSRC:= $> ${:U>}= after # ... and for braced short names ... GB_TARGET:= ${@} GB_MEMBER:= ${%} GB_PREFIX:= ${*} GB_ARCHIVE:= ${!} GB_ALLSRC:= ${>} # ... and for long names. GL_TARGET:= ${.TARGET} GL_MEMBER:= ${.MEMBER} GL_PREFIX:= ${.PREFIX} GL_ARCHIVE:= ${.ARCHIVE} GL_ALLSRC:= ${.ALLSRC} parse-dynamic: @echo $@: ${GS_TARGET} ${GS_MEMBER} ${GS_PREFIX} ${GS_ARCHIVE} ${GS_ALLSRC} @echo $@: ${GB_TARGET} ${GB_MEMBER} ${GB_PREFIX} ${GB_ARCHIVE} ${GB_ALLSRC} @echo $@: ${GL_TARGET} ${GL_MEMBER} ${GL_PREFIX} ${GL_ARCHIVE} ${GL_ALLSRC} # Since 2020-07-28, make complains about unclosed variables. # Before that, it had complained about unclosed variables only when # parsing the modifiers, but not when parsing the variable name. UNCLOSED_INDIR_1= ${UNCLOSED_ORIG UNCLOSED_INDIR_2= ${UNCLOSED_INDIR_1} FLAGS= one two FLAGS+= ${FLAGS.${.ALLSRC:M*.c:T:u}} FLAGS.target2.c= three four target1.c: target2.c: all: target1-flags target2-flags target1-flags: target1.c @echo $@: we have: ${FLAGS} target2-flags: target2.c @echo $@: we have: ${FLAGS} varerror-unclosed: @echo $@:begin @echo $( @echo $(UNCLOSED @echo ${UNCLOSED @echo ${UNCLOSED:M${PATTERN @echo ${UNCLOSED.${param @echo $ .for i in 1 2 3 @echo ${UNCLOSED.${i} .endfor @echo ${UNCLOSED_INDIR_2} @echo $@:end Index: head/contrib/bmake/unit-tests/varmod-defined.exp =================================================================== --- head/contrib/bmake/unit-tests/varmod-defined.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-defined.exp (revision 367863) @@ -1 +1,23 @@ +Global:8_DOLLARS = $$$$$$$$ +Global:VAR = +Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_DOLLAR +Global:VAR = $$$$$$$$ +Var_Parse: ${VAR:D${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR +Applying ${VAR:D...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none) +Var_Parse: ${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR +Result of ${VAR:D${8_DOLLARS}} is "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none) +Global:VAR = $$$$$$$$ +Var_Parse: ${VAR:@var@${8_DOLLARS}@} with VARE_WANTRES|VARE_KEEP_DOLLAR +Applying ${VAR:@...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none) +Modifier part: "var" +Modifier part: "${8_DOLLARS}" +ModifyWords: split "$$$$$$$$" into 1 words +Global:var = $$$$$$$$ +Var_Parse: ${8_DOLLARS} with VARE_WANTRES +ModifyWord_Loop: in "$$$$$$$$", replace "var" with "${8_DOLLARS}" to "$$$$" +Global:delete var +Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none) +Global:VAR = $$$$ +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 exit status 0 Index: head/contrib/bmake/unit-tests/varmod-defined.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-defined.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-defined.mk (revision 367863) @@ -1,89 +1,105 @@ -# $NetBSD: varmod-defined.mk,v 1.7 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-defined.mk,v 1.9 2020/11/12 00:40:55 rillig Exp $ # # Tests for the :D variable modifier, which returns the given string # if the variable is defined. It is closely related to the :U modifier. +.MAKE.SAVE_DOLLARS= yes + DEF= defined .undef UNDEF # Since DEF is defined, the value of the expression is "value", not # "defined". # .if ${DEF:Dvalue} != "value" . error .endif # Since UNDEF is not defined, the "value" is ignored. Instead of leaving the # expression undefined, it is set to "", exactly to allow the expression to # be used in .if conditions. In this place, other undefined expressions # would generate an error message. # XXX: Ideally the error message would be "undefined variable", but as of # 2020-08-25 it is "Malformed conditional". # .if ${UNDEF:Dvalue} != "" . error .endif # The modifier text may contain plain text as well as expressions. # .if ${DEF:D<${DEF}>} != "" . error .endif # Special characters that would be interpreted differently can be escaped. # These are '}' (the closing character of the expression), ':', '$' and '\'. # Any other backslash sequences are preserved. # # The escaping rules for string literals in conditions are completely # different though. There, any character may be escaped using a backslash. # .if ${DEF:D \} \: \$ \\ \) \n } != " } : \$ \\ \\) \\n " . error .endif # Like in several other places in variable expressions, when # ApplyModifier_Defined calls Var_Parse, double dollars lead to a parse # error that is silently ignored. This makes all dollar signs disappear, # except for the last, which is a well-formed variable expression. # .if ${DEF:D$$$$$${DEF}} != "defined" . error .endif # Any other text is written without any further escaping. In contrast # to the :M modifier, parentheses and braces do not need to be nested. # Instead, the :D modifier is implemented sanely by parsing nested # expressions as such, without trying any shortcuts. See ApplyModifier_Match # for an inferior variant. # .if ${DEF:D!&((((} != "!&((((" . error .endif # The :D modifier is often used in combination with the :U modifier. # It does not matter in which order the :D and :U modifiers appear. .if ${UNDEF:Dyes:Uno} != no . error .endif .if ${UNDEF:Uno:Dyes} != no . error .endif .if ${DEF:Dyes:Uno} != yes . error .endif .if ${DEF:Uno:Dyes} != yes . error .endif # Since the variable with the empty name is never defined, the :D modifier # can be used to add comments in the middle of an expression. That # expression always evaluates to an empty string. .if ${:D This is a comment. } != "" . error .endif # TODO: Add more tests for parsing the plain text part, to cover each branch # of ApplyModifier_Defined. + +# The :D and :U modifiers behave differently from the :@var@ modifier in +# that they preserve dollars in a ':=' assignment. This is because +# ApplyModifier_Defined passes the eflags unmodified to Var_Parse, unlike +# ApplyModifier_Loop, which uses ParseModifierPart, which in turn removes +# VARE_KEEP_DOLLAR from eflags. +# +# XXX: This inconsistency is documented nowhere. +.MAKEFLAGS: -dv +8_DOLLARS= $$$$$$$$ +VAR:= ${8_DOLLARS} +VAR:= ${VAR:D${8_DOLLARS}} +VAR:= ${VAR:@var@${8_DOLLARS}@} +.MAKEFLAGS: -d0 all: @:; Index: head/contrib/bmake/unit-tests/varmod-exclam-shell.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-exclam-shell.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-exclam-shell.mk (revision 367863) @@ -1,28 +1,37 @@ -# $NetBSD: varmod-exclam-shell.mk,v 1.3 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-exclam-shell.mk,v 1.4 2020/11/03 18:42:33 rillig Exp $ # -# Tests for the :!cmd! variable modifier. +# Tests for the :!cmd! variable modifier, which evaluates the modifier +# argument, independent of the value or the name of the original variable. .if ${:!echo hello | tr 'l' 'l'!} != "hello" -. warning unexpected +. error .endif # The output is truncated at the first null byte. # Cmd_Exec returns only a string pointer without length information. +# Truncating the output is not necessarily intended but may also be a side +# effect from the implementation. Having null bytes in the output of a +# shell command is so unusual that it doesn't matter in practice. .if ${:!echo hello | tr 'l' '\0'!} != "he" -. warning unexpected +. error .endif +# The newline at the end of the output is stripped. .if ${:!echo!} != "" -. warning A newline at the end of the output must be stripped. +. error .endif +# Only the final newline of the output is stripped. All other newlines are +# converted to spaces. .if ${:!echo;echo!} != " " -. warning Only a single newline at the end of the output is stripped. +. error .endif +# Each newline in the output is converted to a space, except for the newline +# at the end of the output, which is stripped. .if ${:!echo;echo;echo;echo!} != " " -. warning Other newlines in the output are converted to spaces. +. error .endif all: @:; Index: head/contrib/bmake/unit-tests/varmod-ifelse.exp =================================================================== --- head/contrib/bmake/unit-tests/varmod-ifelse.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-ifelse.exp (revision 367863) @@ -1,8 +1,16 @@ make: Bad conditional expression `variable expression == "literal"' in variable expression == "literal"?bad:bad make: "varmod-ifelse.mk" line 27: Malformed conditional (${${:Uvariable expression} == "literal":?bad:bad}) make: Bad conditional expression ` == ""' in == ""?bad-assign:bad-assign make: Bad conditional expression ` == ""' in == ""?bad-cond:bad-cond make: "varmod-ifelse.mk" line 44: Malformed conditional (${${UNDEF} == "":?bad-cond:bad-cond}) +make: Bad conditional expression `1 == == 2' in 1 == == 2?yes:no +make: "varmod-ifelse.mk" line 66: Malformed conditional (${1 == == 2:?yes:no} != "") +CondParser_Eval: "${1 == == 2:?yes:no}" != "" +CondParser_Eval: 1 == == 2 +lhs = 1.000000, rhs = 0.000000, op = == +make: Bad conditional expression `1 == == 2' in 1 == == 2?yes:no +lhs = "", rhs = "", op = != +make: "varmod-ifelse.mk" line 92: warning: Oops, the parse error should have been propagated. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/varmod-ifelse.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-ifelse.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-ifelse.mk (revision 367863) @@ -1,61 +1,97 @@ -# $NetBSD: varmod-ifelse.mk,v 1.5 2020/10/23 14:24:51 rillig Exp $ +# $NetBSD: varmod-ifelse.mk,v 1.6 2020/11/12 00:29:55 rillig Exp $ # # Tests for the ${cond:?then:else} variable modifier, which evaluates either # the then-expression or the else-expression, depending on the condition. # # The modifier was added on 1998-04-01. # # Until 2015-10-11, the modifier always evaluated both the "then" and the # "else" expressions. # TODO: Implementation # The variable name of the expression is expanded and then taken as the # condition. In this case it becomes: # # variable expression == "variable expression" # # This confuses the parser, which expects an operator instead of the bare # word "expression". If the name were expanded lazily, everything would be # fine since the condition would be: # # ${:Uvariable expression} == "literal" # # Evaluating the variable name lazily would require additional code in # Var_Parse and ParseVarname, it would be more useful and predictable # though. .if ${${:Uvariable expression} == "literal":?bad:bad} . error .else . error .endif # In a variable assignment, undefined variables are not an error. # Because of the early expansion, the whole condition evaluates to # ' == ""' though, which cannot be parsed because the left-hand side looks # empty. COND:= ${${UNDEF} == "":?bad-assign:bad-assign} # In a condition, undefined variables generate a "Malformed conditional" # error. That error message is wrong though. In lint mode, the correct # "Undefined variable" error message is generated. # The difference to the ':=' variable assignment is the additional # "Malformed conditional" error message. .if ${${UNDEF} == "":?bad-cond:bad-cond} . error .else . error .endif # When the :? is parsed, it is greedy. The else branch spans all the # text, up until the closing character '}', even if the text looks like # another modifier. .if ${1:?then:else:Q} != "then" . error .endif .if ${0:?then:else:Q} != "else:Q" . error .endif + +# This line generates 2 error messages. The first comes from evaluating the +# malformed conditional "1 == == 2", which is reported as "Bad conditional +# expression" by ApplyModifier_IfElse. The variable expression containing that +# conditional therefore returns a parse error from Var_Parse, and this parse +# error propagates to CondEvalExpression, where the "Malformed conditional" +# comes from. +.if ${1 == == 2:?yes:no} != "" +. error +.else +. error +.endif + +# If the "Bad conditional expression" appears in a quoted string literal, the +# error message "Malformed conditional" is not printed, leaving only the "Bad +# conditional expression". +# +# XXX: The left-hand side is enclosed in quotes. This results in Var_Parse +# being called without VARE_UNDEFERR being set. When ApplyModifier_IfElse +# returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the +# value of the variable expression is still undefined. CondParser_String is +# then supposed to do proper error handling, but since varUndefined is local +# to var.c, it cannot distinguish this return value from an ordinary empty +# string. The left-hand side of the comparison is therefore just an empty +# string, which is obviously equal to the empty string on the right-hand side. +# +# XXX: The debug log for -dc shows a comparison between 1.0 and 0.0. The +# condition should be detected as being malformed before any comparison is +# done since there is no well-formed comparison in the condition at all. +.MAKEFLAGS: -dc +.if "${1 == == 2:?yes:no}" != "" +. error +.else +. warning Oops, the parse error should have been propagated. +.endif +.MAKEFLAGS: -d0 all: @:; Index: head/contrib/bmake/unit-tests/varmod-loop.exp =================================================================== --- head/contrib/bmake/unit-tests/varmod-loop.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-loop.exp (revision 367863) @@ -1,17 +1,25 @@ +ParseReadLine (117): 'USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$' +CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" +lhs = "$$$$ $$$$ $$$$", rhs = "$$$$ $$$$ $$$$", op = != +ParseReadLine (122): 'SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}' +CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" +lhs = "$$ $$$$ $$$$", rhs = "$$ $$$$ $$$$", op = != +ParseReadLine (147): '.MAKEFLAGS: -d0' +ParseDoDependency(.MAKEFLAGS: -d0) :+one+ +two+ +three+: :x1y x2y x3y: :x1y x2y x3y: :mod-loop-varname: :x1y x2y x3y: :: :x1y x2y x3y: empty: :xy xy xy: mod-loop-resolve:w1d2d3w w2i3w w1i2d3 2i${RES3}w w1d2d3 2i${RES3} 1i${RES2}w: mod-loop-varname-dollar:(1) (2) (3). mod-loop-varname-dollar:() () (). mod-loop-varname-dollar:() () (). mod-loop-dollar:1: mod-loop-dollar:${word}$: mod-loop-dollar:$3$: mod-loop-dollar:$${word}$$: mod-loop-dollar:$$5$$: mod-loop-dollar:$$${word}$$$: exit status 0 Index: head/contrib/bmake/unit-tests/varmod-loop.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-loop.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-loop.mk (revision 367863) @@ -1,102 +1,147 @@ -# $NetBSD: varmod-loop.mk,v 1.5 2020/10/31 12:34:03 rillig Exp $ +# $NetBSD: varmod-loop.mk,v 1.8 2020/11/12 00:40:55 rillig Exp $ # # Tests for the :@var@...${var}...@ variable modifier. +.MAKE.SAVE_DOLLARS= yes + all: mod-loop-varname all: mod-loop-resolve all: mod-loop-varname-dollar all: mod-loop-dollar # In the :@ modifier, the name of the loop variable can even be generated # dynamically. There's no practical use-case for this, and hopefully nobody # will ever depend on this, but technically it's possible. # Therefore, in -dL mode, this is forbidden, see lint.mk. mod-loop-varname: @echo :${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@:Q}: # ":::" is a very creative variable name, unlikely in practice. # The expression ${\:\:\:} would not work since backslashes can only # be escaped in the modifiers, but not in the variable name. @echo :${:U1 2 3:@:::@x${${:U\:\:\:}}y@}: # "@@" is another creative variable name. @echo :${:U1 2 3:@\@\@@x${@@}y@}: # Even "@" works as a variable name since the variable is installed # in the "current" scope, which in this case is the one from the # target. @echo :$@: :${:U1 2 3:@\@@x${@}y@}: :$@: # In extreme cases, even the backslash can be used as variable name. # It needs to be doubled though. @echo :${:U1 2 3:@\\@x${${:Ux:S,x,\\,}}y@}: # The variable name can technically be empty, and in this situation # the variable value cannot be accessed since the empty variable is # protected to always return an empty string. @echo empty: :${:U1 2 3:@@x${}y@}: # The :@ modifier resolves the variables a little more often than expected. # In particular, it resolves _all_ variables from the context, and not only # the loop variable (in this case v). # # The d means direct reference, the i means indirect reference. RESOLVE= ${RES1} $${RES1} RES1= 1d${RES2} 1i$${RES2} RES2= 2d${RES3} 2i$${RES3} RES3= 3 mod-loop-resolve: @echo $@:${RESOLVE:@v@w${v}w@:Q}: # Until 2020-07-20, the variable name of the :@ modifier could end with one # or two dollar signs, which were silently ignored. # There's no point in allowing a dollar sign in that position. mod-loop-varname-dollar: @echo $@:${1 2 3:L:@v$@($v)@:Q}. @echo $@:${1 2 3:L:@v$$@($v)@:Q}. @echo $@:${1 2 3:L:@v$$$@($v)@:Q}. -# Demonstrate that it is possible to generate dollar characters using the +# Demonstrate that it is possible to generate dollar signs using the # :@ modifier. # # These are edge cases that could have resulted in a parse error as well # since the $@ at the end could have been interpreted as a variable, which # would mean a missing closing @ delimiter. mod-loop-dollar: @echo $@:${:U1:@word@${word}$@:Q}: @echo $@:${:U2:@word@$${word}$$@:Q}: @echo $@:${:U3:@word@$$${word}$$$@:Q}: @echo $@:${:U4:@word@$$$${word}$$$$@:Q}: @echo $@:${:U5:@word@$$$$${word}$$$$$@:Q}: @echo $@:${:U6:@word@$$$$$${word}$$$$$$@:Q}: # It may happen that there are nested :@ modifiers that use the same name for # for the loop variable. These modifiers influence each other. # # As of 2020-10-18, the :@ modifier is implemented by actually setting a # variable in the context of the expression and deleting it again after the # loop. This is different from the .for loops, which substitute the variable # expression with ${:Uvalue}, leading to different unwanted side effects. # # To make the behavior more predictable, the :@ modifier should restore the # loop variable to the value it had before the loop. This would result in # the string "1a b c1 2a b c2 3a b c3", making the two loops independent. .if ${:U1 2 3:@i@$i${:Ua b c:@i@$i@}${i:Uu}@} != "1a b cu 2a b cu 3a b cu" . error .endif # During the loop, the variable is actually defined and nonempty. # If the loop were implemented in the same way as the .for loop, the variable # would be neither defined nor nonempty since all expressions of the form # ${var} would have been replaced with ${:Uword} before evaluating them. .if defined(var) . error .endif .if ${:Uword:@var@${defined(var):?def:undef} ${empty(var):?empty:nonempty}@} \ != "def nonempty" . error .endif .if defined(var) . error .endif + +# Assignment using the ':=' operator, combined with the :@var@ modifier +# +8_DOLLARS= $$$$$$$$ +# This string literal is written with 8 dollars, and this is saved as the +# variable value. But as soon as this value is evaluated, it goes through +# Var_Subst, which replaces each '$$' with a single '$'. This could be +# prevented by VARE_KEEP_DOLLAR, but that flag is usually removed before +# expanding subexpressions. See ApplyModifier_Loop and ParseModifierPart +# for examples. +# +.MAKEFLAGS: -dcp +USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ +.if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" +. error +.endif +# +SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} +# The ':=' assignment operator evaluates the variable value using the flag +# VARE_KEEP_DOLLAR, which means that some dollar signs are preserved, but not +# all. The dollar signs in the top-level expression and in the indirect +# ${8_DOLLARS} are preserved. +# +# The variable modifier :@var@ does not preserve the dollar signs though, no +# matter in which context it is evaluated. What happens in detail is: +# First, the modifier part "${8_DOLLARS}" is parsed without expanding it. +# Next, each word of the value is expanded on its own, and at this moment +# in ApplyModifier_Loop, the VARE_KEEP_DOLLAR flag is not passed down to +# ModifyWords, resulting in "$$$$" for the first word of USE_8_DOLLARS. +# +# The remaining words of USE_8_DOLLARS are not affected by any variable +# modifier and are thus expanded with the flag VARE_KEEP_DOLLAR in action. +# The variable SUBST_CONTAINING_LOOP therefore gets assigned the raw value +# "$$$$ $$$$$$$$ $$$$$$$$". +# +# The variable expression in the condition then expands this raw stored value +# once, resulting in "$$ $$$$ $$$$". The effects from VARE_KEEP_DOLLAR no +# longer take place since they had only been active during the evaluation of +# the variable assignment. +.if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" +. error +.endif +.MAKEFLAGS: -d0 Index: head/contrib/bmake/unit-tests/varmod-match.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-match.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-match.mk (revision 367863) @@ -1,55 +1,60 @@ -# $NetBSD: varmod-match.mk,v 1.5 2020/09/13 05:36:26 rillig Exp $ +# $NetBSD: varmod-match.mk,v 1.6 2020/11/15 18:33:41 rillig Exp $ # # Tests for the :M variable modifier, which filters words that match the # given pattern. # # See ApplyModifier_Match and ModifyWord_Match for the implementation. .MAKEFLAGS: -dc NUMBERS= One Two Three Four five six seven # Only keep words that start with an uppercase letter. .if ${NUMBERS:M[A-Z]*} != "One Two Three Four" . error .endif # Only keep words that start with a character other than an uppercase letter. .if ${NUMBERS:M[^A-Z]*} != "five six seven" . error .endif # Only keep words that don't start with s and at the same time end with # either of [ex]. # # This test case ensures that the negation from the first character class # does not propagate to the second character class. .if ${NUMBERS:M[^s]*[ex]} != "One Three five" . error .endif # Before 2020-06-13, this expression took quite a long time in Str_Match, # calling itself 601080390 times for 16 asterisks. .if ${:U****************:M****************b} .endif # To match a dollar sign in a word, double it. # # This is different from the :S and :C variable modifiers, where a '$' # has to be escaped as '\$'. .if ${:Ua \$ sign:M*$$*} != "\$" . error .endif # In the :M modifier, '\$' does not escape a dollar. Instead it is # interpreted as a backslash followed by whatever expression the # '$' starts. # # This differs from the :S, :C and several other variable modifiers. ${:U*}= asterisk .if ${:Ua \$ sign any-asterisk:M*\$*} != "any-asterisk" . error .endif + +# TODO: ${VAR:M(((}}}} +# TODO: ${VAR:M{{{)))} +# TODO: ${VAR:M${UNBALANCED}} +# TODO: ${VAR:M${:U(((\}\}\}}} all: @:; Index: head/contrib/bmake/unit-tests/varmod-order-shuffle.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-order-shuffle.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-order-shuffle.mk (revision 367863) @@ -1,41 +1,43 @@ -# $NetBSD: varmod-order-shuffle.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-order-shuffle.mk,v 1.6 2020/11/09 20:16:33 rillig Exp $ # # Tests for the :Ox variable modifier, which returns the words of the # variable, shuffled. +# +# The variable modifier :Ox is available since 2005-06-01. # # As of 2020-08-16, make uses random(3) seeded by the current time in seconds. # This makes the random numbers completely predictable since there is no other # part of make that uses random numbers. # # Tags: probabilistic NUMBERS= one two three four five six seven eight nine ten # 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 each of the # 3 :Ox tests will incorrectly fail with probability 2.756E-7, which # lets the whole test fail once in 1.209.600 runs, on average. # Create two shuffles using the := assignment operator. shuffled1:= ${NUMBERS:Ox} shuffled2:= ${NUMBERS:Ox} .if ${shuffled1} == ${shuffled2} . error ${shuffled1} == ${shuffled2} .endif # Sorting the list before shuffling it has no effect. shuffled1:= ${NUMBERS:O:Ox} shuffled2:= ${NUMBERS:O:Ox} .if ${shuffled1} == ${shuffled2} . error ${shuffled1} == ${shuffled2} .endif # Sorting after shuffling must produce the original numbers. sorted:= ${NUMBERS:Ox:O} .if ${sorted} != ${NUMBERS:O} . error ${sorted} != ${NUMBERS:O} .endif all: @:; Index: head/contrib/bmake/unit-tests/varmod-shell.exp =================================================================== --- head/contrib/bmake/unit-tests/varmod-shell.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-shell.exp (revision 367863) @@ -1 +1,3 @@ +make: "echo word; false" returned non-zero status +make: "echo word; false" returned non-zero status exit status 0 Index: head/contrib/bmake/unit-tests/varmod-shell.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-shell.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-shell.mk (revision 367863) @@ -1,9 +1,35 @@ -# $NetBSD: varmod-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varmod-shell.mk,v 1.5 2020/11/17 20:11:02 rillig Exp $ # # Tests for the :sh variable modifier, which runs the shell command # given by the variable value and returns its output. +# +# This modifier has been added on 2000-04-29. +# +# See also: +# ApplyModifier_ShellCommand # TODO: Implementation + +# The command to be run is enclosed between exclamation marks. +# The previous value of the expression is irrelevant for this modifier. +# The :!cmd! modifier turns an undefined expression into a defined one. +.if ${:!echo word!} != "word" +. error +.endif + +# If the command exits with non-zero, an error message is printed. +# XXX: Processing continues as usual though. +# +# Between 2000-04-29 and 2020-11-17, the error message mentioned the previous +# value of the expression (which is usually an empty string) instead of the +# command that was executed. It's strange that such a simple bug could +# survive such a long time. +.if ${:!echo word; false!} != "word" +. error +.endif +.if ${:Uprevious value:!echo word; false!} != "word" +. error +.endif all: @:; Index: head/contrib/bmake/unit-tests/varmod-subst.exp =================================================================== --- head/contrib/bmake/unit-tests/varmod-subst.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-subst.exp (revision 367863) @@ -1,51 +1,62 @@ mod-subst: :a b b c: :a b b c: : b c: :a c: :x__ 3 x__ 3: 12345 mod-subst-delimiter: 1 two 3 horizontal tabulator 1 two 3 space 1 two 3 exclamation mark -1 two 3 double quotes -1 two 3 hash -1 two 3 dollar -1 two 3 percent +1 two 3 quotation mark +1 two 3 number sign +1 two 3 dollar sign +1 two 3 percent sign +1 two 3 ampersand 1 two 3 apostrophe -1 two 3 opening parenthesis -1 two 3 closing parenthesis +1 two 3 left parenthesis +1 two 3 right parenthesis +1 two 3 asterisk +1 two 3 plus sign +1 two 3 comma +1 two 3 hyphen-minus +1 two 3 full stop +1 two 3 solidus 1 two 3 digit 1 two 3 colon -1 two 3 less than sign -1 two 3 equal sign -1 two 3 greater than sign +1 two 3 semicolon +1 two 3 less-than sign +1 two 3 equals sign +1 two 3 greater-than sign 1 two 3 question mark -1 two 3 at -1 two 3 letter -1 two 3 opening bracket -1 two 3 backslash -1 two 3 closing bracket -1 two 3 caret -1 two 3 opening brace +1 two 3 commercial at +1 two 3 capital letter +1 two 3 left square bracket +1 two 3 reverse solidus +1 two 3 right square bracket +1 two 3 circumflex accent +1 two 3 low line +1 two 3 grave accent +1 two 3 small letter +1 two 3 left curly bracket 1 two 3 vertical line -1 two 3 closing brace +1 two 3 right curly bracket 1 two 3 tilde mod-subst-chain: A B c. make: Unknown modifier 'i' . mod-subst-dollar:$1: mod-subst-dollar:$2: mod-subst-dollar:$3: mod-subst-dollar:$4: mod-subst-dollar:$5: mod-subst-dollar:$6: mod-subst-dollar:$7: mod-subst-dollar:$8: mod-subst-dollar:$40: mod-subst-dollar:U8: mod-subst-dollar:$$$$: mod-subst-dollar:$$$good3 exit status 0 Index: head/contrib/bmake/unit-tests/varmod-subst.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-subst.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-subst.mk (revision 367863) @@ -1,153 +1,181 @@ -# $NetBSD: varmod-subst.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-subst.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :S,from,to, variable modifier. all: mod-subst all: mod-subst-delimiter all: mod-subst-chain all: mod-subst-dollar WORDS= sequences of letters + .if ${WORDS:S,,,} != ${WORDS} . warning The empty pattern matches something. .endif + .if ${WORDS:S,e,*,1} != "s*quences of letters" . warning The :S modifier flag '1' is not applied exactly once. .endif + .if ${WORDS:S,f,*,1} != "sequences o* letters" . warning The :S modifier flag '1' is only applied to the first word,\ not to the first occurrence. .endif + .if ${WORDS:S,e,*,} != "s*quences of l*tters" . warning The :S modifier does not replace every first match per word. .endif + .if ${WORDS:S,e,*,g} != "s*qu*nc*s of l*tt*rs" . warning The :S modifier flag 'g' does not replace every occurrence. .endif + .if ${WORDS:S,^sequ,occurr,} != "occurrences of letters" . warning The :S modifier fails for a short match anchored at the start. .endif + .if ${WORDS:S,^of,with,} != "sequences with letters" . warning The :S modifier fails for an exact match anchored at the start. .endif + .if ${WORDS:S,^office,does not match,} != ${WORDS} . warning The :S modifier matches a too long pattern anchored at the start. .endif + .if ${WORDS:S,f$,r,} != "sequences or letters" . warning The :S modifier fails for a short match anchored at the end. .endif + .if ${WORDS:S,s$,,} != "sequence of letter" . warning The :S modifier fails to replace one occurrence per word. .endif + .if ${WORDS:S,of$,,} != "sequences letters" . warning The :S modifier fails for an exact match anchored at the end. .endif + .if ${WORDS:S,eof$,,} != ${WORDS} . warning The :S modifier matches a too long pattern anchored at the end. .endif + .if ${WORDS:S,^of$,,} != "sequences letters" . warning The :S modifier does not match a word anchored at both ends. .endif + .if ${WORDS:S,^o$,,} != ${WORDS} . warning The :S modifier matches a prefix anchored at both ends. .endif + .if ${WORDS:S,^f$,,} != ${WORDS} . warning The :S modifier matches a suffix anchored at both ends. .endif + .if ${WORDS:S,^eof$,,} != ${WORDS} . warning The :S modifier matches a too long prefix anchored at both ends. .endif + .if ${WORDS:S,^office$,,} != ${WORDS} . warning The :S modifier matches a too long suffix anchored at both ends. .endif mod-subst: @echo $@: @echo :${:Ua b b c:S,a b,,:Q}: @echo :${:Ua b b c:S,a b,,1:Q}: @echo :${:Ua b b c:S,a b,,W:Q}: @echo :${:Ua b b c:S,b,,g:Q}: @echo :${:U1 2 3 1 2 3:S,1 2,___,Wg:S,_,x,:Q}: @echo ${:U12345:S,,sep,g:Q} # The :S and :C modifiers accept an arbitrary character as the delimiter, # including characters that are otherwise used as escape characters or # interpreted in a special way. This can be used to confuse humans. mod-subst-delimiter: @echo $@: @echo ${:U1 2 3:S 2 two :Q} horizontal tabulator @echo ${:U1 2 3:S 2 two :Q} space @echo ${:U1 2 3:S!2!two!:Q} exclamation mark - @echo ${:U1 2 3:S"2"two":Q} double quotes + @echo ${:U1 2 3:S"2"two":Q} quotation mark # In shell command lines, the hash does not need to be escaped. # It needs to be escaped in variable assignment lines though. - @echo ${:U1 2 3:S#2#two#:Q} hash - @echo ${:U1 2 3:S$2$two$:Q} dollar - @echo ${:U1 2 3:S%2%two%:Q} percent + @echo ${:U1 2 3:S#2#two#:Q} number sign + @echo ${:U1 2 3:S$2$two$:Q} dollar sign + @echo ${:U1 2 3:S%2%two%:Q} percent sign + @echo ${:U1 2 3:S&2&two&:Q} ampersand @echo ${:U1 2 3:S'2'two':Q} apostrophe - @echo ${:U1 2 3:S(2(two(:Q} opening parenthesis - @echo ${:U1 2 3:S)2)two):Q} closing parenthesis + @echo ${:U1 2 3:S(2(two(:Q} left parenthesis + @echo ${:U1 2 3:S)2)two):Q} right parenthesis + @echo ${:U1 2 3:S*2*two*:Q} asterisk + @echo ${:U1 2 3:S+2+two+:Q} plus sign + @echo ${:U1 2 3:S,2,two,:Q} comma + @echo ${:U1 2 3:S-2-two-:Q} hyphen-minus + @echo ${:U1 2 3:S.2.two.:Q} full stop + @echo ${:U1 2 3:S/2/two/:Q} solidus @echo ${:U1 2 3:S121two1:Q} digit @echo ${:U1 2 3:S:2:two::Q} colon - @echo ${:U1 2 3:S<22>two>:Q} greater than sign + @echo ${:U1 2 3:S;2;two;:Q} semicolon + @echo ${:U1 2 3:S<22>two>:Q} greater-than sign @echo ${:U1 2 3:S?2?two?:Q} question mark - @echo ${:U1 2 3:S@2@two@:Q} at - @echo ${:U1 2 3:Sa2atwoa:Q} letter - @echo ${:U1 2 3:S[2[two[:Q} opening bracket - @echo ${:U1 2 3:S\2\two\:Q} backslash - @echo ${:U1 2 3:S]2]two]:Q} closing bracket - @echo ${:U1 2 3:S^2^two^:Q} caret - @echo ${:U1 2 3:S{2{two{:Q} opening brace + @echo ${:U1 2 3:S@2@two@:Q} commercial at + @echo ${:U1 2 3:SA2AtwoA:Q} capital letter + @echo ${:U1 2 3:S[2[two[:Q} left square bracket + @echo ${:U1 2 3:S\2\two\:Q} reverse solidus + @echo ${:U1 2 3:S]2]two]:Q} right square bracket + @echo ${:U1 2 3:S^2^two^:Q} circumflex accent + @echo ${:U1 2 3:S_2_two_:Q} low line + @echo ${:U1 2 3:S`2`two`:Q} grave accent + @echo ${:U1 2 3:Sa2atwoa:Q} small letter + @echo ${:U1 2 3:S{2{two{:Q} left curly bracket @echo ${:U1 2 3:S|2|two|:Q} vertical line - @echo ${:U1 2 3:S}2}two}:Q} closing brace + @echo ${:U1 2 3:S}2}two}:Q} right curly bracket @echo ${:U1 2 3:S~2~two~:Q} tilde # The :S and :C modifiers can be chained without a separating ':'. # This is not documented in the manual page. # It works because ApplyModifier_Subst scans for the known modifiers g1W # and then just returns to ApplyModifiers. There, the colon is optionally # skipped (see the *st.next == ':' at the end of the loop). # # Most other modifiers cannot be chained since their parsers skip until # the next ':' or '}' or ')'. mod-subst-chain: @echo $@: @echo ${:Ua b c:S,a,A,S,b,B,}. # There is no 'i' modifier for the :S or :C modifiers. # The error message is "make: Unknown modifier 'i'", which is # kind of correct, although it is mixing the terms for variable # modifiers with the matching modifiers. @echo ${:Uvalue:S,a,x,i}. -# No matter how many dollar characters there are, they all get merged +# No matter how many dollar signs there are, they all get merged # into a single dollar by the :S modifier. # # As of 2020-08-09, this is because ParseModifierPart sees a '$' and # calls Var_Parse to expand the variable. In all other places, the "$$" # is handled outside of Var_Parse. Var_Parse therefore considers "$$" # one of the "really stupid names", skips the first dollar, and parsing # continues with the next character. This repeats for the other dollar # signs, except the one before the delimiter. That one is handled by # the code that optionally interprets the '$' as the end-anchor in the # first part of the :S modifier. That code doesn't call Var_Parse but # simply copies the dollar to the result. mod-subst-dollar: @echo $@:${:U1:S,^,$,:Q}: @echo $@:${:U2:S,^,$$,:Q}: @echo $@:${:U3:S,^,$$$,:Q}: @echo $@:${:U4:S,^,$$$$,:Q}: @echo $@:${:U5:S,^,$$$$$,:Q}: @echo $@:${:U6:S,^,$$$$$$,:Q}: @echo $@:${:U7:S,^,$$$$$$$,:Q}: @echo $@:${:U8:S,^,$$$$$$$$,:Q}: @echo $@:${:U40:S,^,$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$,:Q}: # This generates no dollar at all: @echo $@:${:UU8:S,^,${:U$$$$$$$$},:Q}: -# Here is an alternative way to generate dollar characters. +# Here is an alternative way to generate dollar signs. # It's unexpectedly complicated though. @echo $@:${:U:range=5:ts\x24:C,[0-9],,g:Q}: # In modifiers, dollars are escaped using the backslash, not using another # dollar sign. Therefore, creating a dollar sign is pretty simple: @echo $@:${:Ugood3:S,^,\$\$\$,:Q} Index: head/contrib/bmake/unit-tests/varmod-to-abs.exp =================================================================== --- head/contrib/bmake/unit-tests/varmod-to-abs.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-to-abs.exp (revision 367863) @@ -1 +1,5 @@ +make: "varmod-to-abs.mk" line 18: does-not-exist.c +make: "varmod-to-abs.mk" line 19: does-not-exist.c +cached_realpath: varmod-to-abs.mk -> varmod-to-abs.mk +make: "varmod-to-abs.mk" line 23: varmod-to-abs.mk exit status 0 Index: head/contrib/bmake/unit-tests/varmod-to-abs.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-to-abs.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-to-abs.mk (revision 367863) @@ -1,9 +1,28 @@ -# $NetBSD: varmod-to-abs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varmod-to-abs.mk,v 1.5 2020/11/15 05:48:17 rillig Exp $ # # Tests for the :tA variable modifier, which returns the absolute path for # each of the words in the variable value. # TODO: Implementation + +# Between 2016-06-03 and 2020-11-14, it was possible to trick the :tA modifier +# into resolving completely unrelated absolute paths by defining a global +# variable with the same name as the path that is to be resolved. There were +# a few restrictions though: The "redirected" path had to start with a slash, +# and it had to exist (see ModifyWord_Realpath). +# +# This unintended behavior was caused by cached_realpath using a GNode for +# keeping the cache, just like the GNode for global variables. +.MAKEFLAGS: -dd +does-not-exist.c= /dev/null +.info ${does-not-exist.c:L:tA} +.info ${does-not-exist.c:L:tA} + +# The output of the following line is modified by the global _SED_CMDS in +# unit-tests/Makefile. See the .rawout file for the truth. +.info ${MAKEFILE:tA} + +.MAKEFLAGS: -d0 all: @:; Index: head/contrib/bmake/unit-tests/varmod-to-lower.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-to-lower.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-to-lower.mk (revision 367863) @@ -1,19 +1,21 @@ -# $NetBSD: varmod-to-lower.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-to-lower.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :tl variable modifier, which returns the words in the # variable value, converted to lowercase. +# +# TODO: What about non-ASCII characters? ISO-8859-1, UTF-8? .if ${:UUPPER:tl} != "upper" . error .endif .if ${:Ulower:tl} != "lower" . error .endif .if ${:UMixeD case.:tl} != "mixed case." . error .endif all: @:; Index: head/contrib/bmake/unit-tests/varmod-to-separator.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-to-separator.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-to-separator.mk (revision 367863) @@ -1,169 +1,175 @@ -# $NetBSD: varmod-to-separator.mk,v 1.6 2020/11/01 14:36:25 rillig Exp $ +# $NetBSD: varmod-to-separator.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :ts variable modifier, which joins the words of the variable # using an arbitrary character as word separator. WORDS= one two three four five six # The words are separated by a single space, just as usual. .if ${WORDS:ts } != "one two three four five six" . warning Space as separator does not work. .endif # The separator can be an arbitrary character, for example a comma. .if ${WORDS:ts,} != "one,two,three,four,five,six" . warning Comma as separator does not work. .endif # After the :ts modifier, other modifiers can follow. .if ${WORDS:ts/:tu} != "ONE/TWO/THREE/FOUR/FIVE/SIX" . warning Chaining modifiers does not work. .endif # To use the ':' as the separator, just write it normally. # The first colon is the separator, the second ends the modifier. .if ${WORDS:ts::tu} != "ONE:TWO:THREE:FOUR:FIVE:SIX" . warning Colon as separator does not work. .endif # When there is just a colon but no other character, the words are # "separated" by an empty string, that is, they are all squashed # together. .if ${WORDS:ts:tu} != "ONETWOTHREEFOURFIVESIX" . warning Colon as separator does not work. .endif # Applying the :tu modifier first and then the :ts modifier does not change # anything since neither of these modifiers is related to how the string is # split into words. Beware of separating the words using a single or double # quote though, or other special characters like dollar or backslash. # # This example also demonstrates that the closing brace is not interpreted # as a separator, but as the closing delimiter of the whole variable # expression. .if ${WORDS:tu:ts} != "ONETWOTHREEFOURFIVESIX" . warning Colon as separator does not work. .endif # The '}' plays the same role as the ':' in the preceding examples. # Since there is a single character before it, that character is taken as # the separator. .if ${WORDS:tu:ts/} != "ONE/TWO/THREE/FOUR/FIVE/SIX" . warning Colon as separator does not work. .endif # Now it gets interesting and ambiguous: The separator could either be empty # since it is followed by a colon. Or it could be the colon since that # colon is followed by the closing brace. It's the latter case. .if ${WORDS:ts:} != "one:two:three:four:five:six" . warning Colon followed by closing brace does not work. .endif # As in the ${WORDS:tu:ts} example above, the separator is empty. .if ${WORDS:ts} != "onetwothreefourfivesix" . warning Empty separator before closing brace does not work. .endif # The :ts modifier can be followed by other modifiers. .if ${WORDS:ts:S/two/2/} != "one2threefourfivesix" . warning Separator followed by :S modifier does not work. .endif # The :ts modifier can follow other modifiers. .if ${WORDS:S/two/2/:ts} != "one2threefourfivesix" . warning :S modifier followed by :ts modifier does not work. .endif # The :ts modifier with an actual separator can be followed by other # modifiers. .if ${WORDS:ts/:S/two/2/} != "one/2/three/four/five/six" . warning The :ts modifier followed by an :S modifier does not work. .endif # The separator can be \n, which is a newline. .if ${WORDS:[1..3]:ts\n} != "one${.newline}two${.newline}three" . warning The separator \n does not produce a newline. .endif # The separator can be \t, which is a tab. .if ${WORDS:[1..3]:ts\t} != "one two three" . warning The separator \t does not produce a tab. .endif # The separator can be given as octal number. .if ${WORDS:[1..3]:ts\012:tu} != "ONE${.newline}TWO${.newline}THREE" . warning The separator \012 is not interpreted in octal ASCII. .endif # The octal number can have as many digits as it wants. .if ${WORDS:[1..2]:ts\000000000000000000000000012:tu} != "ONE${.newline}TWO" . warning The separator \012 cannot have many leading zeroes. .endif # The value of the separator character must not be outside the value space # for an unsigned character though. # # Since 2020-11-01, these out-of-bounds values are rejected. .if ${WORDS:[1..3]:ts\400:tu} . warning The separator \400 is accepted even though it is out of bounds. .else . warning The separator \400 is accepted even though it is out of bounds. .endif # The separator can be given as hexadecimal number. .if ${WORDS:[1..3]:ts\xa:tu} != "ONE${.newline}TWO${.newline}THREE" . warning The separator \xa is not interpreted in hexadecimal ASCII. .endif # The hexadecimal number must be in the range of an unsigned char. # # Since 2020-11-01, these out-of-bounds values are rejected. .if ${WORDS:[1..3]:ts\x100:tu} . warning The separator \x100 is accepted even though it is out of bounds. .else . warning The separator \x100 is accepted even though it is out of bounds. .endif # Negative numbers are not allowed for the separator character. .if ${WORDS:[1..3]:ts\-300:tu} . warning The separator \-300 is accepted even though it is negative. .else . warning The separator \-300 is accepted even though it is negative. .endif # The character number is interpreted as octal number by default. # The digit '8' is not an octal digit though. .if ${1 2 3:L:ts\8:tu} . warning The separator \8 is accepted even though it is not octal. .else . warning The separator \8 is accepted even though it is not octal. .endif # Trailing characters after the octal character number are rejected. .if ${1 2 3:L:ts\100L} . warning The separator \100L is accepted even though it contains an 'L'. .else . warning The separator \100L is accepted even though it contains an 'L'. .endif # Trailing characters after the hexadecimal character number are rejected. .if ${1 2 3:L:ts\x40g} . warning The separator \x40g is accepted even though it contains a 'g'. .else . warning The separator \x40g is accepted even though it contains a 'g'. .endif # In the :t modifier, the :t must be followed by any of A, l, s, u. .if ${WORDS:tx} != "anything" . info This line is not reached because of the malformed condition. . info If this line were reached, it would be visible in the -dcpv log. .endif # After the backslash, only n, t, an octal number, or x and a hexadecimal # number are allowed. .if ${WORDS:t\X} != "anything" . info This line is not reached. .endif + +# TODO: This modifier used to accept decimal numbers as well, in the form +# ':ts\120'. When has this been changed to octal, and what happens now +# for ':ts\90' ('Z' in decimal ASCII, undefined in octal)? + +# TODO: :ts\x1F600 all: Index: head/contrib/bmake/unit-tests/varmod-undefined.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod-undefined.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod-undefined.mk (revision 367863) @@ -1,67 +1,68 @@ -# $NetBSD: varmod-undefined.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-undefined.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :U variable modifier, which returns the given string # if the variable is undefined. # # See also: +# directive-for.mk # varmod-defined.mk # The pattern ${:Uword} is heavily used when expanding .for loops. # # This is how an expanded .for loop looks like. # .for word in one # . if ${word} != one .if ${:Uone} != one # . error ${word} . error ${:Uone} # . endif .endif # .endfor # The variable expressions in the text of the :U modifier may be arbitrarily # nested. .if ${:U${:Unested}${${${:Udeeply}}}} != nested . error .endif # The nested variable expressions may contain braces, and these braces don't # need to match pairwise. In the following example, the :S modifier uses '{' # as delimiter, which confuses both editors and humans because the opening # and # closing braces don't match anymore. It's syntactically valid though. # For more similar examples, see varmod-subst.mk, mod-subst-delimiter. .if ${:U${:Uvalue:S{a{X{}} != vXlue . error .endif # The escaping rules for the :U modifier (left-hand side) and condition # string literals (right-hand side) are completely different. # # In the :U modifier, the backslash only escapes very few characters, all # other backslashes are retained. # # In condition string literals, the backslash always escapes the following # character, no matter whether it would be necessary or not. # # In both contexts, \n is an escaped letter n, not a newline; that's what # the .newline variable is for. # # Whitespace at the edges is preserved, on both sides of the comparison. # .if ${:U \: \} \$ \\ \a \b \n } != " : } \$ \\ \\a \\b \\n " . error .endif # Even after the :U modifier has been applied, the expression still remembers # that it originated from an undefined variable, and the :U modifier can # be used to overwrite the value of the expression. # .if ${UNDEF:Uvalue:S,a,X,} != "vXlue" . error .elif ${UNDEF:Uvalue:S,a,X,:Uwas undefined} != "was undefined" . error .endif all: @:; Index: head/contrib/bmake/unit-tests/varmod.exp =================================================================== --- head/contrib/bmake/unit-tests/varmod.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varmod.exp (revision 367863) @@ -1,6 +1,8 @@ make: "varmod.mk" line 42: To escape a dollar, use \$, not $$, at "$$:L} != """ make: "varmod.mk" line 42: Invalid variable name ':', at "$:L} != """ make: "varmod.mk" line 47: Dollar followed by nothing +make: "varmod.mk" line 56: Missing delimiter ':' after modifier "P" +make: "varmod.mk" line 57: Unknown directive "error" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 Index: head/contrib/bmake/unit-tests/varmod.mk =================================================================== --- head/contrib/bmake/unit-tests/varmod.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varmod.mk (revision 367863) @@ -1,51 +1,60 @@ -# $NetBSD: varmod.mk,v 1.3 2020/09/13 07:42:20 rillig Exp $ +# $NetBSD: varmod.mk,v 1.4 2020/11/02 17:30:22 rillig Exp $ # # Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback. DOLLAR1= $$ DOLLAR2= ${:U\$} # To get a single '$' sign in the value of a variable expression, it has to # be written as '$$' in a literal variable value. # # See Var_Parse, where it calls Var_Subst. .if ${DOLLAR1} != "\$" . error .endif # Another way to get a single '$' sign is to use the :U modifier. In the # argument of that modifier, a '$' is escaped using the backslash instead. # # See Var_Parse, where it calls Var_Subst. .if ${DOLLAR2} != "\$" . error .endif # It is also possible to use the :U modifier directly in the expression. # # See Var_Parse, where it calls Var_Subst. .if ${:U\$} != "\$" . error .endif # XXX: As of 2020-09-13, it is not possible to use '$$' in a variable name # to mean a single '$'. This contradicts the manual page, which says that # '$' can be escaped as '$$'. .if ${$$:L} != "" . error .endif # In lint mode, make prints helpful error messages. # For compatibility, make does not print these error messages in normal mode. # Should it? .MAKEFLAGS: -dL .if ${$$:L} != "" . error .endif # A '$' followed by nothing is an error as well. .if ${:Uword:@word@${word}$@} != "word" +. error +.endif + +# The variable modifier :P does not fall back to the SysV modifier. +# Therefore the modifier :P=RE generates a parse error. +# XXX: The .error should not be reached since the variable expression is +# malformed. +VAR= STOP +.if ${VAR:P=RE} != "STORE" . error .endif all: # nothing Index: head/contrib/bmake/unit-tests/varname-dot-shell.exp =================================================================== --- head/contrib/bmake/unit-tests/varname-dot-shell.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varname-dot-shell.exp (revision 367863) @@ -1,32 +1,32 @@ ParseReadLine (10): 'ORIG_SHELL:= ${.SHELL}' Global:ORIG_SHELL = -Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_ASSIGN +Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_KEEP_DOLLAR Global:delete .SHELL (not found) Command:.SHELL = (details omitted) Global:ORIG_SHELL = (details omitted) ParseReadLine (12): '.SHELL= overwritten' Global:.SHELL = overwritten CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES Var_Parse: ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES lhs = "(details omitted)", rhs = "(details omitted)", op = != ParseReadLine (19): '.MAKEFLAGS: .SHELL+=appended' ParseDoDependency(.MAKEFLAGS: .SHELL+=appended) Ignoring append to .SHELL since it is read-only CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES Var_Parse: ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES lhs = "(details omitted)", rhs = "(details omitted)", op = != ParseReadLine (27): '.undef .SHELL' Global:delete .SHELL ParseReadLine (28): '.SHELL= newly overwritten' Global:.SHELL = newly overwritten CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES Var_Parse: ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES lhs = "(details omitted)", rhs = "(details omitted)", op = != ParseReadLine (33): '.MAKEFLAGS: -d0' ParseDoDependency(.MAKEFLAGS: -d0) Global:.MAKEFLAGS = -r -k -d cpv -d Global:.MAKEFLAGS = -r -k -d cpv -d 0 exit status 0 Index: head/contrib/bmake/unit-tests/varname-empty.exp =================================================================== --- head/contrib/bmake/unit-tests/varname-empty.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varname-empty.exp (revision 367863) @@ -1,13 +1,13 @@ Var_Set("${:U}", "cmdline-u", ...) name expands to empty string - ignored -Var_Set("", "cmline-plain", ...) name expands to empty string - ignored +Var_Set("", "cmdline-plain", ...) name expands to empty string - ignored Var_Set("", "default", ...) name expands to empty string - ignored Var_Set("", "assigned", ...) name expands to empty string - ignored Var_Set("", "appended", ...) name expands to empty string - ignored Var_Set("", "", ...) name expands to empty string - ignored Var_Set("", "subst", ...) name expands to empty string - ignored Var_Set("", "shell-output", ...) name expands to empty string - ignored Var_Set("${:U}", "assigned indirectly", ...) name expands to empty string - ignored Var_Set("", "assigned", ...) name expands to empty string - ignored out: fallback out: 1 2 3 exit status 0 Index: head/contrib/bmake/unit-tests/varname-makefile.exp =================================================================== --- head/contrib/bmake/unit-tests/varname-makefile.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varname-makefile.exp (revision 367863) @@ -1 +1,2 @@ +: In the end, MAKEFILE is /dev/null. exit status 0 Index: head/contrib/bmake/unit-tests/varname-makefile.mk =================================================================== --- head/contrib/bmake/unit-tests/varname-makefile.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varname-makefile.mk (revision 367863) @@ -1,44 +1,49 @@ -# $NetBSD: varname-makefile.mk,v 1.2 2020/09/05 06:25:38 rillig Exp $ +# $NetBSD: varname-makefile.mk,v 1.3 2020/11/09 22:36:44 rillig Exp $ # # Tests for the special MAKEFILE variable, which contains the current # makefile from the -f command line option. # # When there are multiple -f options, the variable MAKEFILE is set # again for each of these makefiles, before the file is parsed. # Including a file via .include does not influence the MAKEFILE # variable though. .if ${MAKEFILE:T} != "varname-makefile.mk" . error .endif # This variable lives in the "Internal" namespace. # TODO: Why does it do that, and what consequences does this have? # Deleting the variable does not work since this variable does not live in # the "Global" namespace but in "Internal", which is kind of a child # namespace. # .undef MAKEFILE .if ${MAKEFILE:T} != "varname-makefile.mk" . error .endif # Overwriting this variable is possible since the "Internal" namespace # serves as a fallback for the "Global" namespace (see VarFind). # MAKEFILE= overwritten .if ${MAKEFILE:T} != "overwritten" . error .endif # When the overwritten value is deleted, the fallback value becomes # visible again. # .undef MAKEFILE .if ${MAKEFILE:T} != "varname-makefile.mk" . error .endif all: - @:; + # MAKEFILE is the file that appeared last in the command line. + : In the end, MAKEFILE is ${MAKEFILE}. + +# Additional makefiles can be added while reading a makefile. They will be +# read in order. +.MAKEFLAGS: -f /dev/null Index: head/contrib/bmake/unit-tests/varname-vpath.exp =================================================================== --- head/contrib/bmake/unit-tests/varname-vpath.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varname-vpath.exp (revision 367863) @@ -1 +1,12 @@ +CondParser_Eval: !defined(TEST_MAIN) +CondParser_Eval: exists(file-in-subdirectory) +exists(file-in-subdirectory) result is "" +CondParser_Eval: exists(file2-in-subdirectory) +exists(file2-in-subdirectory) result is "" +CondParser_Eval: exists(file-in-subdirectory) +exists(file-in-subdirectory) result is "varname-vpath.dir/file-in-subdirectory" +: yes 1 +CondParser_Eval: exists(file2-in-subdirectory) +exists(file2-in-subdirectory) result is "varname-vpath.dir2/file2-in-subdirectory" +: yes 2 exit status 0 Index: head/contrib/bmake/unit-tests/varname-vpath.mk =================================================================== --- head/contrib/bmake/unit-tests/varname-vpath.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varname-vpath.mk (revision 367863) @@ -1,8 +1,42 @@ -# $NetBSD: varname-vpath.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-vpath.mk,v 1.3 2020/11/10 00:19:19 rillig Exp $ # -# Tests for the special VPATH variable. +# Tests for the special VPATH variable, which is an obsolete way of +# specifying a colon-separated search path. This search path is not active +# when the makefiles are read, but only later when the shell commands are run. +# +# Instead of the VPATH, better use the -I option or the special target .PATH. -# TODO: Implementation +.if !defined(TEST_MAIN) +all: .SILENT + rm -rf varname-vpath.dir + mkdir varname-vpath.dir + touch varname-vpath.dir/file-in-subdirectory + rm -rf varname-vpath.dir2 + mkdir varname-vpath.dir2 + touch varname-vpath.dir2/file2-in-subdirectory + + TEST_MAIN=yes VPATH=varname-vpath.dir:varname-vpath.dir2 \ + ${MAKE} -f ${MAKEFILE} -dc + + rm -r varname-vpath.dir + rm -r varname-vpath.dir2 + +.else + +# The VPATH variable does not take effect at parse time. +# It is evaluated only once, between reading the makefiles and making the +# targets. Therefore it could also be an ordinary variable, it doesn't need +# to be an environment variable or a command line variable. +. if exists(file-in-subdirectory) +. error +. endif +. if exists(file2-in-subdirectory) +. error +. endif + all: - @:; + : ${exists(file-in-subdirectory):L:?yes 1:no 1} + : ${exists(file2-in-subdirectory):L:?yes 2:no 2} + +.endif Index: head/contrib/bmake/unit-tests/varname.exp =================================================================== --- head/contrib/bmake/unit-tests/varname.exp (revision 367862) +++ head/contrib/bmake/unit-tests/varname.exp (revision 367863) @@ -1 +1,24 @@ -exit status 0 +Global:VAR{{{}}} = 3 braces +Var_Parse: ${VAR{{{}}}}" != "3 braces" with VARE_WANTRES +Global:VARNAME = VAR((( +Var_Parse: ${VARNAME} with VARE_WANTRES +Global:VAR((( = 3 open parentheses +Var_Parse: ${VAR(((}}}}" != "3 open parentheses}}}" with VARE_WANTRES +Var_Parse: ${:UVAR(((}= try1 with VARE_UNDEFERR|VARE_WANTRES +Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) +Result of ${:UVAR(((} is "VAR(((" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) +Global:.ALLTARGETS = VAR(((=) +make: "varname.mk" line 30: No closing parenthesis in archive specification +make: "varname.mk" line 30: Error in archive specification: "VAR" +Var_Parse: ${:UVAR\(\(\(}= try2 with VARE_UNDEFERR|VARE_WANTRES +Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) +Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) +Global:.ALLTARGETS = VAR(((=) VAR\(\(\(= +make: "varname.mk" line 35: Need an operator +Var_Parse: ${VARNAME} with VARE_WANTRES +Global:VAR((( = try3 +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/varname.mk =================================================================== --- head/contrib/bmake/unit-tests/varname.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varname.mk (revision 367863) @@ -1,8 +1,44 @@ -# $NetBSD: varname.mk,v 1.4 2020/10/18 08:47:54 rillig Exp $ +# $NetBSD: varname.mk,v 1.8 2020/11/02 22:59:48 rillig Exp $ # # Tests for special variables, such as .MAKE or .PARSEDIR. +# And for variable names in general. -# TODO: Implementation +.MAKEFLAGS: -dv +# In variable names, braces are allowed, but they must be balanced. +# Parentheses and braces may be mixed. +VAR{{{}}}= 3 braces +.if "${VAR{{{}}}}" != "3 braces" +. error +.endif + +# In variable expressions, the parser works differently. It doesn't treat +# braces and parentheses equally, therefore the first closing brace already +# marks the end of the variable name. +VARNAME= VAR((( +${VARNAME}= 3 open parentheses +.if "${VAR(((}}}}" != "3 open parentheses}}}" +. error +.endif + +# In the above test, the variable name is constructed indirectly. Neither +# of the following expressions produces the intended effect. +# +# This is not a variable assignment since the parentheses and braces are not +# balanced. At the end of the line, there are still 3 levels open, which +# means the variable name is not finished. +${:UVAR(((}= try1 +# On the left-hand side of a variable assignments, the backslash is not parsed +# as an escape character, therefore the parentheses still count to the nesting +# level, which at the end of the line is still 3. Therefore this is not a +# variable assignment as well. +${:UVAR\(\(\(}= try2 +# To assign to a variable with an arbitrary name, the variable name has to +# come from an external source, not the text that is parsed in the assignment +# itself. This is exactly the reason why further above, the indirect +# ${VARNAME} works, while all other attempts fail. +${VARNAME}= try3 + +.MAKEFLAGS: -d0 + all: - @:; Index: head/contrib/bmake/unit-tests/varparse-errors.exp =================================================================== --- head/contrib/bmake/unit-tests/varparse-errors.exp (nonexistent) +++ head/contrib/bmake/unit-tests/varparse-errors.exp (revision 367863) @@ -0,0 +1 @@ +exit status 0 Index: head/contrib/bmake/unit-tests/varparse-errors.mk =================================================================== --- head/contrib/bmake/unit-tests/varparse-errors.mk (nonexistent) +++ head/contrib/bmake/unit-tests/varparse-errors.mk (revision 367863) @@ -0,0 +1,35 @@ +# $NetBSD: varparse-errors.mk,v 1.1 2020/11/08 16:44:47 rillig Exp $ + +# Tests for parsing and evaluating all kinds of variable expressions. +# +# This is the basis for redesigning the error handling in Var_Parse and +# Var_Subst, collecting typical and not so typical use cases. +# +# See also: +# VarParseResult +# Var_Parse +# Var_Subst + +PLAIN= plain value + +LITERAL_DOLLAR= To get a dollar, double $$ it. + +INDIRECT= An ${:Uindirect} value. + +REF_UNDEF= A reference to an ${UNDEF}undefined variable. + +ERR_UNCLOSED= An ${UNCLOSED variable expression. + +ERR_BAD_MOD= An ${:Uindirect:Z} expression with an unknown modifier. + +ERR_EVAL= An evaluation error ${:Uvalue:C,.,\3,}. + +# In a conditional, a variable expression that is not enclosed in quotes is +# expanded using the flags VARE_UNDEFERR and VARE_WANTRES. +# The variable itself must be defined. +# It may refer to undefined variables though. +.if ${REF_UNDEF} != "A reference to an undefined variable." +. error +.endif + +all: Property changes on: head/contrib/bmake/unit-tests/varparse-errors.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/varparse-undef-partial.mk =================================================================== --- head/contrib/bmake/unit-tests/varparse-undef-partial.mk (revision 367862) +++ head/contrib/bmake/unit-tests/varparse-undef-partial.mk (revision 367863) @@ -1,64 +1,64 @@ -# $NetBSD: varparse-undef-partial.mk,v 1.2 2020/09/27 09:53:41 rillig Exp $ +# $NetBSD: varparse-undef-partial.mk,v 1.3 2020/11/04 05:10:01 rillig Exp $ # When an undefined variable is expanded in a ':=' assignment, only the # initial '$' of the variable expression is skipped by the parser, while # the remaining expression is evaluated. In edge cases this can lead to # a completely different interpretation of the partially expanded text. LIST= ${DEF} ${UNDEF} ${VAR.${PARAM}} end DEF= defined PARAM= :Q -# The expression ${VAR.{PARAM}} refers to the variable named "VAR.:Q", +# The expression ${VAR.${PARAM}} refers to the variable named "VAR.:Q", # with the ":Q" being part of the name. This variable is not defined, # therefore the initial '$' of that whole expression is skipped by the # parser (see Var_Subst, the Buf_AddByte in the else branch) and the rest # of the expression is expanded as usual. # # The resulting variable expression is ${VAR.:Q}, which means that the # interpretation of the ":Q" has changed from being part of the variable # name to being a variable modifier. This is a classical code injection. EVAL:= ${LIST} .if ${EVAL} != "defined end" . error ${EVAL} .endif # Define the possible outcomes, to see which of them gets expanded. VAR.= var-dot without parameter ${:UVAR.\:Q}= var-dot with parameter :Q # At this point, the variable "VAR." is defined, therefore the expression -# ${VAR.:Q} is expanded as usual. +# ${VAR.:Q} is expanded, consisting of the variable name "VAR." and the +# modifier ":Q". .if ${EVAL} != "defined var-dot\\ without\\ parameter end" . error ${EVAL} .endif # In contrast to the previous line, evaluating the original LIST again now -# produces a different result since the ":Q" has already been inserted -# literally into the expression. The variable named "VAR.:Q" is defined, -# therefore it is resolved as usual. The ":Q" is interpreted as part of the +# produces a different result since the variable named "VAR.:Q" is now +# defined. It is expanded as usual, interpreting the ":Q" as part of the # variable name, as would be expected from reading the variable expression. EVAL:= ${LIST} .if ${EVAL} != "defined var-dot with parameter :Q end" . error ${EVAL} .endif # It's difficult to decide what the best behavior is in this situation. # Should the whole expression be skipped for now, or should the inner # subexpressions be expanded already? # # Example 1: # CFLAGS:= ${CFLAGS:N-W*} ${COPTS.${COMPILER}} # # The variable COMPILER typically contains an identifier and the variable is # not modified later. In this practical case, it does not matter whether the # expression is expanded early, or whether the whole ${COPTS.${COMPILER}} is # expanded as soon as the variable COPTS.${COMPILER} becomes defined. The # expression ${COMPILER} would be expanded several times, but in this simple # scenario there would not be any side effects. # # TODO: Add a practical example where early/lazy expansion actually makes a # difference. all: @: Index: head/contrib/bmake/util.c =================================================================== --- head/contrib/bmake/util.c (revision 367862) +++ head/contrib/bmake/util.c (revision 367863) @@ -1,589 +1,587 @@ -/* $NetBSD: util.c,v 1.64 2020/10/06 21:51:33 rillig Exp $ */ +/* $NetBSD: util.c,v 1.68 2020/11/16 18:29:49 rillig Exp $ */ /* * Missing stuff from OS's * - * $Id: util.c,v 1.39 2020/10/10 19:42:02 sjg Exp $ + * $Id: util.c,v 1.41 2020/11/18 03:58:32 sjg Exp $ */ #include #include #include #include #include "make.h" -MAKE_RCSID("$NetBSD: util.c,v 1.64 2020/10/06 21:51:33 rillig Exp $"); +MAKE_RCSID("$NetBSD: util.c,v 1.68 2020/11/16 18:29:49 rillig Exp $"); #if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR) extern int errno, sys_nerr; extern char *sys_errlist[]; char * strerror(int e) { static char buf[100]; if (e < 0 || e >= sys_nerr) { - snprintf(buf, sizeof(buf), "Unknown error %d", e); + snprintf(buf, sizeof buf, "Unknown error %d", e); return buf; - } - else + } else return sys_errlist[e]; } #endif #if !defined(HAVE_GETENV) || !defined(HAVE_SETENV) || !defined(HAVE_UNSETENV) extern char **environ; static char * findenv(const char *name, int *offset) { size_t i, len; char *p, *q; len = strlen(name); for (i = 0; (q = environ[i]); i++) { p = strchr(q, '='); if (p == NULL || p - q != len) continue; if (strncmp(name, q, len) == 0) { *offset = i; return q + len + 1; } } *offset = i; return NULL; } char * getenv(const char *name) { int offset; return findenv(name, &offset); } int unsetenv(const char *name) { char **p; int offset; if (name == NULL || *name == '\0' || strchr(name, '=') != NULL) { errno = EINVAL; return -1; } while (findenv(name, &offset)) { /* if set multiple times */ for (p = &environ[offset];; ++p) if (!(*p = *(p + 1))) break; } return 0; } int setenv(const char *name, const char *value, int rewrite) { char *c, **newenv; const char *cc; size_t l_value, size; int offset; if (name == NULL || value == NULL) { errno = EINVAL; return -1; } if (*value == '=') /* no `=' in value */ - ++value; + value++; l_value = strlen(value); /* find if already exists */ if ((c = findenv(name, &offset))) { if (!rewrite) return 0; if (strlen(c) >= l_value) /* old larger; copy over */ goto copy; } else { /* create new slot */ size = sizeof(char *) * (offset + 2); if (savedEnv == environ) { /* just increase size */ if ((newenv = realloc(savedEnv, size)) == NULL) return -1; savedEnv = newenv; } else { /* get new space */ /* * We don't free here because we don't know if * the first allocation is valid on all OS's */ if ((savedEnv = malloc(size)) == NULL) return -1; (void)memcpy(savedEnv, environ, size - sizeof(char *)); } environ = savedEnv; environ[offset + 1] = NULL; } for (cc = name; *cc && *cc != '='; ++cc) /* no `=' in name */ continue; size = cc - name; /* name + `=' + value */ if ((environ[offset] = malloc(size + l_value + 2)) == NULL) return -1; c = environ[offset]; (void)memcpy(c, name, size); c += size; *c++ = '='; copy: (void)memcpy(c, value, l_value + 1); return 0; } #ifdef TEST int main(int argc, char *argv[]) { setenv(argv[1], argv[2], 0); printf("%s\n", getenv(argv[1])); unsetenv(argv[1]); printf("%s\n", getenv(argv[1])); return 0; } #endif #endif #if defined(__hpux__) || defined(__hpux) /* strrcpy(): * Like strcpy, going backwards and returning the new pointer */ static char * strrcpy(char *ptr, char *str) { int len = strlen(str); while (len) *--ptr = str[--len]; return ptr; } /* end strrcpy */ char *sys_siglist[] = { "Signal 0", "Hangup", /* SIGHUP */ "Interrupt", /* SIGINT */ "Quit", /* SIGQUIT */ "Illegal instruction", /* SIGILL */ "Trace/BPT trap", /* SIGTRAP */ "IOT trap", /* SIGIOT */ "EMT trap", /* SIGEMT */ "Floating point exception", /* SIGFPE */ "Killed", /* SIGKILL */ "Bus error", /* SIGBUS */ "Segmentation fault", /* SIGSEGV */ "Bad system call", /* SIGSYS */ "Broken pipe", /* SIGPIPE */ "Alarm clock", /* SIGALRM */ "Terminated", /* SIGTERM */ "User defined signal 1", /* SIGUSR1 */ "User defined signal 2", /* SIGUSR2 */ "Child exited", /* SIGCLD */ "Power-fail restart", /* SIGPWR */ "Virtual timer expired", /* SIGVTALRM */ "Profiling timer expired", /* SIGPROF */ "I/O possible", /* SIGIO */ "Window size changes", /* SIGWINDOW */ "Stopped (signal)", /* SIGSTOP */ "Stopped", /* SIGTSTP */ "Continued", /* SIGCONT */ "Stopped (tty input)", /* SIGTTIN */ "Stopped (tty output)", /* SIGTTOU */ "Urgent I/O condition", /* SIGURG */ "Remote lock lost (NFS)", /* SIGLOST */ "Signal 31", /* reserved */ "DIL signal" /* SIGDIL */ }; #endif /* __hpux__ || __hpux */ #if defined(__hpux__) || defined(__hpux) #include #include #include #include #include #include #include int killpg(int pid, int sig) { return kill(-pid, sig); } #if !defined(BSD) && !defined(d_fileno) # define d_fileno d_ino #endif #ifndef DEV_DEV_COMPARE # define DEV_DEV_COMPARE(a, b) ((a) == (b)) #endif #define ISDOT(c) ((c)[0] == '.' && (((c)[1] == '\0') || ((c)[1] == '/'))) #define ISDOTDOT(c) ((c)[0] == '.' && ISDOT(&((c)[1]))) char * getwd(char *pathname) { DIR *dp; struct dirent *d; extern int errno; struct stat st_root, st_cur, st_next, st_dotdot; char pathbuf[MAXPATHLEN], nextpathbuf[MAXPATHLEN * 2]; char *pathptr, *nextpathptr, *cur_name_add; /* find the inode of root */ if (stat("/", &st_root) == -1) { (void)sprintf(pathname, "getwd: Cannot stat \"/\" (%s)", strerror(errno)); return NULL; } pathbuf[MAXPATHLEN - 1] = '\0'; pathptr = &pathbuf[MAXPATHLEN - 1]; nextpathbuf[MAXPATHLEN - 1] = '\0'; cur_name_add = nextpathptr = &nextpathbuf[MAXPATHLEN - 1]; /* find the inode of the current directory */ if (lstat(".", &st_cur) == -1) { (void)sprintf(pathname, "getwd: Cannot stat \".\" (%s)", strerror(errno)); return NULL; } nextpathptr = strrcpy(nextpathptr, "../"); /* Descend to root */ for (;;) { /* look if we found root yet */ if (st_cur.st_ino == st_root.st_ino && DEV_DEV_COMPARE(st_cur.st_dev, st_root.st_dev)) { (void)strcpy(pathname, *pathptr != '/' ? "/" : pathptr); return pathname; } /* open the parent directory */ if (stat(nextpathptr, &st_dotdot) == -1) { (void)sprintf(pathname, "getwd: Cannot stat directory \"%s\" (%s)", nextpathptr, strerror(errno)); return NULL; } if ((dp = opendir(nextpathptr)) == NULL) { (void)sprintf(pathname, "getwd: Cannot open directory \"%s\" (%s)", nextpathptr, strerror(errno)); return NULL; } /* look in the parent for the entry with the same inode */ if (DEV_DEV_COMPARE(st_dotdot.st_dev, st_cur.st_dev)) { /* Parent has same device. No need to stat every member */ for (d = readdir(dp); d != NULL; d = readdir(dp)) if (d->d_fileno == st_cur.st_ino) break; - } - else { + } else { /* * Parent has a different device. This is a mount point so we * need to stat every member */ for (d = readdir(dp); d != NULL; d = readdir(dp)) { if (ISDOT(d->d_name) || ISDOTDOT(d->d_name)) continue; (void)strcpy(cur_name_add, d->d_name); if (lstat(nextpathptr, &st_next) == -1) { (void)sprintf(pathname, "getwd: Cannot stat \"%s\" (%s)", d->d_name, strerror(errno)); (void)closedir(dp); return NULL; } /* check if we found it yet */ if (st_next.st_ino == st_cur.st_ino && DEV_DEV_COMPARE(st_next.st_dev, st_cur.st_dev)) break; } } if (d == NULL) { (void)sprintf(pathname, "getwd: Cannot find \".\" in \"..\""); (void)closedir(dp); return NULL; } st_cur = st_dotdot; pathptr = strrcpy(pathptr, d->d_name); pathptr = strrcpy(pathptr, "/"); nextpathptr = strrcpy(nextpathptr, "../"); (void)closedir(dp); *cur_name_add = '\0'; } } /* end getwd */ #endif /* __hpux */ #if !defined(HAVE_GETCWD) char * getcwd(path, sz) char *path; int sz; { return getwd(path); } #endif /* force posix signals */ SignalProc bmake_signal(int s, SignalProc a) { struct sigaction sa, osa; sa.sa_handler = a; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(s, &sa, &osa) == -1) return SIG_ERR; else return osa.sa_handler; } #if !defined(HAVE_VSNPRINTF) || !defined(HAVE_VASPRINTF) #include #endif #if !defined(HAVE_VSNPRINTF) #if !defined(__osf__) #ifdef _IOSTRG #define STRFLAG (_IOSTRG|_IOWRT) /* no _IOWRT: avoid stdio bug */ #else #if 0 #define STRFLAG (_IOREAD) /* XXX: Assume svr4 stdio */ #endif #endif /* _IOSTRG */ #endif /* __osf__ */ int vsnprintf(char *s, size_t n, const char *fmt, va_list args) { #ifdef STRFLAG FILE fakebuf; fakebuf._flag = STRFLAG; /* * Some os's are char * _ptr, others are unsigned char *_ptr... * We cast to void * to make everyone happy. */ fakebuf._ptr = (void *)s; - fakebuf._cnt = n-1; + fakebuf._cnt = n - 1; fakebuf._file = -1; _doprnt(fmt, args, &fakebuf); fakebuf._cnt++; putc('\0', &fakebuf); - if (fakebuf._cnt<0) + if (fakebuf._cnt < 0) fakebuf._cnt = 0; - return n-fakebuf._cnt-1; + return n - fakebuf._cnt - 1; #else #ifndef _PATH_DEVNULL # define _PATH_DEVNULL "/dev/null" #endif /* * Rats... we don't want to clobber anything... * do a printf to /dev/null to see how much space we need. */ static FILE *nullfp; int need = 0; /* XXX what's a useful error return? */ if (!nullfp) nullfp = fopen(_PATH_DEVNULL, "w"); if (nullfp) { need = vfprintf(nullfp, fmt, args); if (need < n) (void)vsprintf(s, fmt, args); } return need; #endif } #endif #if !defined(HAVE_SNPRINTF) int snprintf(char *s, size_t n, const char *fmt, ...) { va_list ap; int rv; va_start(ap, fmt); rv = vsnprintf(s, n, fmt, ap); va_end(ap); return rv; } #endif #if !defined(HAVE_STRFTIME) size_t strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) { static char months[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; size_t s; char *b = buf; while (*fmt) { if (len == 0) return buf - b; if (*fmt != '%') { *buf++ = *fmt++; len--; continue; } switch (*fmt++) { case '%': *buf++ = '%'; len--; if (len == 0) return buf - b; /*FALLTHROUGH*/ case '\0': *buf = '%'; s = 1; break; case 'k': s = snprintf(buf, len, "%d", tm->tm_hour); break; case 'M': s = snprintf(buf, len, "%02d", tm->tm_min); break; case 'S': s = snprintf(buf, len, "%02d", tm->tm_sec); break; case 'b': if (tm->tm_mon >= 12) return buf - b; s = snprintf(buf, len, "%s", months[tm->tm_mon]); break; case 'd': s = snprintf(buf, len, "%02d", tm->tm_mday); break; case 'Y': s = snprintf(buf, len, "%d", 1900 + tm->tm_year); break; default: s = snprintf(buf, len, "Unsupported format %c", fmt[-1]); break; } buf += s; len -= s; } return buf - b; } #endif #if !defined(HAVE_KILLPG) #if !defined(__hpux__) && !defined(__hpux) int killpg(int pid, int sig) { return kill(-pid, sig); } #endif #endif #if !defined(HAVE_WARNX) static void vwarnx(const char *fmt, va_list args) { fprintf(stderr, "%s: ", progname); if ((fmt)) { vfprintf(stderr, fmt, args); fprintf(stderr, ": "); } } #endif #if !defined(HAVE_WARN) static void vwarn(const char *fmt, va_list args) { vwarnx(fmt, args); fprintf(stderr, "%s\n", strerror(errno)); } #endif #if !defined(HAVE_ERR) static void verr(int eval, const char *fmt, va_list args) { vwarn(fmt, args); exit(eval); } #endif #if !defined(HAVE_ERRX) static void verrx(int eval, const char *fmt, va_list args) { vwarnx(fmt, args); exit(eval); } #endif #if !defined(HAVE_ERR) void err(int eval, const char *fmt, ...) { va_list ap; va_start(ap, fmt); verr(eval, fmt, ap); va_end(ap); } #endif #if !defined(HAVE_ERRX) void errx(int eval, const char *fmt, ...) { va_list ap; va_start(ap, fmt); verrx(eval, fmt, ap); va_end(ap); } #endif #if !defined(HAVE_WARN) void warn(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vwarn(fmt, ap); va_end(ap); } #endif #if !defined(HAVE_WARNX) void warnx(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } #endif Index: head/contrib/bmake/var.c =================================================================== --- head/contrib/bmake/var.c (revision 367862) +++ head/contrib/bmake/var.c (revision 367863) @@ -1,4086 +1,4118 @@ -/* $NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $ */ +/* $NetBSD: var.c,v 1.689 2020/11/17 20:11:02 rillig 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. */ /* * Handling of variables and the expressions formed from them. * * Variables are set using lines of the form VAR=value. Both the variable * name and the value can contain references to other variables, by using * expressions like ${VAR}, ${VAR:Modifiers}, ${${VARNAME}} or ${VAR:${MODS}}. * * Interface: * Var_Init Initialize this module. * * Var_End Clean up the module. * * Var_Set Set the value of the variable, creating it if * necessary. * * Var_Append Append more characters to the variable, creating it if * necessary. A space is placed between the old value and * the new one. * * Var_Exists See if a variable exists. * * Var_Value Return the unexpanded value of a variable, or NULL if * the variable is undefined. * * Var_Subst Substitute all variable expressions in a string. * * Var_Parse Parse a variable expression such as ${VAR:Mpattern}. * * Var_Delete Delete a variable. * * Var_ExportVars Export some or even all variables to the environment * of this process and its child processes. * * Var_Export Export the variable to the environment of this process * and its child processes. * * Var_UnExport Don't export the variable anymore. * * Debugging: * Var_Stats Print out hashing statistics if in -dh mode. * * Var_Dump Print out all variables defined in the given context. * * XXX: There's a lot of duplication in these functions. */ #include #include #ifndef NO_REGEX #include #endif #include "make.h" #include #ifdef HAVE_INTTYPES_H #include #elif defined(HAVE_STDINT_H) #include #endif #ifdef HAVE_LIMITS_H #include #endif #include #include "dir.h" #include "job.h" #include "metachar.h" /* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $"); +MAKE_RCSID("$NetBSD: var.c,v 1.689 2020/11/17 20:11:02 rillig Exp $"); #define VAR_DEBUG1(fmt, arg1) DEBUG1(VAR, fmt, arg1) #define VAR_DEBUG2(fmt, arg1, arg2) DEBUG2(VAR, fmt, arg1, arg2) #define VAR_DEBUG3(fmt, arg1, arg2, arg3) DEBUG3(VAR, fmt, arg1, arg2, arg3) #define VAR_DEBUG4(fmt, arg1, arg2, arg3, arg4) DEBUG4(VAR, fmt, arg1, arg2, arg3, arg4) ENUM_FLAGS_RTTI_3(VarEvalFlags, - VARE_UNDEFERR, VARE_WANTRES, VARE_ASSIGN); + VARE_UNDEFERR, VARE_WANTRES, VARE_KEEP_DOLLAR); /* * This lets us tell if we have replaced the original environ * (which we cannot free). */ char **savedEnv = NULL; /* Special return value for Var_Parse, indicating a parse error. It may be * caused by an undefined variable, a syntax error in a modifier or * something entirely different. */ char var_Error[] = ""; /* Special return value for Var_Parse, indicating an undefined variable in * a case where VARE_UNDEFERR is not set. This undefined variable is * typically a dynamic variable such as ${.TARGET}, whose expansion needs to * be deferred until it is defined in an actual target. */ static char varUndefined[] = ""; -/* Special return value for Var_Parse, just to avoid allocating empty strings. - * In contrast to var_Error and varUndefined, this is not an error marker but - * just an ordinary successful return value. */ -static char emptyString[] = ""; - /* * Traditionally this make consumed $$ during := like any other expansion. * Other make's do not, and this make follows straight since 2016-01-09. * * This knob allows controlling the behavior. * FALSE to consume $$ during := assignment. * TRUE to preserve $$ during := assignment. */ #define MAKE_SAVE_DOLLARS ".MAKE.SAVE_DOLLARS" static Boolean save_dollars = FALSE; /* * Internally, variables are contained in four different contexts. * 1) the environment. They cannot 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 makefiles are located * here. * 3) the command-line context. All variables set on the command line * are placed in this context. * 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 (but see opts.checkEnvFirst). */ GNode *VAR_INTERNAL; /* variables from make itself */ GNode *VAR_GLOBAL; /* variables from the makefile */ GNode *VAR_CMDLINE; /* variables defined on the command-line */ typedef enum VarFlags { /* The variable's value is currently being used by Var_Parse or Var_Subst. * This marker is used to avoid endless recursion. */ VAR_IN_USE = 0x01, /* The variable comes from the environment. * These variables are not registered in any GNode, therefore they must * be freed as soon as they are not used anymore. */ VAR_FROM_ENV = 0x02, /* The variable is exported to the environment, to be used by child * processes. */ VAR_EXPORTED = 0x10, /* At the point where this variable was exported, it contained an * unresolved reference to another variable. Before any child process is * started, it needs to be exported again, in the hope that the referenced * variable can then be resolved. */ VAR_REEXPORT = 0x20, /* The variable came from the command line. */ VAR_FROM_CMD = 0x40, /* The variable value cannot be changed anymore, and the variable cannot * be deleted. Any attempts to do so are ignored. */ VAR_READONLY = 0x80 } VarFlags; ENUM_FLAGS_RTTI_6(VarFlags, VAR_IN_USE, VAR_FROM_ENV, VAR_EXPORTED, VAR_REEXPORT, VAR_FROM_CMD, VAR_READONLY); /* Variables are defined using one of the VAR=value assignments. Their * value can be queried by expressions such as $V, ${VAR}, or with modifiers * such as ${VAR:S,from,to,g:Q}. * * There are 3 kinds of variables: context variables, environment variables, * undefined variables. * * Context variables are stored in a GNode.context. The only way to undefine * a context variable is using the .undef directive. In particular, it must * not be possible to undefine a variable during the evaluation of an * expression, or Var.name might point nowhere. * * Environment variables are temporary. They are returned by VarFind, and * after using them, they must be freed using VarFreeEnv. * * Undefined variables occur during evaluation of variable expressions such * as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers. */ typedef struct Var { /* The name of the variable, once set, doesn't change anymore. * For context variables, it aliases the corresponding HashEntry name. * For environment and undefined variables, it is allocated. */ const char *name; void *name_freeIt; Buffer val; /* its value */ VarFlags flags; /* miscellaneous status flags */ } Var; /* * Exporting vars is expensive so skip it if we can */ typedef enum VarExportedMode { VAR_EXPORTED_NONE, VAR_EXPORTED_SOME, VAR_EXPORTED_ALL } VarExportedMode; static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE; typedef enum VarExportFlags { + VAR_EXPORT_NORMAL = 0, /* * We pass this to Var_Export when doing the initial export * or after updating an exported var. */ VAR_EXPORT_PARENT = 0x01, /* * We pass this to Var_Export1 to tell it to leave the value alone. */ VAR_EXPORT_LITERAL = 0x02 } VarExportFlags; /* Flags for pattern matching in the :S and :C modifiers */ typedef enum VarPatternFlags { VARP_SUB_GLOBAL = 0x01, /* Replace as often as possible ('g') */ VARP_SUB_ONE = 0x02, /* Replace only once ('1') */ VARP_ANCHOR_START = 0x04, /* Match at start of word ('^') */ VARP_ANCHOR_END = 0x08 /* Match at end of word ('$') */ } VarPatternFlags; static Var * VarNew(const char *name, void *name_freeIt, const char *value, VarFlags flags) { size_t value_len = strlen(value); Var *var = bmake_malloc(sizeof *var); var->name = name; var->name_freeIt = name_freeIt; - Buf_Init(&var->val, value_len + 1); + Buf_InitSize(&var->val, value_len + 1); Buf_AddBytes(&var->val, value, value_len); var->flags = flags; return var; } static const char * CanonicalVarname(const char *name) { if (*name == '.' && ch_isupper(name[1])) { switch (name[1]) { case 'A': if (strcmp(name, ".ALLSRC") == 0) name = ALLSRC; if (strcmp(name, ".ARCHIVE") == 0) name = ARCHIVE; break; case 'I': if (strcmp(name, ".IMPSRC") == 0) name = IMPSRC; break; case 'M': if (strcmp(name, ".MEMBER") == 0) name = MEMBER; break; case 'O': if (strcmp(name, ".OODATE") == 0) name = OODATE; break; case 'P': if (strcmp(name, ".PREFIX") == 0) name = PREFIX; break; case 'S': if (strcmp(name, ".SHELL") == 0) { if (!shellPath) Shell_Init(); } break; case 'T': if (strcmp(name, ".TARGET") == 0) name = TARGET; break; } } /* GNU make has an additional alias $^ == ${.ALLSRC}. */ return name; } static Var * GNode_FindVar(GNode *ctxt, const char *varname, unsigned int hash) { return HashTable_FindValueHash(&ctxt->context, varname, hash); } /* Find the variable in the context, and maybe in other contexts as well. * * Input: * name name to find, is not expanded any further * ctxt context in which to look first * elsewhere TRUE to look in other contexts as well * * Results: * The found variable, or NULL if the variable does not exist. * If the variable is an environment variable, it must be freed using * VarFreeEnv after use. */ static Var * VarFind(const char *name, GNode *ctxt, Boolean elsewhere) { Var *var; unsigned int nameHash; /* * 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. */ name = CanonicalVarname(name); nameHash = Hash_Hash(name); /* First look for the variable in the given context. */ var = GNode_FindVar(ctxt, name, nameHash); if (!elsewhere) return var; /* The variable was not found in the given context. Now look for it in * the other contexts as well. */ if (var == NULL && ctxt != VAR_CMDLINE) var = GNode_FindVar(VAR_CMDLINE, name, nameHash); if (!opts.checkEnvFirst && var == NULL && ctxt != VAR_GLOBAL) { var = GNode_FindVar(VAR_GLOBAL, name, nameHash); if (var == NULL && ctxt != VAR_INTERNAL) { /* VAR_INTERNAL is subordinate to VAR_GLOBAL */ var = GNode_FindVar(VAR_INTERNAL, name, nameHash); } } if (var == NULL) { char *env; if ((env = getenv(name)) != NULL) { char *varname = bmake_strdup(name); return VarNew(varname, varname, env, VAR_FROM_ENV); } if (opts.checkEnvFirst && ctxt != VAR_GLOBAL) { var = GNode_FindVar(VAR_GLOBAL, name, nameHash); if (var == NULL && ctxt != VAR_INTERNAL) var = GNode_FindVar(VAR_INTERNAL, name, nameHash); return var; } return NULL; } return var; } /* If the variable is an environment variable, free it. * * Input: * v the variable * freeValue true if the variable value should be freed as well * * Results: * TRUE if it is an environment variable, FALSE otherwise. */ static Boolean VarFreeEnv(Var *v, Boolean freeValue) { if (!(v->flags & VAR_FROM_ENV)) return FALSE; free(v->name_freeIt); Buf_Destroy(&v->val, freeValue); free(v); return TRUE; } /* Add a new variable of the given name and value to 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, VarSet_Flags flags) +VarAdd(const char *name, const char *val, GNode *ctxt, VarSetFlags flags) { HashEntry *he = HashTable_CreateEntry(&ctxt->context, name, NULL); Var *v = VarNew(he->key /* aliased */, NULL, val, flags & VAR_SET_READONLY ? VAR_READONLY : 0); HashEntry_Set(he, v); if (!(ctxt->flags & INTERNAL)) { VAR_DEBUG3("%s:%s = %s\n", ctxt->name, name, val); } } /* Remove a variable from a context, freeing all related memory as well. * The variable name is expanded once. */ void Var_Delete(const char *name, GNode *ctxt) { char *name_freeIt = NULL; HashEntry *he; if (strchr(name, '$') != NULL) { (void)Var_Subst(name, VAR_GLOBAL, VARE_WANTRES, &name_freeIt); /* TODO: handle errors */ name = name_freeIt; } he = HashTable_FindEntry(&ctxt->context, name); VAR_DEBUG3("%s:delete %s%s\n", ctxt->name, name, he != NULL ? "" : " (not found)"); free(name_freeIt); if (he != NULL) { Var *v = HashEntry_Get(he); if (v->flags & VAR_EXPORTED) unsetenv(v->name); if (strcmp(v->name, MAKE_EXPORTED) == 0) var_exportedVars = VAR_EXPORTED_NONE; assert(v->name_freeIt == NULL); HashTable_DeleteEntry(&ctxt->context, he); Buf_Destroy(&v->val, TRUE); free(v); } } static Boolean MayExport(const char *name) { if (name[0] == '.') return FALSE; /* skip internals */ if (name[0] == '-') return FALSE; /* skip misnamed variables */ if (name[1] == '\0') { /* * 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 FALSE; } } return TRUE; } /* * Export a single variable. * 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 Boolean Var_Export1(const char *name, VarExportFlags flags) { VarExportFlags parent = flags & VAR_EXPORT_PARENT; Var *v; char *val; if (!MayExport(name)) return FALSE; - v = VarFind(name, VAR_GLOBAL, 0); + v = VarFind(name, VAR_GLOBAL, FALSE); if (v == NULL) return FALSE; if (!parent && (v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT)) return FALSE; /* nothing to do */ val = Buf_GetAll(&v->val, NULL); if (!(flags & VAR_EXPORT_LITERAL) && strchr(val, '$') != NULL) { char *expr; if (parent) { /* * Flag the variable as something we need to re-export. * No point actually exporting it now though, * the child process can do it at the last minute. */ v->flags |= VAR_EXPORTED | VAR_REEXPORT; return TRUE; } if (v->flags & VAR_IN_USE) { /* * We recursed while exporting in a child. * This isn't going to end well, just skip it. */ return FALSE; } /* XXX: name is injected without escaping it */ expr = str_concat3("${", name, "}"); (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &val); /* TODO: handle errors */ setenv(name, val, 1); free(val); free(expr); } else { if (parent) v->flags &= ~(unsigned)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 TRUE; } /* * This gets called from our child processes. */ void Var_ExportVars(void) { char *val; /* * 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. */ char tmp[BUFSIZ]; - snprintf(tmp, sizeof(tmp), "%d", makelevel + 1); + snprintf(tmp, sizeof tmp, "%d", makelevel + 1); setenv(MAKE_LEVEL_ENV, tmp, 1); if (var_exportedVars == VAR_EXPORTED_NONE) return; if (var_exportedVars == VAR_EXPORTED_ALL) { HashIter hi; /* Ouch! Exporting all variables at once is crazy... */ HashIter_Init(&hi, &VAR_GLOBAL->context); while (HashIter_Next(&hi) != NULL) { Var *var = hi.entry->value; - Var_Export1(var->name, 0); + Var_Export1(var->name, VAR_EXPORT_NORMAL); } return; } (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", VAR_GLOBAL, VARE_WANTRES, &val); /* TODO: handle errors */ if (*val) { Words words = Str_Words(val, FALSE); size_t i; for (i = 0; i < words.len; i++) - Var_Export1(words.words[i], 0); + Var_Export1(words.words[i], VAR_EXPORT_NORMAL); Words_Free(words); } free(val); } /* * This is called when .export is seen or .MAKE.EXPORTED is modified. * * It is also called when any exported variable is modified. * XXX: Is it really? * * str has the format "[-env|-literal] varname...". */ void Var_Export(const char *str, Boolean isExport) { VarExportFlags flags; char *val; if (isExport && str[0] == '\0') { var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */ return; } if (isExport && strncmp(str, "-env", 4) == 0) { str += 4; flags = 0; } else if (isExport && strncmp(str, "-literal", 8) == 0) { str += 8; flags = VAR_EXPORT_LITERAL; } else { flags = VAR_EXPORT_PARENT; } (void)Var_Subst(str, VAR_GLOBAL, VARE_WANTRES, &val); /* TODO: handle errors */ if (val[0] != '\0') { Words words = Str_Words(val, FALSE); size_t i; for (i = 0; i < words.len; i++) { const char *name = words.words[i]; if (Var_Export1(name, flags)) { if (var_exportedVars == VAR_EXPORTED_NONE) var_exportedVars = VAR_EXPORTED_SOME; if (isExport && (flags & VAR_EXPORT_PARENT)) { Var_Append(MAKE_EXPORTED, name, VAR_GLOBAL); } } } Words_Free(words); } free(val); } extern char **environ; /* * This is called when .unexport[-env] is seen. * * str must have the form "unexport[-env] varname...". */ void Var_UnExport(const char *str) { const char *varnames; char *varnames_freeIt; Boolean unexport_env; varnames = NULL; varnames_freeIt = NULL; str += strlen("unexport"); unexport_env = strncmp(str, "-env", 4) == 0; if (unexport_env) { const char *cp; 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 *)); } /* Note: we cannot safely free() the original environ. */ environ = savedEnv = newenv; newenv[0] = NULL; newenv[1] = NULL; if (cp && *cp) setenv(MAKE_LEVEL_ENV, cp, 1); } else { cpp_skip_whitespace(&str); if (str[0] != '\0') varnames = str; } if (varnames == NULL) { /* Using .MAKE.EXPORTED */ (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", VAR_GLOBAL, VARE_WANTRES, &varnames_freeIt); /* TODO: handle errors */ varnames = varnames_freeIt; } { Var *v; size_t i; Words words = Str_Words(varnames, FALSE); for (i = 0; i < words.len; i++) { const char *varname = words.words[i]; - v = VarFind(varname, VAR_GLOBAL, 0); + v = VarFind(varname, VAR_GLOBAL, FALSE); if (v == NULL) { VAR_DEBUG1("Not unexporting \"%s\" (not found)\n", varname); continue; } VAR_DEBUG1("Unexporting \"%s\"\n", varname); if (!unexport_env && (v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT)) unsetenv(v->name); v->flags &= ~(unsigned)(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 (varnames == str) { /* XXX: v->name is injected without escaping it */ char *expr = str_concat3("${" MAKE_EXPORTED ":N", v->name, "}"); char *cp; (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &cp); /* TODO: handle errors */ Var_Set(MAKE_EXPORTED, cp, VAR_GLOBAL); free(cp); free(expr); } } Words_Free(words); if (varnames != str) { Var_Delete(MAKE_EXPORTED, VAR_GLOBAL); free(varnames_freeIt); } } } /* See Var_Set for documentation. */ void -Var_Set_with_flags(const char *name, const char *val, GNode *ctxt, - VarSet_Flags flags) +Var_SetWithFlags(const char *name, const char *val, GNode *ctxt, + VarSetFlags flags) { const char *unexpanded_name = name; char *name_freeIt = NULL; Var *v; assert(val != NULL); if (strchr(name, '$') != NULL) { (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt); /* TODO: handle errors */ name = name_freeIt; } if (name[0] == '\0') { VAR_DEBUG2("Var_Set(\"%s\", \"%s\", ...) " "name expands to empty string - ignored\n", unexpanded_name, val); free(name_freeIt); return; } if (ctxt == VAR_GLOBAL) { - v = VarFind(name, VAR_CMDLINE, 0); + v = VarFind(name, VAR_CMDLINE, FALSE); if (v != NULL) { if (v->flags & VAR_FROM_CMD) { VAR_DEBUG3("%s:%s = %s ignored!\n", ctxt->name, name, val); goto out; } VarFreeEnv(v, TRUE); } } /* * 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... */ - v = VarFind(name, ctxt, 0); + v = VarFind(name, ctxt, FALSE); if (v == NULL) { - if (ctxt == VAR_CMDLINE && !(flags & VAR_NO_EXPORT)) { + if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT)) { /* * 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. */ /* XXX: name is expanded for the second time */ Var_Delete(name, VAR_GLOBAL); } VarAdd(name, val, ctxt, flags); } else { if ((v->flags & VAR_READONLY) && !(flags & VAR_SET_READONLY)) { VAR_DEBUG3("%s:%s = %s ignored (read-only)\n", ctxt->name, name, val); goto out; } Buf_Empty(&v->val); Buf_AddStr(&v->val, val); VAR_DEBUG3("%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) * Other than internals. */ - if (ctxt == VAR_CMDLINE && !(flags & VAR_NO_EXPORT) && name[0] != '.') { + if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT) && name[0] != '.') { if (v == NULL) - v = VarFind(name, ctxt, 0); /* we just added it */ + v = VarFind(name, ctxt, FALSE); /* we just added it */ 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 (!opts.varNoExportEnv) setenv(name, val, 1); Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL); } if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0) - save_dollars = s2Boolean(val, save_dollars); + save_dollars = ParseBoolean(val, save_dollars); out: free(name_freeIt); if (v != NULL) VarFreeEnv(v, TRUE); } /*- *----------------------------------------------------------------------- * Var_Set -- * Set the variable name to the value val in the given context. * * If the variable doesn't yet exist, it is created. * Otherwise the new value overwrites and replaces the old value. * * Input: * name name of the variable to set, is expanded once * val value to give to the variable * ctxt context in which to set it * * 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_CMDLINE, * only VAR_CMDLINE->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_CMDLINE from the command line and skip it if so. *----------------------------------------------------------------------- */ void Var_Set(const char *name, const char *val, GNode *ctxt) { - Var_Set_with_flags(name, val, ctxt, 0); + Var_SetWithFlags(name, val, ctxt, VAR_SET_NONE); } /*- *----------------------------------------------------------------------- * Var_Append -- * The variable of the given name has the given value appended to it in * the given context. * * If the variable doesn't exist, it is created. Otherwise the strings * are concatenated, with a space in between. * * Input: * name name of the variable to modify, is expanded once * val string to append to it * ctxt context in which this should occur * * 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) { char *name_freeIt = NULL; Var *v; assert(val != NULL); if (strchr(name, '$') != NULL) { const char *unexpanded_name = name; (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt); /* TODO: handle errors */ name = name_freeIt; if (name[0] == '\0') { VAR_DEBUG2("Var_Append(\"%s\", \"%s\", ...) " "name expands to empty string - ignored\n", unexpanded_name, val); free(name_freeIt); return; } } v = VarFind(name, ctxt, ctxt == VAR_GLOBAL); if (v == NULL) { /* XXX: name is expanded for the second time */ Var_Set(name, val, ctxt); } else if (v->flags & VAR_READONLY) { VAR_DEBUG1("Ignoring append to %s since it is read-only\n", name); } else if (ctxt == VAR_CMDLINE || !(v->flags & VAR_FROM_CMD)) { Buf_AddByte(&v->val, ' '); Buf_AddStr(&v->val, val); VAR_DEBUG3("%s:%s = %s\n", ctxt->name, name, Buf_GetAll(&v->val, NULL)); if (v->flags & VAR_FROM_ENV) { - HashEntry *h; - /* * 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 &= ~(unsigned)VAR_FROM_ENV; - h = HashTable_CreateEntry(&ctxt->context, name, NULL); - HashEntry_Set(h, v); + /* This is the only place where a variable is created whose + * v->name is not the same as ctxt->context->key. */ + HashTable_Set(&ctxt->context, name, v); } } free(name_freeIt); } /* See if the given variable exists, in the given context or in other * fallback contexts. * * Input: * name Variable to find, is expanded once * ctxt Context in which to start search */ Boolean Var_Exists(const char *name, GNode *ctxt) { char *name_freeIt = NULL; Var *v; if (strchr(name, '$') != NULL) { (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt); /* TODO: handle errors */ name = name_freeIt; } v = VarFind(name, ctxt, TRUE); free(name_freeIt); if (v == NULL) return FALSE; (void)VarFreeEnv(v, TRUE); return TRUE; } /*- *----------------------------------------------------------------------- * Var_Value -- * Return the unexpanded value of the given variable in the given * context, or the usual contexts. * * Input: * name name to find, is not expanded any further * ctxt context in which to search for it * * Results: * The value if the variable exists, NULL if it doesn't. * If the returned value is not NULL, the caller must free * out_freeIt when the returned value is no longer needed. *----------------------------------------------------------------------- */ const char * Var_Value(const char *name, GNode *ctxt, void **out_freeIt) { Var *v = VarFind(name, ctxt, TRUE); char *value; *out_freeIt = NULL; if (v == NULL) return NULL; value = Buf_GetAll(&v->val, NULL); if (VarFreeEnv(v, FALSE)) *out_freeIt = value; return value; } /* Return the unexpanded variable value from this node, without trying to look * up the variable in any other context. */ const char * Var_ValueDirect(const char *name, GNode *ctxt) { Var *v = VarFind(name, ctxt, FALSE); return v != NULL ? Buf_GetAll(&v->val, NULL) : NULL; } /* SepBuf is a string being built from words, interleaved with separators. */ typedef struct SepBuf { Buffer buf; Boolean needSep; char sep; /* usually ' ', but see the :ts modifier */ } SepBuf; static void SepBuf_Init(SepBuf *buf, char sep) { - Buf_Init(&buf->buf, 32 /* bytes */); + Buf_InitSize(&buf->buf, 32); buf->needSep = FALSE; buf->sep = sep; } static void SepBuf_Sep(SepBuf *buf) { buf->needSep = TRUE; } static void SepBuf_AddBytes(SepBuf *buf, const char *mem, size_t mem_size) { if (mem_size == 0) return; if (buf->needSep && buf->sep != '\0') { Buf_AddByte(&buf->buf, buf->sep); buf->needSep = FALSE; } Buf_AddBytes(&buf->buf, mem, mem_size); } static void SepBuf_AddBytesBetween(SepBuf *buf, const char *start, const char *end) { SepBuf_AddBytes(buf, start, (size_t)(end - start)); } static void SepBuf_AddStr(SepBuf *buf, const char *str) { SepBuf_AddBytes(buf, str, strlen(str)); } static char * SepBuf_Destroy(SepBuf *buf, Boolean free_buf) { return Buf_Destroy(&buf->buf, free_buf); } /* This callback for ModifyWords gets a single word from a variable expression * and typically adds a modification of this word to the buffer. It may also * do nothing or add several words. * * For example, in ${:Ua b c:M*2}, the callback is called 3 times, once for * each word of "a b c". */ typedef void (*ModifyWordsCallback)(const char *word, SepBuf *buf, void *data); /* Callback for ModifyWords to implement the :H modifier. * Add the dirname of the given word to the buffer. */ static void ModifyWord_Head(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { const char *slash = strrchr(word, '/'); if (slash != NULL) SepBuf_AddBytesBetween(buf, word, slash); else SepBuf_AddStr(buf, "."); } /* Callback for ModifyWords to implement the :T modifier. * Add the basename of the given word to the buffer. */ static void ModifyWord_Tail(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { const char *slash = strrchr(word, '/'); const char *base = slash != NULL ? slash + 1 : word; SepBuf_AddStr(buf, base); } /* Callback for ModifyWords to implement the :E modifier. * Add the filename suffix of the given word to the buffer, if it exists. */ static void ModifyWord_Suffix(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { const char *dot = strrchr(word, '.'); if (dot != NULL) SepBuf_AddStr(buf, dot + 1); } /* Callback for ModifyWords to implement the :R modifier. * Add the basename of the given word to the buffer. */ static void ModifyWord_Root(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { const char *dot = strrchr(word, '.'); size_t len = dot != NULL ? (size_t)(dot - word) : strlen(word); SepBuf_AddBytes(buf, word, len); } /* Callback for ModifyWords to implement the :M modifier. * Place the word in the buffer if it matches the given pattern. */ static void ModifyWord_Match(const char *word, SepBuf *buf, void *data) { const char *pattern = data; VAR_DEBUG2("VarMatch [%s] [%s]\n", word, pattern); if (Str_Match(word, pattern)) SepBuf_AddStr(buf, word); } /* Callback for ModifyWords to implement the :N modifier. * Place the word in the buffer if it doesn't match the given pattern. */ static void ModifyWord_NoMatch(const char *word, SepBuf *buf, void *data) { const char *pattern = data; if (!Str_Match(word, pattern)) SepBuf_AddStr(buf, word); } #ifdef SYSVVARSUB /* Check word against pattern for a match (% is a wildcard). * * Input: * word Word to examine * pattern Pattern to examine against * * Results: * Returns the start of the match, or NULL. * out_match_len returns the length of the match, if any. * out_hasPercent returns whether the pattern contains a percent. */ static const char * SysVMatch(const char *word, const char *pattern, size_t *out_match_len, Boolean *out_hasPercent) { const char *p = pattern; const char *w = word; const char *percent; size_t w_len; size_t p_len; const char *w_tail; *out_hasPercent = FALSE; percent = strchr(p, '%'); if (percent != NULL) { /* ${VAR:...%...=...} */ *out_hasPercent = TRUE; if (*w == '\0') return NULL; /* empty word does not match pattern */ /* check that the prefix matches */ for (; p != percent && *w != '\0' && *w == *p; w++, p++) continue; if (p != percent) return NULL; /* No match */ p++; /* Skip the percent */ if (*p == '\0') { /* No more pattern, return the rest of the string */ *out_match_len = strlen(w); return w; } } /* Test whether the tail matches */ w_len = strlen(w); p_len = strlen(p); if (w_len < p_len) return NULL; w_tail = w + w_len - p_len; if (memcmp(p, w_tail, p_len) != 0) return NULL; *out_match_len = (size_t)(w_tail - w); return w; } struct ModifyWord_SYSVSubstArgs { GNode *ctx; const char *lhs; const char *rhs; }; /* Callback for ModifyWords to implement the :%.from=%.to modifier. */ static void ModifyWord_SYSVSubst(const char *word, SepBuf *buf, void *data) { const struct ModifyWord_SYSVSubstArgs *args = data; char *rhs_expanded; const char *rhs; const char *percent; size_t match_len; Boolean lhsPercent; const char *match = SysVMatch(word, args->lhs, &match_len, &lhsPercent); if (match == NULL) { SepBuf_AddStr(buf, word); return; } /* Append rhs to the buffer, substituting the first '%' with the * match, but only if the lhs had a '%' as well. */ (void)Var_Subst(args->rhs, args->ctx, VARE_WANTRES, &rhs_expanded); /* TODO: handle errors */ rhs = rhs_expanded; percent = strchr(rhs, '%'); if (percent != NULL && lhsPercent) { /* Copy the prefix of the replacement pattern */ SepBuf_AddBytesBetween(buf, rhs, percent); rhs = percent + 1; } if (percent != NULL || !lhsPercent) SepBuf_AddBytes(buf, match, match_len); /* Append the suffix of the replacement pattern */ SepBuf_AddStr(buf, rhs); free(rhs_expanded); } #endif struct ModifyWord_SubstArgs { const char *lhs; size_t lhsLen; const char *rhs; size_t rhsLen; VarPatternFlags pflags; Boolean matched; }; /* Callback for ModifyWords to implement the :S,from,to, modifier. * Perform a string substitution on the given word. */ static void ModifyWord_Subst(const char *word, SepBuf *buf, void *data) { size_t wordLen = strlen(word); struct ModifyWord_SubstArgs *args = data; const char *match; if ((args->pflags & VARP_SUB_ONE) && args->matched) goto nosub; if (args->pflags & VARP_ANCHOR_START) { if (wordLen < args->lhsLen || memcmp(word, args->lhs, args->lhsLen) != 0) goto nosub; if ((args->pflags & VARP_ANCHOR_END) && wordLen != args->lhsLen) goto nosub; /* :S,^prefix,replacement, or :S,^whole$,replacement, */ SepBuf_AddBytes(buf, args->rhs, args->rhsLen); SepBuf_AddBytes(buf, word + args->lhsLen, wordLen - args->lhsLen); args->matched = TRUE; return; } if (args->pflags & VARP_ANCHOR_END) { const char *start; if (wordLen < args->lhsLen) goto nosub; start = word + (wordLen - args->lhsLen); if (memcmp(start, args->lhs, args->lhsLen) != 0) goto nosub; /* :S,suffix$,replacement, */ SepBuf_AddBytesBetween(buf, word, start); SepBuf_AddBytes(buf, args->rhs, args->rhsLen); args->matched = TRUE; return; } if (args->lhs[0] == '\0') goto nosub; /* unanchored case, may match more than once */ while ((match = strstr(word, args->lhs)) != NULL) { SepBuf_AddBytesBetween(buf, word, match); SepBuf_AddBytes(buf, args->rhs, args->rhsLen); args->matched = TRUE; wordLen -= (size_t)(match - word) + args->lhsLen; word += (size_t)(match - word) + args->lhsLen; if (wordLen == 0 || !(args->pflags & VARP_SUB_GLOBAL)) break; } nosub: SepBuf_AddBytes(buf, word, wordLen); } #ifndef NO_REGEX /* Print the error caused by a regcomp or regexec call. */ static void -VarREError(int reerr, regex_t *pat, const char *str) +VarREError(int reerr, const regex_t *pat, const char *str) { - size_t errlen = regerror(reerr, pat, 0, 0); + size_t errlen = regerror(reerr, pat, NULL, 0); char *errbuf = bmake_malloc(errlen); regerror(reerr, pat, errbuf, errlen); Error("%s: %s", str, errbuf); free(errbuf); } struct ModifyWord_SubstRegexArgs { regex_t re; size_t nsub; char *replace; VarPatternFlags pflags; Boolean matched; }; /* Callback for ModifyWords to implement the :C/from/to/ modifier. * Perform a regex substitution on the given word. */ static void ModifyWord_SubstRegex(const char *word, SepBuf *buf, void *data) { struct ModifyWord_SubstRegexArgs *args = data; int xrv; const char *wp = word; char *rp; int flags = 0; regmatch_t m[10]; if ((args->pflags & VARP_SUB_ONE) && args->matched) goto nosub; tryagain: xrv = regexec(&args->re, wp, args->nsub, m, flags); switch (xrv) { case 0: args->matched = TRUE; SepBuf_AddBytes(buf, wp, (size_t)m[0].rm_so); for (rp = args->replace; *rp; rp++) { if (*rp == '\\' && (rp[1] == '&' || rp[1] == '\\')) { SepBuf_AddBytes(buf, rp + 1, 1); rp++; continue; } if (*rp == '&') { SepBuf_AddBytesBetween(buf, wp + m[0].rm_so, wp + m[0].rm_eo); continue; } if (*rp != '\\' || !ch_isdigit(rp[1])) { SepBuf_AddBytes(buf, rp, 1); continue; } { /* \0 to \9 backreference */ size_t n = (size_t)(rp[1] - '0'); rp++; if (n >= args->nsub) { Error("No subexpression \\%zu", n); } else if (m[n].rm_so == -1) { Error("No match for subexpression \\%zu", n); } else { SepBuf_AddBytesBetween(buf, wp + m[n].rm_so, wp + m[n].rm_eo); } } } wp += m[0].rm_eo; if (args->pflags & VARP_SUB_GLOBAL) { flags |= REG_NOTBOL; if (m[0].rm_so == 0 && m[0].rm_eo == 0) { SepBuf_AddBytes(buf, wp, 1); wp++; } if (*wp) goto tryagain; } if (*wp) { SepBuf_AddStr(buf, wp); } break; default: VarREError(xrv, &args->re, "Unexpected regex error"); /* FALLTHROUGH */ case REG_NOMATCH: nosub: SepBuf_AddStr(buf, wp); break; } } #endif struct ModifyWord_LoopArgs { GNode *ctx; char *tvar; /* name of temporary variable */ char *str; /* string to expand */ VarEvalFlags eflags; }; /* Callback for ModifyWords to implement the :@var@...@ modifier of ODE make. */ static void ModifyWord_Loop(const char *word, SepBuf *buf, void *data) { const struct ModifyWord_LoopArgs *args; char *s; if (word[0] == '\0') return; args = data; - Var_Set_with_flags(args->tvar, word, args->ctx, VAR_NO_EXPORT); + Var_SetWithFlags(args->tvar, word, args->ctx, VAR_SET_NO_EXPORT); (void)Var_Subst(args->str, args->ctx, args->eflags, &s); /* TODO: handle errors */ VAR_DEBUG4("ModifyWord_Loop: " "in \"%s\", replace \"%s\" with \"%s\" to \"%s\"\n", word, args->tvar, args->str, s); if (s[0] == '\n' || Buf_EndsWith(&buf->buf, '\n')) buf->needSep = FALSE; SepBuf_AddStr(buf, s); free(s); } /* The :[first..last] modifier selects words from the expression. * It can also reverse the words. */ static char * VarSelectWords(char sep, Boolean oneBigWord, const char *str, int first, int last) { Words words; int len, start, end, step; int i; SepBuf buf; SepBuf_Init(&buf, sep); if (oneBigWord) { /* fake what Str_Words() would do if there were only one word */ words.len = 1; - words.words = bmake_malloc((words.len + 1) * sizeof(char *)); + words.words = bmake_malloc((words.len + 1) * sizeof(words.words[0])); words.freeIt = bmake_strdup(str); words.words[0] = words.freeIt; words.words[1] = NULL; } else { words = Str_Words(str, FALSE); } /* * Now sanitize the given range. * If first or last are negative, convert them to the positive equivalents * (-1 gets converted to ac, -2 gets converted to (ac - 1), etc.). */ len = (int)words.len; if (first < 0) first += len + 1; if (last < 0) last += len + 1; /* * We avoid scanning more of the list than we need to. */ if (first > last) { start = (first > len ? len : first) - 1; end = last < 1 ? 0 : last - 1; step = -1; } else { start = first < 1 ? 0 : first - 1; end = last > len ? len : last; step = 1; } for (i = start; (step < 0) == (i >= end); i += step) { SepBuf_AddStr(&buf, words.words[i]); SepBuf_Sep(&buf); } Words_Free(words); return SepBuf_Destroy(&buf, FALSE); } /* Callback for ModifyWords to implement the :tA modifier. * Replace each word with the result of realpath() if successful. */ static void ModifyWord_Realpath(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) { struct stat st; char rbuf[MAXPATHLEN]; const char *rp = cached_realpath(word, rbuf); if (rp != NULL && *rp == '/' && stat(rp, &st) == 0) word = rp; SepBuf_AddStr(buf, word); } /* Modify each of the words of the passed string using the given function. * * Input: * str String whose words should be modified * modifyWord Function that modifies a single word * modifyWord_args Custom arguments for modifyWord * * Results: * A string of all the words modified appropriately. *----------------------------------------------------------------------- */ static char * ModifyWords(const char *str, ModifyWordsCallback modifyWord, void *modifyWord_args, Boolean oneBigWord, char sep) { SepBuf result; Words words; size_t i; if (oneBigWord) { SepBuf_Init(&result, sep); modifyWord(str, &result, modifyWord_args); return SepBuf_Destroy(&result, FALSE); } SepBuf_Init(&result, sep); words = Str_Words(str, FALSE); VAR_DEBUG2("ModifyWords: split \"%s\" into %zu words\n", str, words.len); for (i = 0; i < words.len; i++) { modifyWord(words.words[i], &result, modifyWord_args); if (Buf_Len(&result.buf) > 0) SepBuf_Sep(&result); } Words_Free(words); return SepBuf_Destroy(&result, FALSE); } static char * Words_JoinFree(Words words) { Buffer buf; size_t i; - Buf_Init(&buf, 0); + Buf_Init(&buf); for (i = 0; i < words.len; i++) { if (i != 0) Buf_AddByte(&buf, ' '); /* XXX: st->sep, for consistency */ Buf_AddStr(&buf, words.words[i]); } Words_Free(words); return Buf_Destroy(&buf, FALSE); } /* Remove adjacent duplicate words. */ static char * VarUniq(const char *str) { Words words = Str_Words(str, FALSE); if (words.len > 1) { size_t i, j; for (j = 0, i = 1; i < words.len; i++) if (strcmp(words.words[i], words.words[j]) != 0 && (++j != i)) words.words[j] = words.words[i]; words.len = j + 1; } return Words_JoinFree(words); } /* Quote shell meta-characters and space characters in the string. * If quoteDollar is set, also quote and double any '$' characters. */ static char * VarQuote(const char *str, Boolean quoteDollar) { Buffer buf; - Buf_Init(&buf, 0); + Buf_Init(&buf); for (; *str != '\0'; str++) { if (*str == '\n') { const char *newline = Shell_GetNewline(); if (newline == NULL) newline = "\\\n"; Buf_AddStr(&buf, newline); continue; } if (ch_isspace(*str) || is_shell_metachar((unsigned char)*str)) Buf_AddByte(&buf, '\\'); Buf_AddByte(&buf, *str); if (quoteDollar && *str == '$') Buf_AddStr(&buf, "\\$"); } return Buf_Destroy(&buf, FALSE); } /* Compute the 32-bit hash of the given string, using the MurmurHash3 * algorithm. Output is encoded as 8 hex digits, in Little Endian order. */ static char * VarHash(const char *str) { static const char hexdigits[16] = "0123456789abcdef"; const unsigned char *ustr = (const unsigned char *)str; uint32_t h = 0x971e137bU; uint32_t c1 = 0x95543787U; uint32_t c2 = 0x2ad7eb25U; size_t len2 = strlen(str); char *buf; size_t i; size_t len; for (len = len2; len; ) { uint32_t k = 0; switch (len) { default: k = ((uint32_t)ustr[3] << 24) | ((uint32_t)ustr[2] << 16) | ((uint32_t)ustr[1] << 8) | (uint32_t)ustr[0]; len -= 4; ustr += 4; break; case 3: k |= (uint32_t)ustr[2] << 16; /* FALLTHROUGH */ case 2: k |= (uint32_t)ustr[1] << 8; /* FALLTHROUGH */ case 1: k |= (uint32_t)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 ^= (uint32_t)len2; h *= 0x85ebca6b; h ^= h >> 13; h *= 0xc2b2ae35; h ^= h >> 16; buf = bmake_malloc(9); for (i = 0; i < 8; i++) { buf[i] = hexdigits[h & 0x0f]; h >>= 4; } buf[8] = '\0'; return buf; } static char * VarStrftime(const char *fmt, Boolean zulu, time_t tim) { char buf[BUFSIZ]; - if (!tim) + if (tim == 0) time(&tim); - if (!*fmt) + if (*fmt == '\0') fmt = "%c"; - strftime(buf, sizeof(buf), fmt, zulu ? gmtime(&tim) : localtime(&tim)); + strftime(buf, sizeof buf, fmt, zulu ? gmtime(&tim) : localtime(&tim)); - buf[sizeof(buf) - 1] = '\0'; + buf[sizeof buf - 1] = '\0'; return bmake_strdup(buf); } -/* The ApplyModifier functions all work in the same way. They get the - * current parsing position (pp) and parse the modifier from there. The - * modifier typically lasts until the next ':', or a closing '}' or ')' +/* + * The ApplyModifier functions take an expression that is being evaluated. + * Their task is to apply a single modifier to the expression. + * To do this, they parse the modifier and its parameters from pp and apply + * the parsed modifier to the current value of the expression, generating a + * new value from it. + * + * The modifier typically lasts until the next ':', or a closing '}' or ')' * (taken from st->endc), or the end of the string (parse error). * * The high-level behavior of these functions is: * * 1. parse the modifier * 2. evaluate the modifier * 3. housekeeping * * Parsing the modifier * * If parsing succeeds, the parsing position *pp is updated to point to the * first character following the modifier, which typically is either ':' or - * st->endc. + * st->endc. The modifier doesn't have to check for this delimiter character, + * this is done by ApplyModifiers. * + * XXX: As of 2020-11-15, some modifiers such as :S, :C, :P, :L do not + * need to be followed by a ':' or endc; this was an unintended mistake. + * * If parsing fails because of a missing delimiter (as in the :S, :C or :@ * modifiers), return AMR_CLEANUP. * * If parsing fails because the modifier is unknown, return AMR_UNKNOWN to * try the SysV modifier ${VAR:from=to} as fallback. This should only be * done as long as there have been no side effects from evaluating nested * variables, to avoid evaluating them more than once. In this case, the - * parsing position must not be updated. (XXX: Why not? The original parsing - * position is well-known in ApplyModifiers.) + * parsing position may or may not be updated. (XXX: Why not? The original + * parsing position is well-known in ApplyModifiers.) * * If parsing fails and the SysV modifier ${VAR:from=to} should not be used * as a fallback, either issue an error message using Error or Parse_Error * and then return AMR_CLEANUP, or return AMR_BAD for the default error * message. Both of these return values will stop processing the variable * expression. (XXX: As of 2020-08-23, evaluation of the whole string * continues nevertheless after skipping a few bytes, which essentially is * undefined behavior. Not in the sense of C, but still it's impossible to * predict what happens in the parser.) * * Evaluating the modifier * * After parsing, the modifier is evaluated. The side effects from evaluating * nested variable expressions in the modifier text often already happen * during parsing though. * * Evaluating the modifier usually takes the current value of the variable - * expression from st->val, or the variable name from st->v->name and stores + * expression from st->val, or the variable name from st->var->name and stores * the result in st->newVal. * * If evaluating fails (as of 2020-08-23), an error message is printed using * Error. This function has no side-effects, it really just prints the error * message. Processing the expression continues as if everything were ok. * XXX: This should be fixed by adding proper error handling to Var_Subst, * Var_Parse, ApplyModifiers and ModifyWords. * * Housekeeping * * Some modifiers such as :D and :U turn undefined expressions into defined * expressions (see VEF_UNDEF, VEF_DEF). * * Some modifiers need to free some memory. */ typedef enum VarExprFlags { /* The variable expression is based on an undefined variable. */ VEF_UNDEF = 0x01, /* The variable expression started as an undefined expression, but one * of the modifiers (such as :D or :U) has turned the expression from * undefined to defined. */ VEF_DEF = 0x02 } VarExprFlags; ENUM_FLAGS_RTTI_2(VarExprFlags, VEF_UNDEF, VEF_DEF); typedef struct ApplyModifiersState { const char startc; /* '\0' or '{' or '(' */ const char endc; /* '\0' or '}' or ')' */ - Var * const v; + Var * const var; GNode * const ctxt; const VarEvalFlags eflags; char *val; /* The old value of the expression, * before applying the modifier, never NULL */ char *newVal; /* The new value of the expression, * after applying the modifier, never NULL */ char sep; /* Word separator in expansions * (see the :ts modifier) */ Boolean oneBigWord; /* TRUE if some modifiers that otherwise split * the variable value into words, like :S and * :C, treat the variable value as a single big * word, possibly containing spaces. */ VarExprFlags exprFlags; } ApplyModifiersState; static void ApplyModifiersState_Define(ApplyModifiersState *st) { if (st->exprFlags & VEF_UNDEF) st->exprFlags |= VEF_DEF; } typedef enum ApplyModifierResult { AMR_OK, /* Continue parsing */ AMR_UNKNOWN, /* Not a match, try other modifiers as well */ AMR_BAD, /* Error out with "Bad modifier" message */ AMR_CLEANUP /* Error out without error message */ } ApplyModifierResult; /* Allow backslashes to escape the delimiter, $, and \, but don't touch other * backslashes. */ static Boolean IsEscapedModifierPart(const char *p, char delim, struct ModifyWord_SubstArgs *subst) { if (p[0] != '\\') return FALSE; if (p[1] == delim || p[1] == '\\' || p[1] == '$') return TRUE; return p[1] == '&' && subst != NULL; } /* * Parse a part of a modifier such as the "from" and "to" in :S/from/to/ or * the "var" or "replacement ${var}" in :@var@replacement ${var}@, up to and * including the next unescaped delimiter. The delimiter, as well as the * backslash or the dollar, can be escaped with a backslash. * * Return the parsed (and possibly expanded) string, or NULL if no delimiter * was found. On successful return, the parsing position pp points right * after the delimiter. The delimiter is not included in the returned * value though. */ static VarParseResult ParseModifierPart( const char **pp, /* The parsing position, updated upon return */ char delim, /* Parsing stops at this delimiter */ VarEvalFlags eflags, /* Flags for evaluating nested variables; * if VARE_WANTRES is not set, the text is * only parsed */ ApplyModifiersState *st, char **out_part, size_t *out_length, /* Optionally stores the length of the returned * string, just to save another strlen call. */ VarPatternFlags *out_pflags,/* For the first part of the :S modifier, * sets the VARP_ANCHOR_END flag if the last * character of the pattern is a $. */ struct ModifyWord_SubstArgs *subst /* For the second part of the :S modifier, * allow ampersands to be escaped and replace * unescaped ampersands with subst->lhs. */ ) { Buffer buf; const char *p; - Buf_Init(&buf, 0); + Buf_Init(&buf); /* * Skim through until the matching delimiter is found; pick up variable * expressions on the way. */ p = *pp; while (*p != '\0' && *p != delim) { const char *varstart; if (IsEscapedModifierPart(p, delim, subst)) { Buf_AddByte(&buf, p[1]); p += 2; continue; } if (*p != '$') { /* Unescaped, simple text */ if (subst != NULL && *p == '&') Buf_AddBytes(&buf, subst->lhs, subst->lhsLen); else Buf_AddByte(&buf, *p); p++; continue; } if (p[1] == delim) { /* Unescaped $ at end of pattern */ if (out_pflags != NULL) *out_pflags |= VARP_ANCHOR_END; else Buf_AddByte(&buf, *p); p++; continue; } if (eflags & VARE_WANTRES) { /* Nested variable, evaluated */ const char *nested_p = p; const char *nested_val; void *nested_val_freeIt; - VarEvalFlags nested_eflags = eflags & ~(unsigned)VARE_ASSIGN; + VarEvalFlags nested_eflags = eflags & ~(unsigned)VARE_KEEP_DOLLAR; (void)Var_Parse(&nested_p, st->ctxt, nested_eflags, &nested_val, &nested_val_freeIt); /* TODO: handle errors */ Buf_AddStr(&buf, nested_val); free(nested_val_freeIt); p += nested_p - p; continue; } /* XXX: This whole block is very similar to Var_Parse without * VARE_WANTRES. There may be subtle edge cases though that are * not yet covered in the unit tests and that are parsed differently, * depending on whether they are evaluated or not. * * This subtle difference is not documented in the manual page, * neither is the difference between parsing :D and :M documented. * No code should ever depend on these details, but who knows. */ varstart = p; /* Nested variable, only parsed */ if (p[1] == '(' || p[1] == '{') { /* * Find the end of this variable reference * and suck it in without further ado. * It will be interpreted later. */ char startc = p[1]; int endc = startc == '(' ? ')' : '}'; int depth = 1; for (p += 2; *p != '\0' && depth > 0; p++) { if (p[-1] != '\\') { if (*p == startc) depth++; if (*p == endc) depth--; } } Buf_AddBytesBetween(&buf, varstart, p); } else { Buf_AddByte(&buf, *varstart); p++; } } if (*p != delim) { *pp = p; - Error("Unfinished modifier for %s ('%c' missing)", st->v->name, delim); + Error("Unfinished modifier for %s ('%c' missing)", + st->var->name, delim); *out_part = NULL; return VPR_PARSE_MSG; } *pp = ++p; if (out_length != NULL) *out_length = Buf_Len(&buf); *out_part = Buf_Destroy(&buf, FALSE); VAR_DEBUG1("Modifier part: \"%s\"\n", *out_part); return VPR_OK; } /* Test whether mod starts with modname, followed by a delimiter. */ -static Boolean +MAKE_INLINE Boolean ModMatch(const char *mod, const char *modname, char endc) { size_t n = strlen(modname); return strncmp(mod, modname, n) == 0 && (mod[n] == endc || mod[n] == ':'); } /* Test whether mod starts with modname, followed by a delimiter or '='. */ -static inline Boolean +MAKE_INLINE Boolean ModMatchEq(const char *mod, const char *modname, char endc) { size_t n = strlen(modname); return strncmp(mod, modname, n) == 0 && (mod[n] == endc || mod[n] == ':' || mod[n] == '='); } static Boolean TryParseIntBase0(const char **pp, int *out_num) { char *end; long n; errno = 0; n = strtol(*pp, &end, 0); if ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE) return FALSE; if (n < INT_MIN || n > INT_MAX) return FALSE; *pp = end; *out_num = (int)n; return TRUE; } static Boolean TryParseSize(const char **pp, size_t *out_num) { char *end; unsigned long n; if (!ch_isdigit(**pp)) return FALSE; errno = 0; n = strtoul(*pp, &end, 10); if (n == ULONG_MAX && errno == ERANGE) return FALSE; if (n > SIZE_MAX) return FALSE; *pp = end; *out_num = (size_t)n; return TRUE; } static Boolean TryParseChar(const char **pp, int base, char *out_ch) { char *end; unsigned long n; if (!ch_isalnum(**pp)) return FALSE; errno = 0; n = strtoul(*pp, &end, base); if (n == ULONG_MAX && errno == ERANGE) return FALSE; if (n > UCHAR_MAX) return FALSE; *pp = end; *out_ch = (char)n; return TRUE; } /* :@var@...${var}...@ */ static ApplyModifierResult ApplyModifier_Loop(const char **pp, ApplyModifiersState *st) { struct ModifyWord_LoopArgs args; char prev_sep; - VarEvalFlags eflags = st->eflags & ~(unsigned)VARE_WANTRES; VarParseResult res; args.ctx = st->ctxt; (*pp)++; /* Skip the first '@' */ - res = ParseModifierPart(pp, '@', eflags, st, + res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.tvar, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; - if (DEBUG(LINT) && strchr(args.tvar, '$') != NULL) { + if (opts.lint && strchr(args.tvar, '$') != NULL) { Parse_Error(PARSE_FATAL, "In the :@ modifier of \"%s\", the variable name \"%s\" " "must not contain a dollar.", - st->v->name, args.tvar); + st->var->name, args.tvar); return AMR_CLEANUP; } - res = ParseModifierPart(pp, '@', eflags, st, + res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.str, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; - args.eflags = st->eflags & (VARE_UNDEFERR | VARE_WANTRES); + args.eflags = st->eflags & ~(unsigned)VARE_KEEP_DOLLAR; prev_sep = st->sep; st->sep = ' '; /* XXX: should be st->sep for consistency */ st->newVal = ModifyWords(st->val, ModifyWord_Loop, &args, st->oneBigWord, st->sep); st->sep = prev_sep; + /* XXX: Consider restoring the previous variable instead of deleting. */ Var_Delete(args.tvar, st->ctxt); free(args.tvar); free(args.str); return AMR_OK; } /* :Ddefined or :Uundefined */ static ApplyModifierResult ApplyModifier_Defined(const char **pp, ApplyModifiersState *st) { Buffer buf; const char *p; - VarEvalFlags eflags = st->eflags & ~(unsigned)VARE_WANTRES; - if (st->eflags & VARE_WANTRES) { + VarEvalFlags eflags = VARE_NONE; + if (st->eflags & VARE_WANTRES) if ((**pp == 'D') == !(st->exprFlags & VEF_UNDEF)) - eflags |= VARE_WANTRES; - } + eflags = st->eflags; - Buf_Init(&buf, 0); + Buf_Init(&buf); p = *pp + 1; while (*p != st->endc && *p != ':' && *p != '\0') { + /* XXX: This code is similar to the one in Var_Parse. + * See if the code can be merged. + * See also ApplyModifier_Match. */ + /* Escaped delimiter or other special character */ if (*p == '\\') { char c = p[1]; if (c == st->endc || c == ':' || c == '$' || c == '\\') { Buf_AddByte(&buf, c); p += 2; continue; } } /* Nested variable expression */ if (*p == '$') { const char *nested_val; void *nested_val_freeIt; (void)Var_Parse(&p, st->ctxt, eflags, &nested_val, &nested_val_freeIt); /* TODO: handle errors */ Buf_AddStr(&buf, nested_val); free(nested_val_freeIt); continue; } /* Ordinary text */ Buf_AddByte(&buf, *p); p++; } *pp = p; ApplyModifiersState_Define(st); if (eflags & VARE_WANTRES) { st->newVal = Buf_Destroy(&buf, FALSE); } else { st->newVal = st->val; Buf_Destroy(&buf, TRUE); } return AMR_OK; } /* :L */ static ApplyModifierResult ApplyModifier_Literal(const char **pp, ApplyModifiersState *st) { ApplyModifiersState_Define(st); - st->newVal = bmake_strdup(st->v->name); + st->newVal = bmake_strdup(st->var->name); (*pp)++; return AMR_OK; } static Boolean TryParseTime(const char **pp, time_t *out_time) { char *end; unsigned long n; if (!ch_isdigit(**pp)) return FALSE; errno = 0; n = strtoul(*pp, &end, 10); if (n == ULONG_MAX && errno == ERANGE) return FALSE; *pp = end; *out_time = (time_t)n; /* ignore possible truncation for now */ return TRUE; } /* :gmtime */ static ApplyModifierResult ApplyModifier_Gmtime(const char **pp, ApplyModifiersState *st) { time_t utc; const char *mod = *pp; if (!ModMatchEq(mod, "gmtime", st->endc)) return AMR_UNKNOWN; if (mod[6] == '=') { const char *arg = mod + 7; if (!TryParseTime(&arg, &utc)) { Parse_Error(PARSE_FATAL, "Invalid time value: %s\n", mod + 7); return AMR_CLEANUP; } *pp = arg; } else { utc = 0; *pp = mod + 6; } st->newVal = VarStrftime(st->val, TRUE, utc); return AMR_OK; } /* :localtime */ static ApplyModifierResult ApplyModifier_Localtime(const char **pp, ApplyModifiersState *st) { time_t utc; const char *mod = *pp; if (!ModMatchEq(mod, "localtime", st->endc)) return AMR_UNKNOWN; if (mod[9] == '=') { const char *arg = mod + 10; if (!TryParseTime(&arg, &utc)) { Parse_Error(PARSE_FATAL, "Invalid time value: %s\n", mod + 10); return AMR_CLEANUP; } *pp = arg; } else { utc = 0; *pp = mod + 9; } st->newVal = VarStrftime(st->val, FALSE, utc); return AMR_OK; } /* :hash */ static ApplyModifierResult ApplyModifier_Hash(const char **pp, ApplyModifiersState *st) { if (!ModMatch(*pp, "hash", st->endc)) return AMR_UNKNOWN; st->newVal = VarHash(st->val); *pp += 4; return AMR_OK; } /* :P */ static ApplyModifierResult ApplyModifier_Path(const char **pp, ApplyModifiersState *st) { GNode *gn; char *path; ApplyModifiersState_Define(st); - gn = Targ_FindNode(st->v->name); + gn = Targ_FindNode(st->var->name); if (gn == NULL || gn->type & OP_NOPATH) { path = NULL; } else if (gn->path != NULL) { path = bmake_strdup(gn->path); } else { SearchPath *searchPath = Suff_FindPath(gn); - path = Dir_FindFile(st->v->name, searchPath); + path = Dir_FindFile(st->var->name, searchPath); } if (path == NULL) - path = bmake_strdup(st->v->name); + path = bmake_strdup(st->var->name); st->newVal = path; (*pp)++; return AMR_OK; } /* :!cmd! */ static ApplyModifierResult ApplyModifier_ShellCommand(const char **pp, ApplyModifiersState *st) { char *cmd; const char *errfmt; VarParseResult res; (*pp)++; res = ParseModifierPart(pp, '!', st->eflags, st, &cmd, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; errfmt = NULL; if (st->eflags & VARE_WANTRES) st->newVal = Cmd_Exec(cmd, &errfmt); else - st->newVal = emptyString; + st->newVal = bmake_strdup(""); + if (errfmt != NULL) + Error(errfmt, cmd); /* XXX: why still return AMR_OK? */ free(cmd); - if (errfmt != NULL) - Error(errfmt, st->val); /* XXX: why still return AMR_OK? */ - ApplyModifiersState_Define(st); return AMR_OK; } /* The :range modifier generates an integer sequence as long as the words. * The :range=7 modifier generates an integer sequence from 1 to 7. */ static ApplyModifierResult ApplyModifier_Range(const char **pp, ApplyModifiersState *st) { size_t n; Buffer buf; size_t i; const char *mod = *pp; if (!ModMatchEq(mod, "range", st->endc)) return AMR_UNKNOWN; if (mod[5] == '=') { const char *p = mod + 6; if (!TryParseSize(&p, &n)) { Parse_Error(PARSE_FATAL, "Invalid number: %s\n", mod + 6); return AMR_CLEANUP; } *pp = p; } else { n = 0; *pp = mod + 5; } if (n == 0) { Words words = Str_Words(st->val, FALSE); n = words.len; Words_Free(words); } - Buf_Init(&buf, 0); + Buf_Init(&buf); for (i = 0; i < n; i++) { if (i != 0) Buf_AddByte(&buf, ' '); /* XXX: st->sep, for consistency */ Buf_AddInt(&buf, 1 + (int)i); } st->newVal = Buf_Destroy(&buf, FALSE); return AMR_OK; } /* :Mpattern or :Npattern */ static ApplyModifierResult ApplyModifier_Match(const char **pp, ApplyModifiersState *st) { const char *mod = *pp; Boolean copy = FALSE; /* pattern should be, or has been, copied */ Boolean needSubst = FALSE; const char *endpat; char *pattern; ModifyWordsCallback callback; /* * 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. + * XXX: This will likely not work right if $() and ${} are intermixed. */ + /* XXX: This code is similar to the one in Var_Parse. + * See if the code can be merged. + * See also ApplyModifier_Defined. */ int nest = 0; const char *p; for (p = mod + 1; *p != '\0' && !(*p == ':' && nest == 0); p++) { if (*p == '\\' && (p[1] == ':' || p[1] == st->endc || p[1] == st->startc)) { if (!needSubst) copy = TRUE; p++; continue; } if (*p == '$') needSubst = TRUE; if (*p == '(' || *p == '{') nest++; if (*p == ')' || *p == '}') { nest--; if (nest < 0) break; } } *pp = p; endpat = p; if (copy) { char *dst; const char *src; /* Compress the \:'s out of the pattern. */ pattern = bmake_malloc((size_t)(endpat - (mod + 1)) + 1); dst = pattern; src = mod + 1; for (; src < endpat; src++, dst++) { if (src[0] == '\\' && src + 1 < endpat && /* XXX: st->startc is missing here; see above */ (src[1] == ':' || src[1] == st->endc)) src++; *dst = *src; } *dst = '\0'; endpat = dst; } else { pattern = bmake_strsedup(mod + 1, endpat); } if (needSubst) { /* pattern contains embedded '$', so use Var_Subst to expand it. */ char *old_pattern = pattern; (void)Var_Subst(pattern, st->ctxt, st->eflags, &pattern); /* TODO: handle errors */ free(old_pattern); } - VAR_DEBUG3("Pattern[%s] for [%s] is [%s]\n", st->v->name, st->val, pattern); + VAR_DEBUG3("Pattern[%s] for [%s] is [%s]\n", + st->var->name, st->val, pattern); callback = mod[0] == 'M' ? ModifyWord_Match : ModifyWord_NoMatch; st->newVal = ModifyWords(st->val, callback, pattern, st->oneBigWord, st->sep); free(pattern); return AMR_OK; } /* :S,from,to, */ static ApplyModifierResult ApplyModifier_Subst(const char **pp, ApplyModifiersState *st) { struct ModifyWord_SubstArgs args; char *lhs, *rhs; Boolean oneBigWord; VarParseResult res; char delim = (*pp)[1]; if (delim == '\0') { Error("Missing delimiter for :S modifier"); (*pp)++; return AMR_CLEANUP; } *pp += 2; args.pflags = 0; args.matched = FALSE; /* * If pattern begins with '^', it is anchored to the * start of the word -- skip over it and flag pattern. */ if (**pp == '^') { args.pflags |= VARP_ANCHOR_START; (*pp)++; } res = ParseModifierPart(pp, delim, st->eflags, st, &lhs, &args.lhsLen, &args.pflags, NULL); if (res != VPR_OK) return AMR_CLEANUP; args.lhs = lhs; res = ParseModifierPart(pp, delim, st->eflags, st, &rhs, &args.rhsLen, NULL, &args); if (res != VPR_OK) return AMR_CLEANUP; args.rhs = rhs; oneBigWord = st->oneBigWord; for (;; (*pp)++) { switch (**pp) { case 'g': args.pflags |= VARP_SUB_GLOBAL; continue; case '1': args.pflags |= VARP_SUB_ONE; continue; case 'W': oneBigWord = TRUE; continue; } break; } st->newVal = ModifyWords(st->val, ModifyWord_Subst, &args, oneBigWord, st->sep); free(lhs); free(rhs); return AMR_OK; } #ifndef NO_REGEX /* :C,from,to, */ static ApplyModifierResult ApplyModifier_Regex(const char **pp, ApplyModifiersState *st) { char *re; struct ModifyWord_SubstRegexArgs args; Boolean oneBigWord; int error; VarParseResult res; char delim = (*pp)[1]; if (delim == '\0') { Error("Missing delimiter for :C modifier"); (*pp)++; return AMR_CLEANUP; } *pp += 2; res = ParseModifierPart(pp, delim, st->eflags, st, &re, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; res = ParseModifierPart(pp, delim, st->eflags, st, &args.replace, NULL, NULL, NULL); if (args.replace == NULL) { free(re); return AMR_CLEANUP; } args.pflags = 0; args.matched = FALSE; oneBigWord = st->oneBigWord; for (;; (*pp)++) { switch (**pp) { case 'g': args.pflags |= VARP_SUB_GLOBAL; continue; case '1': args.pflags |= VARP_SUB_ONE; continue; case 'W': oneBigWord = TRUE; continue; } break; } error = regcomp(&args.re, re, REG_EXTENDED); free(re); if (error) { VarREError(error, &args.re, "Regex compilation error"); free(args.replace); return AMR_CLEANUP; } args.nsub = args.re.re_nsub + 1; if (args.nsub > 10) args.nsub = 10; st->newVal = ModifyWords(st->val, ModifyWord_SubstRegex, &args, oneBigWord, st->sep); regfree(&args.re); free(args.replace); return AMR_OK; } #endif /* :Q, :q */ static ApplyModifierResult ApplyModifier_Quote(const char **pp, ApplyModifiersState *st) { if ((*pp)[1] == st->endc || (*pp)[1] == ':') { st->newVal = VarQuote(st->val, **pp == 'q'); (*pp)++; return AMR_OK; } else return AMR_UNKNOWN; } static void ModifyWord_Copy(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) { SepBuf_AddStr(buf, word); } /* :ts */ static ApplyModifierResult ApplyModifier_ToSep(const char **pp, ApplyModifiersState *st) { const char *sep = *pp + 2; /* ":ts" or ":ts:" */ if (sep[0] != st->endc && (sep[1] == st->endc || sep[1] == ':')) { st->sep = sep[0]; *pp = sep + 1; goto ok; } /* ":ts" or ":ts:" */ if (sep[0] == st->endc || sep[0] == ':') { st->sep = '\0'; /* no separator */ *pp = sep; goto ok; } /* ":ts". */ if (sep[0] != '\\') { (*pp)++; /* just for backwards compatibility */ return AMR_BAD; } /* ":ts\n" */ if (sep[1] == 'n') { st->sep = '\n'; *pp = sep + 2; goto ok; } /* ":ts\t" */ if (sep[1] == 't') { st->sep = '\t'; *pp = sep + 2; goto ok; } /* ":ts\x40" or ":ts\100" */ { const char *p = sep + 1; int base = 8; /* assume octal */ if (sep[1] == 'x') { base = 16; p++; } else if (!ch_isdigit(sep[1])) { (*pp)++; /* just for backwards compatibility */ return AMR_BAD; /* ":ts". */ } if (!TryParseChar(&p, base, &st->sep)) { Parse_Error(PARSE_FATAL, "Invalid character number: %s\n", p); return AMR_CLEANUP; } if (*p != ':' && *p != st->endc) { (*pp)++; /* just for backwards compatibility */ return AMR_BAD; } *pp = p; } ok: st->newVal = ModifyWords(st->val, ModifyWord_Copy, NULL, st->oneBigWord, st->sep); return AMR_OK; } /* :tA, :tu, :tl, :ts, etc. */ static ApplyModifierResult ApplyModifier_To(const char **pp, ApplyModifiersState *st) { const char *mod = *pp; assert(mod[0] == 't'); if (mod[1] == st->endc || mod[1] == ':' || mod[1] == '\0') { *pp = mod + 1; return AMR_BAD; /* Found ":t" or ":t:". */ } if (mod[1] == 's') return ApplyModifier_ToSep(pp, st); if (mod[2] != st->endc && mod[2] != ':') { *pp = mod + 1; return AMR_BAD; /* Found ":t". */ } /* Check for two-character options: ":tu", ":tl" */ if (mod[1] == 'A') { /* absolute path */ st->newVal = ModifyWords(st->val, ModifyWord_Realpath, NULL, st->oneBigWord, st->sep); *pp = mod + 2; return AMR_OK; } if (mod[1] == 'u') { /* :tu */ size_t i; size_t len = strlen(st->val); st->newVal = bmake_malloc(len + 1); for (i = 0; i < len + 1; i++) st->newVal[i] = ch_toupper(st->val[i]); *pp = mod + 2; return AMR_OK; } if (mod[1] == 'l') { /* :tl */ size_t i; size_t len = strlen(st->val); st->newVal = bmake_malloc(len + 1); for (i = 0; i < len + 1; i++) st->newVal[i] = ch_tolower(st->val[i]); *pp = mod + 2; return AMR_OK; } if (mod[1] == 'W' || mod[1] == 'w') { /* :tW, :tw */ st->oneBigWord = mod[1] == 'W'; st->newVal = st->val; *pp = mod + 2; return AMR_OK; } /* Found ":t:" or ":t". */ *pp = mod + 1; return AMR_BAD; } /* :[#], :[1], :[-1..1], etc. */ static ApplyModifierResult ApplyModifier_Words(const char **pp, ApplyModifiersState *st) { char *estr; int first, last; VarParseResult res; const char *p; (*pp)++; /* skip the '[' */ res = ParseModifierPart(pp, ']', st->eflags, st, &estr, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; /* now *pp points just after the closing ']' */ if (**pp != ':' && **pp != st->endc) goto bad_modifier; /* Found junk after ']' */ if (estr[0] == '\0') goto bad_modifier; /* empty square brackets in ":[]". */ if (estr[0] == '#' && estr[1] == '\0') { /* Found ":[#]" */ if (st->oneBigWord) { st->newVal = bmake_strdup("1"); } else { Buffer buf; Words words = Str_Words(st->val, FALSE); size_t ac = words.len; Words_Free(words); - Buf_Init(&buf, 4); /* 3 digits + '\0' is usually enough */ + Buf_InitSize(&buf, 4); /* 3 digits + '\0' is usually enough */ Buf_AddInt(&buf, (int)ac); st->newVal = Buf_Destroy(&buf, FALSE); } goto ok; } if (estr[0] == '*' && estr[1] == '\0') { /* Found ":[*]" */ st->oneBigWord = TRUE; st->newVal = st->val; goto ok; } if (estr[0] == '@' && estr[1] == '\0') { /* Found ":[@]" */ st->oneBigWord = FALSE; st->newVal = st->val; goto ok; } /* * We expect estr to contain a single integer for :[N], or two integers * separated by ".." for :[start..end]. */ p = estr; if (!TryParseIntBase0(&p, &first)) goto bad_modifier; /* Found junk instead of a number */ if (p[0] == '\0') { /* Found only one integer in :[N] */ last = first; } else if (p[0] == '.' && p[1] == '.' && p[2] != '\0') { /* Expecting another integer after ".." */ p += 2; if (!TryParseIntBase0(&p, &last) || *p != '\0') goto bad_modifier; /* Found junk after ".." */ } else goto bad_modifier; /* Found junk instead of ".." */ /* * Now first and last are properly filled in, but we still have to check * for 0 as a special case. */ if (first == 0 && last == 0) { /* ":[0]" or perhaps ":[0..0]" */ st->oneBigWord = TRUE; st->newVal = st->val; goto ok; } /* ":[0..N]" or ":[N..0]" */ if (first == 0 || last == 0) goto bad_modifier; /* Normal case: select the words described by first and last. */ st->newVal = VarSelectWords(st->sep, st->oneBigWord, st->val, first, last); ok: free(estr); return AMR_OK; bad_modifier: free(estr); return AMR_BAD; } static int str_cmp_asc(const void *a, const void *b) { return strcmp(*(const char * const *)a, *(const char * const *)b); } static int str_cmp_desc(const void *a, const void *b) { return strcmp(*(const char * const *)b, *(const char * const *)a); } /* :O (order ascending) or :Or (order descending) or :Ox (shuffle) */ static ApplyModifierResult ApplyModifier_Order(const char **pp, ApplyModifiersState *st) { const char *mod = (*pp)++; /* skip past the 'O' in any case */ Words words = Str_Words(st->val, FALSE); if (mod[1] == st->endc || mod[1] == ':') { /* :O sorts ascending */ - qsort(words.words, words.len, sizeof(char *), str_cmp_asc); + qsort(words.words, words.len, sizeof words.words[0], str_cmp_asc); } else if ((mod[1] == 'r' || mod[1] == 'x') && (mod[2] == st->endc || mod[2] == ':')) { (*pp)++; if (mod[1] == 'r') { /* :Or sorts descending */ - qsort(words.words, words.len, sizeof(char *), str_cmp_desc); + qsort(words.words, words.len, sizeof words.words[0], str_cmp_desc); } else { /* :Ox shuffles * * 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). */ size_t i; for (i = words.len - 1; i > 0; i--) { size_t rndidx = (size_t)random() % (i + 1); char *t = words.words[i]; words.words[i] = words.words[rndidx]; words.words[rndidx] = t; } } } else { Words_Free(words); return AMR_BAD; } st->newVal = Words_JoinFree(words); return AMR_OK; } /* :? then : else */ static ApplyModifierResult ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) { char *then_expr, *else_expr; VarParseResult res; Boolean value = FALSE; - VarEvalFlags then_eflags = st->eflags & ~(unsigned)VARE_WANTRES; - VarEvalFlags else_eflags = st->eflags & ~(unsigned)VARE_WANTRES; + VarEvalFlags then_eflags = VARE_NONE; + VarEvalFlags else_eflags = VARE_NONE; int cond_rc = COND_PARSE; /* anything other than COND_INVALID */ if (st->eflags & VARE_WANTRES) { - cond_rc = Cond_EvalCondition(st->v->name, &value); + cond_rc = Cond_EvalCondition(st->var->name, &value); if (cond_rc != COND_INVALID && value) - then_eflags |= VARE_WANTRES; + then_eflags = st->eflags; if (cond_rc != COND_INVALID && !value) - else_eflags |= VARE_WANTRES; + else_eflags = st->eflags; } (*pp)++; /* skip past the '?' */ res = ParseModifierPart(pp, ':', then_eflags, st, &then_expr, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; res = ParseModifierPart(pp, st->endc, else_eflags, st, &else_expr, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; (*pp)--; if (cond_rc == COND_INVALID) { Error("Bad conditional expression `%s' in %s?%s:%s", - st->v->name, st->v->name, then_expr, else_expr); + st->var->name, st->var->name, then_expr, else_expr); return AMR_CLEANUP; } if (value) { st->newVal = then_expr; free(else_expr); } else { st->newVal = else_expr; free(then_expr); } ApplyModifiersState_Define(st); return AMR_OK; } /* * 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. */ static ApplyModifierResult ApplyModifier_Assign(const char **pp, ApplyModifiersState *st) { GNode *v_ctxt; char delim; char *val; VarParseResult res; const char *mod = *pp; const char *op = mod + 1; if (op[0] == '=') goto ok; if ((op[0] == '!' || op[0] == '+' || op[0] == '?') && op[1] == '=') goto ok; return AMR_UNKNOWN; /* "::" */ ok: - if (st->v->name[0] == '\0') { + if (st->var->name[0] == '\0') { *pp = mod + 1; return AMR_BAD; } v_ctxt = st->ctxt; /* context where v belongs */ if (!(st->exprFlags & VEF_UNDEF) && st->ctxt != VAR_GLOBAL) { - Var *gv = VarFind(st->v->name, st->ctxt, 0); + Var *gv = VarFind(st->var->name, st->ctxt, FALSE); if (gv == NULL) v_ctxt = VAR_GLOBAL; else VarFreeEnv(gv, TRUE); } switch (op[0]) { case '+': case '?': case '!': *pp = mod + 3; break; default: *pp = mod + 2; break; } delim = st->startc == '(' ? ')' : '}'; res = ParseModifierPart(pp, delim, st->eflags, st, &val, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; (*pp)--; if (st->eflags & VARE_WANTRES) { switch (op[0]) { case '+': - Var_Append(st->v->name, val, v_ctxt); + Var_Append(st->var->name, val, v_ctxt); break; case '!': { const char *errfmt; char *cmd_output = Cmd_Exec(val, &errfmt); if (errfmt) Error(errfmt, val); else - Var_Set(st->v->name, cmd_output, v_ctxt); + Var_Set(st->var->name, cmd_output, v_ctxt); free(cmd_output); break; } case '?': if (!(st->exprFlags & VEF_UNDEF)) break; /* FALLTHROUGH */ default: - Var_Set(st->v->name, val, v_ctxt); + Var_Set(st->var->name, val, v_ctxt); break; } } free(val); - st->newVal = emptyString; + st->newVal = bmake_strdup(""); return AMR_OK; } /* :_=... * remember current value */ static ApplyModifierResult ApplyModifier_Remember(const char **pp, ApplyModifiersState *st) { const char *mod = *pp; if (!ModMatchEq(mod, "_", st->endc)) return AMR_UNKNOWN; if (mod[1] == '=') { size_t n = strcspn(mod + 2, ":)}"); char *name = bmake_strldup(mod + 2, n); Var_Set(name, st->val, st->ctxt); free(name); *pp = mod + 2 + n; } else { Var_Set("_", st->val, st->ctxt); *pp = mod + 1; } st->newVal = st->val; return AMR_OK; } /* Apply the given function to each word of the variable value, * for a single-letter modifier such as :H, :T. */ static ApplyModifierResult ApplyModifier_WordFunc(const char **pp, ApplyModifiersState *st, ModifyWordsCallback modifyWord) { char delim = (*pp)[1]; if (delim != st->endc && delim != ':') return AMR_UNKNOWN; st->newVal = ModifyWords(st->val, modifyWord, NULL, st->oneBigWord, st->sep); (*pp)++; return AMR_OK; } static ApplyModifierResult ApplyModifier_Unique(const char **pp, ApplyModifiersState *st) { if ((*pp)[1] == st->endc || (*pp)[1] == ':') { st->newVal = VarUniq(st->val); (*pp)++; return AMR_OK; } else return AMR_UNKNOWN; } #ifdef SYSVVARSUB /* :from=to */ static ApplyModifierResult ApplyModifier_SysV(const char **pp, ApplyModifiersState *st) { char *lhs, *rhs; VarParseResult res; const char *mod = *pp; Boolean eqFound = FALSE; /* * First we make a pass through the string trying to verify it is a * SysV-make-style translation. It must be: = */ int depth = 1; const char *p = mod; while (*p != '\0' && depth > 0) { if (*p == '=') { /* XXX: should also test depth == 1 */ eqFound = TRUE; /* continue looking for st->endc */ } else if (*p == st->endc) depth--; else if (*p == st->startc) depth++; if (depth > 0) p++; } if (*p != st->endc || !eqFound) return AMR_UNKNOWN; *pp = mod; res = ParseModifierPart(pp, '=', st->eflags, st, &lhs, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; /* The SysV modifier lasts until the end of the variable expression. */ res = ParseModifierPart(pp, st->endc, st->eflags, st, &rhs, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; (*pp)--; if (lhs[0] == '\0' && st->val[0] == '\0') { st->newVal = st->val; /* special case */ } else { struct ModifyWord_SYSVSubstArgs args = {st->ctxt, lhs, rhs}; st->newVal = ModifyWords(st->val, ModifyWord_SYSVSubst, &args, st->oneBigWord, st->sep); } free(lhs); free(rhs); return AMR_OK; } #endif #ifdef SUNSHCMD /* :sh */ static ApplyModifierResult ApplyModifier_SunShell(const char **pp, ApplyModifiersState *st) { const char *p = *pp; if (p[1] == 'h' && (p[2] == st->endc || p[2] == ':')) { if (st->eflags & VARE_WANTRES) { const char *errfmt; st->newVal = Cmd_Exec(st->val, &errfmt); if (errfmt) Error(errfmt, st->val); } else - st->newVal = emptyString; + st->newVal = bmake_strdup(""); *pp = p + 2; return AMR_OK; } else return AMR_UNKNOWN; } #endif static void LogBeforeApply(const ApplyModifiersState *st, const char *mod, const char endc) { char eflags_str[VarEvalFlags_ToStringSize]; char vflags_str[VarFlags_ToStringSize]; char exprflags_str[VarExprFlags_ToStringSize]; Boolean is_single_char = mod[0] != '\0' && (mod[1] == endc || mod[1] == ':'); /* At this point, only the first character of the modifier can * be used since the end of the modifier is not yet known. */ debug_printf("Applying ${%s:%c%s} to \"%s\" (%s, %s, %s)\n", - st->v->name, mod[0], is_single_char ? "" : "...", st->val, + st->var->name, mod[0], is_single_char ? "" : "...", st->val, Enum_FlagsToString(eflags_str, sizeof eflags_str, st->eflags, VarEvalFlags_ToStringSpecs), Enum_FlagsToString(vflags_str, sizeof vflags_str, - st->v->flags, VarFlags_ToStringSpecs), + st->var->flags, VarFlags_ToStringSpecs), Enum_FlagsToString(exprflags_str, sizeof exprflags_str, st->exprFlags, VarExprFlags_ToStringSpecs)); } static void LogAfterApply(ApplyModifiersState *st, const char *p, const char *mod) { char eflags_str[VarEvalFlags_ToStringSize]; char vflags_str[VarFlags_ToStringSize]; char exprflags_str[VarExprFlags_ToStringSize]; const char *quot = st->newVal == var_Error ? "" : "\""; const char *newVal = st->newVal == var_Error ? "error" : st->newVal; debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s, %s)\n", - st->v->name, (int)(p - mod), mod, quot, newVal, quot, + st->var->name, (int)(p - mod), mod, quot, newVal, quot, Enum_FlagsToString(eflags_str, sizeof eflags_str, st->eflags, VarEvalFlags_ToStringSpecs), Enum_FlagsToString(vflags_str, sizeof vflags_str, - st->v->flags, VarFlags_ToStringSpecs), + st->var->flags, VarFlags_ToStringSpecs), Enum_FlagsToString(exprflags_str, sizeof exprflags_str, st->exprFlags, VarExprFlags_ToStringSpecs)); } static ApplyModifierResult ApplyModifier(const char **pp, ApplyModifiersState *st) { switch (**pp) { case ':': return ApplyModifier_Assign(pp, st); case '@': return ApplyModifier_Loop(pp, st); case '_': return ApplyModifier_Remember(pp, st); case 'D': case 'U': return ApplyModifier_Defined(pp, st); case 'L': return ApplyModifier_Literal(pp, st); case 'P': return ApplyModifier_Path(pp, st); case '!': return ApplyModifier_ShellCommand(pp, st); case '[': return ApplyModifier_Words(pp, st); case 'g': return ApplyModifier_Gmtime(pp, st); case 'h': return ApplyModifier_Hash(pp, st); case 'l': return ApplyModifier_Localtime(pp, st); case 't': return ApplyModifier_To(pp, st); case 'N': case 'M': return ApplyModifier_Match(pp, st); case 'S': return ApplyModifier_Subst(pp, st); case '?': return ApplyModifier_IfElse(pp, st); #ifndef NO_REGEX case 'C': return ApplyModifier_Regex(pp, st); #endif case 'q': case 'Q': return ApplyModifier_Quote(pp, st); case 'T': return ApplyModifier_WordFunc(pp, st, ModifyWord_Tail); case 'H': return ApplyModifier_WordFunc(pp, st, ModifyWord_Head); case 'E': return ApplyModifier_WordFunc(pp, st, ModifyWord_Suffix); case 'R': return ApplyModifier_WordFunc(pp, st, ModifyWord_Root); case 'r': return ApplyModifier_Range(pp, st); case 'O': return ApplyModifier_Order(pp, st); case 'u': return ApplyModifier_Unique(pp, st); #ifdef SUNSHCMD case 's': return ApplyModifier_SunShell(pp, st); #endif default: return AMR_UNKNOWN; } } static char *ApplyModifiers(const char **, char *, char, char, Var *, VarExprFlags *, GNode *, VarEvalFlags, void **); typedef enum ApplyModifiersIndirectResult { AMIR_CONTINUE, AMIR_APPLY_MODS, AMIR_OUT } ApplyModifiersIndirectResult; /* While expanding a variable expression, expand and apply indirect - * modifiers. */ + * modifiers such as in ${VAR:${M_indirect}}. */ static ApplyModifiersIndirectResult ApplyModifiersIndirect( ApplyModifiersState *const st, - const char **inout_p, - void **const out_freeIt + const char **const inout_p, + void **const inout_freeIt ) { const char *p = *inout_p; - const char *nested_p = p; - void *freeIt; - const char *rval; - char c; + const char *mods; + void *mods_freeIt; - (void)Var_Parse(&nested_p, st->ctxt, st->eflags, &rval, &freeIt); + (void)Var_Parse(&p, st->ctxt, st->eflags, &mods, &mods_freeIt); /* TODO: handle errors */ /* * If we have not parsed up to st->endc or ':', we are not * interested. This means the expression ${VAR:${M_1}${M_2}} * is not accepted, but ${VAR:${M_1}:${M_2}} is. */ - if (rval[0] != '\0' && - (c = *nested_p) != '\0' && c != ':' && c != st->endc) { - if (DEBUG(LINT)) + if (mods[0] != '\0' && *p != '\0' && *p != ':' && *p != st->endc) { + if (opts.lint) Parse_Error(PARSE_FATAL, "Missing delimiter ':' after indirect modifier \"%.*s\"", - (int)(nested_p - p), p); + (int)(p - *inout_p), *inout_p); - free(freeIt); + free(mods_freeIt); /* XXX: apply_mods doesn't sound like "not interested". */ - /* XXX: Why is the indirect modifier parsed again by + /* XXX: Why is the indirect modifier parsed once more by * apply_mods? If any, p should be advanced to nested_p. */ return AMIR_APPLY_MODS; } VAR_DEBUG3("Indirect modifier \"%s\" from \"%.*s\"\n", - rval, (int)(size_t)(nested_p - p), p); + mods, (int)(p - *inout_p), *inout_p); - p = nested_p; - - if (rval[0] != '\0') { - const char *rval_pp = rval; - st->val = ApplyModifiers(&rval_pp, st->val, '\0', '\0', st->v, - &st->exprFlags, st->ctxt, st->eflags, out_freeIt); - if (st->val == var_Error - || (st->val == varUndefined && !(st->eflags & VARE_UNDEFERR)) - || *rval_pp != '\0') { - free(freeIt); + if (mods[0] != '\0') { + const char *rval_pp = mods; + st->val = ApplyModifiers(&rval_pp, st->val, '\0', '\0', st->var, + &st->exprFlags, st->ctxt, st->eflags, + inout_freeIt); + if (st->val == var_Error || st->val == varUndefined || + *rval_pp != '\0') { + free(mods_freeIt); *inout_p = p; return AMIR_OUT; /* error already reported */ } } - free(freeIt); + free(mods_freeIt); if (*p == ':') p++; else if (*p == '\0' && st->endc != '\0') { Error("Unclosed variable specification after complex " - "modifier (expecting '%c') for %s", st->endc, st->v->name); + "modifier (expecting '%c') for %s", st->endc, st->var->name); *inout_p = p; return AMIR_OUT; } *inout_p = p; return AMIR_CONTINUE; } /* Apply any modifiers (such as :Mpattern or :@var@loop@ or :Q or ::=value). */ static char * ApplyModifiers( - const char **pp, /* the parsing position, updated upon return */ + const char **const pp, /* the parsing position, updated upon return */ char *const val, /* the current value of the expression */ char const startc, /* '(' or '{', or '\0' for indirect modifiers */ char const endc, /* ')' or '}', or '\0' for indirect modifiers */ - Var * const v, - VarExprFlags *exprFlags, - GNode * const ctxt, /* for looking up and modifying variables */ + Var *const v, + VarExprFlags *const exprFlags, + GNode *const ctxt, /* for looking up and modifying variables */ VarEvalFlags const eflags, - void ** const out_freeIt /* free this after using the return value */ + void **const inout_freeIt /* free this after using the return value */ ) { ApplyModifiersState st = { startc, endc, v, ctxt, eflags, val, /* .val */ var_Error, /* .newVal */ ' ', /* .sep */ FALSE, /* .oneBigWord */ *exprFlags /* .exprFlags */ }; const char *p; const char *mod; ApplyModifierResult res; assert(startc == '(' || startc == '{' || startc == '\0'); assert(endc == ')' || endc == '}' || endc == '\0'); assert(val != NULL); p = *pp; + + if (*p == '\0' && endc != '\0') { + Error("Unclosed variable expression (expecting '%c') for \"%s\"", + st.endc, st.var->name); + goto cleanup; + } + while (*p != '\0' && *p != endc) { if (*p == '$') { ApplyModifiersIndirectResult amir; - amir = ApplyModifiersIndirect(&st, &p, out_freeIt); + amir = ApplyModifiersIndirect(&st, &p, inout_freeIt); if (amir == AMIR_CONTINUE) continue; if (amir == AMIR_OUT) goto out; } st.newVal = var_Error; /* default value, in case of errors */ mod = p; if (DEBUG(VAR)) LogBeforeApply(&st, mod, endc); res = ApplyModifier(&p, &st); #ifdef SYSVVARSUB if (res == AMR_UNKNOWN) { assert(p == mod); res = ApplyModifier_SysV(&p, &st); } #endif if (res == AMR_UNKNOWN) { Error("Unknown modifier '%c'", *mod); + /* Guess the end of the current modifier. + * XXX: Skipping the rest of the modifier hides errors and leads + * to wrong results. Parsing should rather stop here. */ for (p++; *p != ':' && *p != st.endc && *p != '\0'; p++) continue; st.newVal = var_Error; } if (res == AMR_CLEANUP) goto cleanup; if (res == AMR_BAD) goto bad_modifier; if (DEBUG(VAR)) LogAfterApply(&st, p, mod); if (st.newVal != st.val) { - if (*out_freeIt) { + if (*inout_freeIt != NULL) { free(st.val); - *out_freeIt = NULL; + *inout_freeIt = NULL; } st.val = st.newVal; - if (st.val != var_Error && st.val != varUndefined && - st.val != emptyString) { - *out_freeIt = st.val; - } + if (st.val != var_Error && st.val != varUndefined) + *inout_freeIt = st.val; } if (*p == '\0' && st.endc != '\0') { Error("Unclosed variable specification (expecting '%c') " "for \"%s\" (value \"%s\") modifier %c", - st.endc, st.v->name, st.val, *mod); + st.endc, st.var->name, st.val, *mod); } else if (*p == ':') { p++; - } else if (DEBUG(LINT) && *p != '\0' && *p != endc) { + } else if (opts.lint && *p != '\0' && *p != endc) { Parse_Error(PARSE_FATAL, "Missing delimiter ':' after modifier \"%.*s\"", (int)(p - mod), mod); + /* TODO: propagate parse error to the enclosing expression */ } } out: *pp = p; assert(st.val != NULL); /* Use var_Error or varUndefined instead. */ *exprFlags = st.exprFlags; return st.val; bad_modifier: + /* XXX: The modifier end is only guessed. */ Error("Bad modifier `:%.*s' for %s", - (int)strcspn(mod, ":)}"), mod, st.v->name); + (int)strcspn(mod, ":)}"), mod, st.var->name); cleanup: *pp = p; - free(*out_freeIt); - *out_freeIt = NULL; + free(*inout_freeIt); + *inout_freeIt = NULL; *exprFlags = st.exprFlags; return var_Error; } /* Only four of the local variables are treated specially as they are the * only four that will be set when dynamic sources are expanded. */ static Boolean VarnameIsDynamic(const char *name, size_t len) { if (len == 1 || (len == 2 && (name[1] == 'F' || name[1] == 'D'))) { switch (name[0]) { case '@': case '%': case '*': case '!': return TRUE; } return FALSE; } if ((len == 7 || len == 8) && name[0] == '.' && ch_isupper(name[1])) { return strcmp(name, ".TARGET") == 0 || strcmp(name, ".ARCHIVE") == 0 || strcmp(name, ".PREFIX") == 0 || strcmp(name, ".MEMBER") == 0; } return FALSE; } static const char * UndefinedShortVarValue(char varname, const GNode *ctxt, VarEvalFlags eflags) { if (ctxt == VAR_CMDLINE || 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 (varname) { case '@': return "$(.TARGET)"; case '%': return "$(.MEMBER)"; case '*': return "$(.PREFIX)"; case '!': return "$(.ARCHIVE)"; } } return eflags & VARE_UNDEFERR ? var_Error : varUndefined; } /* Parse a variable name, until the end character or a colon, whichever * comes first. */ static char * ParseVarname(const char **pp, char startc, char endc, GNode *ctxt, VarEvalFlags eflags, size_t *out_varname_len) { Buffer buf; const char *p = *pp; int depth = 1; - Buf_Init(&buf, 0); + Buf_Init(&buf); while (*p != '\0') { /* Track depth so we can spot parse errors. */ if (*p == startc) depth++; if (*p == endc) { if (--depth == 0) break; } if (*p == ':' && depth == 1) break; /* A variable inside a variable, expand. */ if (*p == '$') { - void *freeIt; - const char *rval; - (void)Var_Parse(&p, ctxt, eflags, &rval, &freeIt); + const char *nested_val; + void *nested_val_freeIt; + (void)Var_Parse(&p, ctxt, eflags, &nested_val, &nested_val_freeIt); /* TODO: handle errors */ - Buf_AddStr(&buf, rval); - free(freeIt); + Buf_AddStr(&buf, nested_val); + free(nested_val_freeIt); } else { Buf_AddByte(&buf, *p); p++; } } *pp = p; *out_varname_len = Buf_Len(&buf); return Buf_Destroy(&buf, FALSE); } -static Boolean +static VarParseResult ValidShortVarname(char varname, const char *start) { switch (varname) { case '\0': case ')': case '}': case ':': case '$': break; /* and continue below */ default: - return TRUE; + return VPR_OK; } - if (!DEBUG(LINT)) - return FALSE; + if (!opts.lint) + return VPR_PARSE_SILENT; if (varname == '$') Parse_Error(PARSE_FATAL, "To escape a dollar, use \\$, not $$, at \"%s\"", start); else if (varname == '\0') Parse_Error(PARSE_FATAL, "Dollar followed by nothing"); else Parse_Error(PARSE_FATAL, "Invalid variable name '%c', at \"%s\"", varname, start); - return FALSE; + return VPR_PARSE_MSG; } /* Parse a single-character variable name such as $V or $@. * Return whether to continue parsing. */ static Boolean -ParseVarnameShort( - char startc, - const char **pp, - GNode *ctxt, - VarEvalFlags eflags, - VarParseResult *out_FALSE_res, - const char **out_FALSE_val, - Var **out_TRUE_var -) { +ParseVarnameShort(char startc, const char **pp, GNode *ctxt, + VarEvalFlags eflags, + VarParseResult *out_FALSE_res, const char **out_FALSE_val, + Var **out_TRUE_var) +{ char name[2]; Var *v; + VarParseResult vpr; /* * 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. */ - if (!ValidShortVarname(startc, *pp)) { + vpr = ValidShortVarname(startc, *pp); + if (vpr != VPR_OK) { (*pp)++; *out_FALSE_val = var_Error; - *out_FALSE_res = VPR_PARSE_MSG; + *out_FALSE_res = vpr; return FALSE; } name[0] = startc; name[1] = '\0'; v = VarFind(name, ctxt, TRUE); if (v == NULL) { *pp += 2; *out_FALSE_val = UndefinedShortVarValue(startc, ctxt, eflags); - if (DEBUG(LINT) && *out_FALSE_val == var_Error) { + if (opts.lint && *out_FALSE_val == var_Error) { Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", name); *out_FALSE_res = VPR_UNDEF_MSG; return FALSE; } *out_FALSE_res = eflags & VARE_UNDEFERR ? VPR_UNDEF_SILENT : VPR_OK; return FALSE; } *out_TRUE_var = v; return TRUE; } +/* Find variables like @F or ", varname[0]) == NULL) + return NULL; + + { + char name[] = { varname[0], '\0' }; + Var *v = VarFind(name, ctxt, FALSE); + + if (v != NULL) { + if (varname[1] == 'D') { + *out_extraModifiers = "H:"; + } else { /* F */ + *out_extraModifiers = "T:"; + } + } + return v; + } +} + +static VarParseResult +EvalUndefined(Boolean dynamic, const char *start, const char *p, char *varname, + VarEvalFlags eflags, + void **out_freeIt, const char **out_val) +{ + if (dynamic) { + char *pstr = bmake_strsedup(start, p); + free(varname); + *out_freeIt = pstr; + *out_val = pstr; + return VPR_OK; + } + + if ((eflags & VARE_UNDEFERR) && opts.lint) { + Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", varname); + free(varname); + *out_val = var_Error; + return VPR_UNDEF_MSG; + } + + if (eflags & VARE_UNDEFERR) { + free(varname); + *out_val = var_Error; + return VPR_UNDEF_SILENT; + } + + free(varname); + *out_val = varUndefined; + return VPR_OK; +} + /* Parse a long variable name enclosed in braces or parentheses such as $(VAR) * or ${VAR}, up to the closing brace or parenthesis, or in the case of * ${VAR:Modifiers}, up to the ':' that starts the modifiers. * Return whether to continue parsing. */ static Boolean ParseVarnameLong( - const char **pp, + const char *p, char startc, GNode *ctxt, VarEvalFlags eflags, + const char **out_FALSE_pp, VarParseResult *out_FALSE_res, const char **out_FALSE_val, - void **out_FALSE_freePtr, + void **out_FALSE_freeIt, char *out_TRUE_endc, const char **out_TRUE_p, Var **out_TRUE_v, Boolean *out_TRUE_haveModifier, const char **out_TRUE_extraModifiers, Boolean *out_TRUE_dynamic, VarExprFlags *out_TRUE_exprFlags ) { size_t namelen; char *varname; Var *v; Boolean haveModifier; Boolean dynamic = FALSE; - const char *const start = *pp; + const char *const start = p; char endc = startc == '(' ? ')' : '}'; - const char *p = start + 2; + p += 2; /* skip "${" or "$(" or "y(" */ varname = ParseVarname(&p, startc, endc, ctxt, eflags, &namelen); if (*p == ':') { haveModifier = TRUE; } else if (*p == endc) { haveModifier = FALSE; } else { Parse_Error(PARSE_FATAL, "Unclosed variable \"%s\"", varname); - *pp = p; free(varname); + *out_FALSE_pp = p; *out_FALSE_val = var_Error; *out_FALSE_res = VPR_PARSE_MSG; return FALSE; } v = VarFind(varname, ctxt, TRUE); /* At this point, p points just after the variable name, * either at ':' or at endc. */ - /* - * 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_CMDLINE && ctxt != VAR_GLOBAL && - namelen == 2 && (varname[1] == 'F' || varname[1] == 'D') && - strchr("@%?*!<>", varname[0]) != NULL) - { - /* - * Well, it's local -- go look for it. - */ - char name[] = { varname[0], '\0' }; - v = VarFind(name, ctxt, 0); + if (v == NULL) + v = FindLocalLegacyVar(varname, namelen, ctxt, out_TRUE_extraModifiers); - if (v != NULL) { - if (varname[1] == 'D') { - *out_TRUE_extraModifiers = "H:"; - } else { /* F */ - *out_TRUE_extraModifiers = "T:"; - } - } - } - if (v == NULL) { /* Defer expansion of dynamic variables if they appear in non-local * context since they are not defined there. */ dynamic = VarnameIsDynamic(varname, namelen) && (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL); if (!haveModifier) { p++; /* skip endc */ - *pp = p; - if (dynamic) { - char *pstr = bmake_strsedup(start, p); - free(varname); - *out_FALSE_res = VPR_OK; - *out_FALSE_freePtr = pstr; - *out_FALSE_val = pstr; - return FALSE; - } - - if ((eflags & VARE_UNDEFERR) && (eflags & VARE_WANTRES) && - DEBUG(LINT)) - { - Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", - varname); - free(varname); - *out_FALSE_res = VPR_UNDEF_MSG; - *out_FALSE_val = var_Error; - return FALSE; - } - - if (eflags & VARE_UNDEFERR) { - free(varname); - *out_FALSE_res = VPR_UNDEF_SILENT; - *out_FALSE_val = var_Error; - return FALSE; - } - - free(varname); - *out_FALSE_res = VPR_OK; - *out_FALSE_val = varUndefined; + *out_FALSE_pp = p; + *out_FALSE_res = EvalUndefined(dynamic, start, p, varname, eflags, + out_FALSE_freeIt, out_FALSE_val); return FALSE; } /* The variable expression is based on an undefined variable. * Nevertheless it needs a Var, for modifiers that access the * variable name, such as :L or :?. * * Most modifiers leave this expression in the "undefined" state * (VEF_UNDEF), only a few modifiers like :D, :U, :L, :P turn this * undefined expression into a defined expression (VEF_DEF). * * At the end, after applying all modifiers, if the expression * is still undefined, Var_Parse will return an empty string * instead of the actually computed value. */ v = VarNew(varname, varname, "", 0); *out_TRUE_exprFlags = VEF_UNDEF; } else free(varname); *out_TRUE_endc = endc; *out_TRUE_p = p; *out_TRUE_v = v; *out_TRUE_haveModifier = haveModifier; *out_TRUE_dynamic = dynamic; return TRUE; } -/*- - *----------------------------------------------------------------------- - * Var_Parse -- - * Given the start of a variable expression (such as $v, $(VAR), - * ${VAR:Mpattern}), extract the variable name, possibly some - * modifiers and find its value by applying the modifiers to the - * original value. +/* + * Given the start of a variable expression (such as $v, $(VAR), + * ${VAR:Mpattern}), extract the variable name and value, and the modifiers, + * if any. While doing that, apply the modifiers to the value of the + * expression, forming its final value. A few of the modifiers such as :!cmd! + * or ::= have side effects. * - * When parsing a condition in ParseEmptyArg, pp may also point to - * the "y" of "empty(VARNAME:Modifiers)", which is syntactically - * identical. - * * Input: - * str The string to parse - * ctxt The context for the variable - * flags Select the exact details of parsing - * out_val_freeIt Must be freed by the caller after using out_val + * *pp The string to parse. + * When parsing a condition in ParseEmptyArg, it may also + * point to the "y" of "empty(VARNAME:Modifiers)", which + * is syntactically the same. + * ctxt The context for finding variables + * eflags Control the exact details of parsing * - * Results: - * Returns the value of the variable expression, never NULL. - * Returns var_Error if there was a parse error and VARE_UNDEFERR was - * set. - * Returns varUndefined if there was an undefined variable and - * VARE_UNDEFERR was not set. - * - * Parsing should continue at *pp. - * TODO: Document the value of *pp on parse errors. It might be advanced - * by 0, or +1, or the index of the parse error, or the guessed end of the - * variable expression. - * - * If var_Error is returned, a diagnostic may or may not have been - * printed. XXX: This is inconsistent. - * - * If varUndefined is returned, a diagnostic may or may not have been - * printed. XXX: This is inconsistent. - * - * After using the returned value, *out_val_freeIt must be freed, - * preferably using bmake_free since it is NULL in most cases. - * - * Side Effects: - * Any effects from the modifiers, such as :!cmd! or ::=value. - *----------------------------------------------------------------------- + * Output: + * *pp The position where to continue parsing. + * TODO: After a parse error, the value of *pp is + * unspecified. It may not have been updated at all, + * point to some random character in the string, to the + * location of the parse error, or at the end of the + * string. + * *out_val The value of the variable expression, never NULL. + * *out_val var_Error if there was a parse error. + * *out_val var_Error if the base variable of the expression was + * undefined, eflags contains VARE_UNDEFERR, and none of + * the modifiers turned the undefined expression into a + * defined expression. + * XXX: It is not guaranteed that an error message has + * been printed. + * *out_val varUndefined if the base variable of the expression + * was undefined, eflags did not contain VARE_UNDEFERR, + * and none of the modifiers turned the undefined + * expression into a defined expression. + * XXX: It is not guaranteed that an error message has + * been printed. + * *out_val_freeIt Must be freed by the caller after using *out_val. */ /* coverity[+alloc : arg-*4] */ VarParseResult Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags, const char **out_val, void **out_val_freeIt) { - const char *const start = *pp; - const char *p; + const char *p = *pp; + const char *const start = p; Boolean haveModifier; /* TRUE if have modifiers for the variable */ char startc; /* Starting character if variable in parens * or braces */ char endc; /* Ending character if variable in parens * or braces */ 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 expression, unaltered */ const char *extramodifiers; Var *v; - char *nstr; + char *value; char eflags_str[VarEvalFlags_ToStringSize]; VarExprFlags exprFlags = 0; VAR_DEBUG2("Var_Parse: %s with %s\n", start, Enum_FlagsToString(eflags_str, sizeof eflags_str, eflags, VarEvalFlags_ToStringSpecs)); *out_val_freeIt = NULL; extramodifiers = NULL; /* extra modifiers to apply first */ dynamic = FALSE; /* Appease GCC, which thinks that the variable might not be * initialized. */ endc = '\0'; - startc = start[1]; + startc = p[1]; if (startc != '(' && startc != '{') { VarParseResult res; if (!ParseVarnameShort(startc, pp, ctxt, eflags, &res, out_val, &v)) return res; haveModifier = FALSE; - p = start + 1; + p++; } else { VarParseResult res; - if (!ParseVarnameLong(pp, startc, ctxt, eflags, - &res, out_val, out_val_freeIt, + if (!ParseVarnameLong(p, startc, ctxt, eflags, + pp, &res, out_val, out_val_freeIt, &endc, &p, &v, &haveModifier, &extramodifiers, &dynamic, &exprFlags)) return res; } if (v->flags & VAR_IN_USE) Fatal("Variable %s is recursive.", v->name); - /* - * 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 && (eflags & VARE_WANTRES)) { + /* XXX: This assignment creates an alias to the current value of the + * variable. This means that as long as the value of the expression stays + * the same, the value of the variable must not change. + * Using the '::=' modifier, it could be possible to do exactly this. + * At the bottom of this function, the resulting value is compared to the + * then-current value of the variable. This might also invoke undefined + * behavior. */ + value = Buf_GetAll(&v->val, NULL); + + /* Before applying any modifiers, expand any nested expressions from the + * variable value. */ + if (strchr(value, '$') != NULL && (eflags & VARE_WANTRES)) { VarEvalFlags nested_eflags = eflags; - if (DEBUG(LINT)) + if (opts.lint) nested_eflags &= ~(unsigned)VARE_UNDEFERR; v->flags |= VAR_IN_USE; - (void)Var_Subst(nstr, ctxt, nested_eflags, &nstr); + (void)Var_Subst(value, ctxt, nested_eflags, &value); v->flags &= ~(unsigned)VAR_IN_USE; /* TODO: handle errors */ - *out_val_freeIt = nstr; + *out_val_freeIt = value; } if (haveModifier || extramodifiers != NULL) { void *extraFree; extraFree = NULL; if (extramodifiers != NULL) { const char *em = extramodifiers; - nstr = ApplyModifiers(&em, nstr, '(', ')', - v, &exprFlags, ctxt, eflags, &extraFree); + value = ApplyModifiers(&em, value, '\0', '\0', + v, &exprFlags, ctxt, eflags, &extraFree); } if (haveModifier) { /* Skip initial colon. */ p++; - nstr = ApplyModifiers(&p, nstr, startc, endc, - v, &exprFlags, ctxt, eflags, out_val_freeIt); + value = ApplyModifiers(&p, value, startc, endc, + v, &exprFlags, ctxt, eflags, out_val_freeIt); free(extraFree); } else { *out_val_freeIt = extraFree; } } if (*p != '\0') /* Skip past endc if possible. */ p++; *pp = p; if (v->flags & VAR_FROM_ENV) { /* Free the environment variable now since we own it, * but don't free the variable value if it will be returned. */ - Boolean keepValue = nstr == Buf_GetAll(&v->val, NULL); + Boolean keepValue = value == Buf_GetAll(&v->val, NULL); if (keepValue) - *out_val_freeIt = nstr; + *out_val_freeIt = value; (void)VarFreeEnv(v, !keepValue); } else if (exprFlags & VEF_UNDEF) { if (!(exprFlags & VEF_DEF)) { + /* TODO: Use a local variable instead of out_val_freeIt. + * Variables named out_* must only be written to. */ if (*out_val_freeIt != NULL) { free(*out_val_freeIt); *out_val_freeIt = NULL; } if (dynamic) { - nstr = bmake_strsedup(start, p); - *out_val_freeIt = nstr; + value = bmake_strsedup(start, p); + *out_val_freeIt = value; } else { /* The expression is still undefined, therefore discard the * actual value and return an error marker instead. */ - nstr = (eflags & VARE_UNDEFERR) ? var_Error : varUndefined; + value = eflags & VARE_UNDEFERR ? var_Error : varUndefined; } } - if (nstr != Buf_GetAll(&v->val, NULL)) + if (value != Buf_GetAll(&v->val, NULL)) Buf_Destroy(&v->val, TRUE); free(v->name_freeIt); free(v); } - *out_val = nstr; + *out_val = value; return VPR_UNKNOWN; } -/* Substitute for all variables in the given string in the given context. +static void +VarSubstNested(const char **const pp, Buffer *const buf, GNode *const ctxt, + VarEvalFlags const eflags, Boolean *inout_errorReported) +{ + const char *p = *pp; + const char *nested_p = p; + const char *val; + void *val_freeIt; + + (void)Var_Parse(&nested_p, ctxt, eflags, &val, &val_freeIt); + /* TODO: handle errors */ + + if (val == var_Error || val == varUndefined) { + if (!preserveUndefined) { + p = nested_p; + } else if ((eflags & VARE_UNDEFERR) || val == var_Error) { + /* XXX: This condition is wrong. If val == var_Error, + * this doesn't necessarily mean there was an undefined + * variable. It could equally well be a parse error; see + * unit-tests/varmod-order.exp. */ + + /* + * If variable is undefined, complain and skip the + * variable. The complaint will stop us from doing anything + * when the file is parsed. + */ + if (!*inout_errorReported) { + Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"", + (int)(size_t)(nested_p - p), p); + } + p = nested_p; + *inout_errorReported = TRUE; + } else { + /* Copy the initial '$' of the undefined expression, + * thereby deferring expansion of the expression, but + * expand nested expressions if already possible. + * See unit-tests/varparse-undef-partial.mk. */ + Buf_AddByte(buf, *p); + p++; + } + } else { + p = nested_p; + Buf_AddStr(buf, val); + } + + free(val_freeIt); + + *pp = p; +} + +/* Expand all variable expressions like $V, ${VAR}, $(VAR:Modifiers) in the + * given string. * - * If eflags & VARE_UNDEFERR, Parse_Error will be called when an undefined - * variable is encountered. - * - * If eflags & VARE_WANTRES, any effects from the modifiers, such as ::=, - * :sh or !cmd! take place. - * * Input: - * str the string which to substitute - * ctxt the context wherein to find variables - * eflags VARE_UNDEFERR if undefineds are an error - * VARE_WANTRES if we actually want the result - * VARE_ASSIGN if we are in a := assignment - * - * Results: - * The resulting string. + * str The string in which the variable expressions are + * expanded. + * ctxt The context in which to start searching for + * variables. The other contexts are searched as well. + * eflags Special effects during expansion. */ VarParseResult Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res) { const char *p = str; Buffer buf; /* Buffer for forming things */ /* Set true if an error has already been reported, * to prevent a plethora of messages when recursing */ + /* XXX: Why is the 'static' necessary here? */ static Boolean errorReported; - Buf_Init(&buf, 0); + Buf_Init(&buf); errorReported = FALSE; while (*p != '\0') { if (p[0] == '$' && p[1] == '$') { /* A dollar sign may be escaped with another dollar sign. */ - if (save_dollars && (eflags & VARE_ASSIGN)) + if (save_dollars && (eflags & VARE_KEEP_DOLLAR)) Buf_AddByte(&buf, '$'); Buf_AddByte(&buf, '$'); p += 2; - } else if (*p != '$') { + + } else if (p[0] == '$') { + VarSubstNested(&p, &buf, ctxt, eflags, &errorReported); + + } else { /* * Skip as many characters as possible -- either to the end of * the string or to the next dollar sign (variable expression). */ const char *plainStart = p; for (p++; *p != '$' && *p != '\0'; p++) continue; Buf_AddBytesBetween(&buf, plainStart, p); - } else { - const char *nested_p = p; - void *freeIt; - const char *val; - (void)Var_Parse(&nested_p, ctxt, eflags, &val, &freeIt); - /* TODO: handle errors */ - - if (val == var_Error || val == varUndefined) { - /* - * 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) { - p = nested_p; - } else if ((eflags & VARE_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\"", - (int)(size_t)(nested_p - p), p); - } - p = nested_p; - errorReported = TRUE; - } else { - /* Copy the initial '$' of the undefined expression, - * thereby deferring expansion of the expression, but - * expand nested expressions if already possible. - * See unit-tests/varparse-undef-partial.mk. */ - Buf_AddByte(&buf, *p); - p++; - } - } else { - p = nested_p; - Buf_AddStr(&buf, val); - } - free(freeIt); - freeIt = NULL; } } *out_res = Buf_DestroyCompact(&buf); return VPR_OK; } /* Initialize the variables module. */ void Var_Init(void) { - VAR_INTERNAL = Targ_NewGN("Internal"); - VAR_GLOBAL = Targ_NewGN("Global"); - VAR_CMDLINE = Targ_NewGN("Command"); + VAR_INTERNAL = GNode_New("Internal"); + VAR_GLOBAL = GNode_New("Global"); + VAR_CMDLINE = GNode_New("Command"); } /* Clean up the variables module. */ void Var_End(void) { Var_Stats(); } void Var_Stats(void) { HashTable_DebugStats(&VAR_GLOBAL->context, "VAR_GLOBAL"); } /* Print all variables in a context, sorted by name. */ void Var_Dump(GNode *ctxt) { Vector /* of const char * */ vec; HashIter hi; size_t i; const char **varnames; Vector_Init(&vec, sizeof(const char *)); HashIter_Init(&hi, &ctxt->context); while (HashIter_Next(&hi) != NULL) *(const char **)Vector_Push(&vec) = hi.entry->key; varnames = vec.items; qsort(varnames, vec.len, sizeof varnames[0], str_cmp_asc); for (i = 0; i < vec.len; i++) { const char *varname = varnames[i]; Var *var = HashTable_FindValue(&ctxt->context, varname); debug_printf("%-16s = %s\n", varname, Buf_GetAll(&var->val, NULL)); } Vector_Done(&vec); } Index: head/contrib/bmake =================================================================== --- head/contrib/bmake (revision 367862) +++ head/contrib/bmake (revision 367863) Property changes on: head/contrib/bmake ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /vendor/NetBSD/bmake/dist:r367462-367861 Index: head/usr.bin/bmake/Makefile =================================================================== --- head/usr.bin/bmake/Makefile (revision 367862) +++ head/usr.bin/bmake/Makefile (revision 367863) @@ -1,159 +1,165 @@ # This is a generated file, do NOT edit! # See contrib/bmake/bsd.after-import.mk # # $FreeBSD$ SRCTOP?= ${.CURDIR:H:H} # look here first for config.h CFLAGS+= -I${.CURDIR} # for after-import CLEANDIRS+= FreeBSD CLEANFILES+= bootstrap -# $Id: Makefile,v 1.113 2020/10/26 17:55:09 sjg Exp $ +# $Id: Makefile,v 1.114 2020/11/13 21:47:25 sjg Exp $ PROG?= ${.CURDIR:T} SRCS= \ arch.c \ buf.c \ compat.c \ cond.c \ dir.c \ enum.c \ for.c \ hash.c \ job.c \ lst.c \ main.c \ make.c \ make_malloc.c \ meta.c \ metachar.c \ parse.c \ str.c \ suff.c \ targ.c \ trace.c \ util.c \ var.c .sinclude "Makefile.inc" # 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}\"" + +.for x in FORCE_MACHINE FORCE_MACHINE_ARCH +.ifdef $x +COPTS.main.c+= "-D$x=\"${$x}\"" +.endif +.endfor # meta mode can be useful even without filemon # should be set by now USE_FILEMON ?= no .if ${USE_FILEMON:tl} != "no" .PATH: ${srcdir}/filemon SRCS+= filemon_${USE_FILEMON}.c COPTS.meta.c+= -DUSE_FILEMON -DUSE_FILEMON_${USE_FILEMON:tu} COPTS.job.c+= ${COPTS.meta.c} .if ${USE_FILEMON} == "dev" FILEMON_H ?= /usr/include/dev/filemon/filemon.h .if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h" COPTS.filemon_dev.c += -DHAVE_FILEMON_H -I${FILEMON_H:H} .endif .endif # USE_FILEMON == dev .endif # USE_FILEMON .PATH: ${srcdir} .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: @(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 '/^.Dt/s/MAKE/${PROG:tu}/' \ -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= ${SHAREDIR.bmake:U${prefix}/share} BINDIR= ${BINDIR.bmake:U${prefix}/bin} MANDIR= ${MANDIR.bmake:U${SHAREDIR}/man} .if !exists(.depend) ${OBJS}: config.h .endif # A simple unit-test driver to help catch regressions TEST_MAKE ?= ${.OBJDIR}/${PROG:T} accept test: cd ${.CURDIR}/unit-tests && \ MAKEFLAGS= ${TEST_MAKE} -r -m / ${.TARGET} ${TESTS:DTESTS=${TESTS:Q}} # 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/Makefile.config =================================================================== --- head/usr.bin/bmake/Makefile.config (revision 367862) +++ head/usr.bin/bmake/Makefile.config (revision 367863) @@ -1,27 +1,27 @@ # This is a generated file, do NOT edit! # See contrib/bmake/bsd.after-import.mk # # $FreeBSD$ SRCTOP?= ${.CURDIR:H:H} # things set by configure -_MAKE_VERSION?=20201101 +_MAKE_VERSION?=20201117 prefix?= /usr srcdir= ${SRCTOP}/contrib/bmake CC?= cc DEFAULT_SYS_PATH?= .../share/mk:/usr/share/mk CPPFLAGS+= CFLAGS+= ${CPPFLAGS} -DHAVE_CONFIG_H LDFLAGS+= LIBOBJS+= ${LIBOBJDIR}stresep$U.o LDADD+= USE_META?= yes USE_FILEMON?= dev FILEMON_H?= /usr/include/dev/filemon/filemon.h BMAKE_PATH_MAX?= 1024 # used if MAXPATHLEN not defined CPPFLAGS+= -DBMAKE_PATH_MAX=${BMAKE_PATH_MAX} Index: head/usr.bin/bmake/config.h =================================================================== --- head/usr.bin/bmake/config.h (revision 367862) +++ head/usr.bin/bmake/config.h (revision 367863) @@ -1,346 +1,346 @@ /* $FreeBSD$ */ /* config.h. Generated from config.h.in by configure. */ /* config.h.in. Generated from configure.in by autoheader. */ /* Define if building universal (internal helper macro) */ /* #undef AC_APPLE_UNIVERSAL_BUILD */ /* Path of default shell */ /* #undef DEFSHELL_CUSTOM */ /* Shell spec to use by default */ /* #undef DEFSHELL_INDEX */ /* Define to 1 if you have the header file. */ #define HAVE_AR_H 1 /* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you don't. */ #define HAVE_DECL_SYS_SIGLIST 1 /* Define to 1 if you have the header file, and it defines `DIR'. */ #define HAVE_DIRENT_H 1 /* Define to 1 if you have the `dirname' function. */ #define HAVE_DIRNAME 1 /* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ /* #undef HAVE_DOPRNT */ /* Define to 1 if you have the `err' function. */ #define HAVE_ERR 1 /* Define to 1 if you have the `errx' function. */ #define HAVE_ERRX 1 /* Define to 1 if you have the header file. */ #define HAVE_ERR_H 1 /* Define to 1 if you have the header file. */ #define HAVE_FCNTL_H 1 /* Define to 1 if you have the `fork' function. */ #define HAVE_FORK 1 /* Define to 1 if you have the `getcwd' function. */ #define HAVE_GETCWD 1 /* Define to 1 if you have the `getenv' function. */ #define HAVE_GETENV 1 /* Define to 1 if you have the `getopt' function. */ #define HAVE_GETOPT 1 /* Define to 1 if you have the `getwd' function. */ #define HAVE_GETWD 1 /* Define to 1 if you have the header file. */ #define HAVE_INTTYPES_H 1 /* Define to 1 if you have the `killpg' function. */ #define HAVE_KILLPG 1 /* Define to 1 if you have the header file. */ #define HAVE_LIBGEN_H 1 /* Define to 1 if you have the header file. */ #define HAVE_LIMITS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_MEMORY_H 1 /* Define to 1 if you have the `mmap' function. */ #define HAVE_MMAP 1 /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_NDIR_H */ /* Define to 1 if you have the header file. */ #define HAVE_PATHS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_POLL_H 1 /* Define to 1 if you have the `putenv' function. */ #define HAVE_PUTENV 1 /* Define to 1 if you have the header file. */ #define HAVE_RANLIB_H 1 /* Define to 1 if you have the `realpath' function. */ #define HAVE_REALPATH 1 /* Define to 1 if you have the `select' function. */ #define HAVE_SELECT 1 /* Define to 1 if you have the `setenv' function. */ #define HAVE_SETENV 1 /* Define to 1 if you have the `setpgid' function. */ #define HAVE_SETPGID 1 /* Define to 1 if you have the `setsid' function. */ #define HAVE_SETSID 1 /* Define to 1 if you have the `sigaction' function. */ #define HAVE_SIGACTION 1 /* Define to 1 if you have the `sigvec' function. */ #define HAVE_SIGVEC 1 /* Define to 1 if you have the `snprintf' function. */ #define HAVE_SNPRINTF 1 /* Define to 1 if you have the header file. */ #define HAVE_STDINT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the `strerror' function. */ #define HAVE_STRERROR 1 /* Define to 1 if you have the `stresep' function. */ /* #undef HAVE_STRESEP */ /* Define to 1 if you have the `strftime' function. */ #define HAVE_STRFTIME 1 /* Define to 1 if you have the header file. */ #define HAVE_STRINGS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRING_H 1 /* Define to 1 if you have the `strlcpy' function. */ #define HAVE_STRLCPY 1 /* Define to 1 if you have the `strsep' function. */ #define HAVE_STRSEP 1 /* Define to 1 if you have the `strtod' function. */ #define HAVE_STRTOD 1 /* Define to 1 if you have the `strtol' function. */ #define HAVE_STRTOL 1 /* Define to 1 if `st_rdev' is a member of `struct stat'. */ #define HAVE_STRUCT_STAT_ST_RDEV 1 /* Define to 1 if your `struct stat' has `st_rdev'. Deprecated, use `HAVE_STRUCT_STAT_ST_RDEV' instead. */ #define HAVE_ST_RDEV 1 /* Define to 1 if you have the `sysctl' function. */ #define HAVE_SYSCTL 1 /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_SYS_DIR_H */ /* Define to 1 if you have the header file. */ #define HAVE_SYS_MMAN_H 1 /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_SYS_NDIR_H */ /* Define to 1 if you have the header file. */ #define HAVE_SYS_PARAM_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_SELECT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_SOCKET_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_STAT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_SYSCTL_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TIME_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TYPES_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_UIO_H 1 /* Define to 1 if you have that is POSIX.1 compatible. */ #define HAVE_SYS_WAIT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_UNISTD_H 1 /* Define to 1 if you have the `unsetenv' function. */ #define HAVE_UNSETENV 1 /* Define to 1 if you have the header file. */ #define HAVE_UTIME_H 1 /* Define to 1 if you have the `vfork' function. */ #define HAVE_VFORK 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_VFORK_H */ /* Define to 1 if you have the `vprintf' function. */ #define HAVE_VPRINTF 1 /* Define to 1 if you have the `vsnprintf' function. */ #define HAVE_VSNPRINTF 1 /* Define to 1 if you have the `wait3' function. */ #define HAVE_WAIT3 1 /* Define to 1 if you have the `wait4' function. */ #define HAVE_WAIT4 1 /* Define to 1 if you have the `waitpid' function. */ #define HAVE_WAITPID 1 /* Define to 1 if you have the `warn' function. */ #define HAVE_WARN 1 /* Define to 1 if you have the `warnx' function. */ #define HAVE_WARNX 1 /* Define to 1 if `fork' works. */ #define HAVE_WORKING_FORK 1 /* Define to 1 if `vfork' works. */ #define HAVE_WORKING_VFORK 1 /* define if your compiler has __attribute__ */ #define HAVE___ATTRIBUTE__ 1 /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "sjg@NetBSD.org" /* Define to the full name of this package. */ #define PACKAGE_NAME "bmake" /* Define to the full name and version of this package. */ -#define PACKAGE_STRING "bmake 20201018" +#define PACKAGE_STRING "bmake 20201112" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "bmake" /* Define to the home page for this package. */ #define PACKAGE_URL "" /* Define to the version of this package. */ -#define PACKAGE_VERSION "20201018" +#define PACKAGE_VERSION "20201112" /* Define as the return type of signal handlers (`int' or `void'). */ #define RETSIGTYPE void /* Define to 1 if the `S_IS*' macros in do not work properly. */ /* #undef STAT_MACROS_BROKEN */ /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define to 1 if you can safely include both and . */ #define TIME_WITH_SYS_TIME 1 /* Define to 1 if your declares `struct tm'. */ /* #undef TM_IN_SYS_TIME */ /* Enable extensions on AIX 3, Interix. */ #ifndef _ALL_SOURCE # define _ALL_SOURCE 1 #endif /* Enable GNU extensions on systems that have them. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE 1 #endif /* Enable threading extensions on Solaris. */ #ifndef _POSIX_PTHREAD_SEMANTICS # define _POSIX_PTHREAD_SEMANTICS 1 #endif /* Enable extensions on HP NonStop. */ #ifndef _TANDEM_SOURCE # define _TANDEM_SOURCE 1 #endif /* Enable general extensions on Solaris. */ #ifndef __EXTENSIONS__ # define __EXTENSIONS__ 1 #endif /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel). */ #if defined AC_APPLE_UNIVERSAL_BUILD # if defined __BIG_ENDIAN__ # define WORDS_BIGENDIAN 1 # endif #else # ifndef WORDS_BIGENDIAN /* # undef WORDS_BIGENDIAN */ # endif #endif /* Define to 1 if on MINIX. */ /* #undef _MINIX */ /* Define to 2 if the system does not provide POSIX.1 features except with this defined. */ /* #undef _POSIX_1_SOURCE */ /* Define to 1 if you need to in order for `stat' and other things to work. */ /* #undef _POSIX_SOURCE */ /* Define for Solaris 2.5.1 so the uint32_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT32_T */ /* C99 function name */ /* #undef __func__ */ /* Define to empty if `const' does not conform to ANSI C. */ /* #undef const */ /* Define to `int' if does not define. */ /* #undef mode_t */ /* Define to `long int' if does not define. */ /* #undef off_t */ /* Define to `int' if does not define. */ /* #undef pid_t */ /* Define to `unsigned int' if does not define. */ /* #undef size_t */ /* Define to the type of an unsigned integer type of width exactly 32 bits if such a type exists and the standard includes do not define it. */ /* #undef uint32_t */ /* Define as `fork' if `vfork' does not work. */ /* #undef vfork */ Index: head/usr.bin/bmake/unit-tests/Makefile =================================================================== --- head/usr.bin/bmake/unit-tests/Makefile (revision 367862) +++ head/usr.bin/bmake/unit-tests/Makefile (revision 367863) @@ -1,569 +1,606 @@ # This is a generated file, do NOT edit! # See contrib/bmake/bsd.after-import.mk # # $FreeBSD$ -# $Id: Makefile,v 1.107 2020/11/02 00:40:25 sjg Exp $ +# $Id: Makefile,v 1.115 2020/11/18 04:01:07 sjg Exp $ # -# $NetBSD: Makefile,v 1.181 2020/11/01 19:02:22 rillig Exp $ +# $NetBSD: Makefile,v 1.206 2020/11/18 01:12:00 sjg Exp $ # # Unit tests for make(1) # # The main targets are: # # all: # run all the tests # test: # run 'all', and compare to expected results # accept: # move generated output to expected results # # Settable variables # # TEST_MAKE # The make program to be tested. # # # Adding a test case # # Each feature should get its own set of tests in its own suitably # named makefile (*.mk), with its own set of expected results (*.exp), # and it should be added to the TESTS list. # # A few *.mk files are helper files for other tests (such as include-sub.mk) # and are thus not added to TESTS. Such files must be ignored in # src/tests/usr.bin/make/t_make.sh. # # Each test is in a sub-makefile. # Keep the list sorted. # Any test that is commented out must be ignored in # src/tests/usr.bin/make/t_make.sh as well. #TESTS+= archive TESTS+= archive-suffix +TESTS+= cmd-errors +TESTS+= cmd-errors-lint TESTS+= cmd-interrupt TESTS+= cmdline +TESTS+= cmdline-undefined TESTS+= comment TESTS+= cond-cmp-numeric TESTS+= cond-cmp-numeric-eq TESTS+= cond-cmp-numeric-ge TESTS+= cond-cmp-numeric-gt TESTS+= cond-cmp-numeric-le TESTS+= cond-cmp-numeric-lt TESTS+= cond-cmp-numeric-ne TESTS+= cond-cmp-string TESTS+= cond-cmp-unary TESTS+= cond-func TESTS+= cond-func-commands TESTS+= cond-func-defined TESTS+= cond-func-empty TESTS+= cond-func-exists TESTS+= cond-func-make TESTS+= cond-func-target TESTS+= cond-late TESTS+= cond-op TESTS+= cond-op-and +TESTS+= cond-op-and-lint TESTS+= cond-op-not TESTS+= cond-op-or +TESTS+= cond-op-or-lint TESTS+= cond-op-parentheses TESTS+= cond-short TESTS+= cond-token-number TESTS+= cond-token-plain TESTS+= cond-token-string TESTS+= cond-token-var TESTS+= cond-undef-lint TESTS+= cond1 TESTS+= counter TESTS+= counter-append TESTS+= dep TESTS+= dep-colon TESTS+= dep-colon-bug-cross-file TESTS+= dep-double-colon TESTS+= dep-double-colon-indep TESTS+= dep-exclam TESTS+= dep-none TESTS+= dep-percent TESTS+= dep-var TESTS+= dep-wildcards TESTS+= depsrc TESTS+= depsrc-end TESTS+= depsrc-exec TESTS+= depsrc-ignore TESTS+= depsrc-made TESTS+= depsrc-make TESTS+= depsrc-meta TESTS+= depsrc-nometa TESTS+= depsrc-nometa_cmp TESTS+= depsrc-nopath TESTS+= depsrc-notmain TESTS+= depsrc-optional TESTS+= depsrc-phony TESTS+= depsrc-precious TESTS+= depsrc-recursive TESTS+= depsrc-silent TESTS+= depsrc-use TESTS+= depsrc-usebefore TESTS+= depsrc-usebefore-double-colon TESTS+= depsrc-wait TESTS+= deptgt TESTS+= deptgt-begin TESTS+= deptgt-default TESTS+= deptgt-delete_on_error TESTS+= deptgt-end TESTS+= deptgt-end-jobs TESTS+= deptgt-error TESTS+= deptgt-ignore TESTS+= deptgt-interrupt TESTS+= deptgt-main TESTS+= deptgt-makeflags TESTS+= deptgt-no_parallel TESTS+= deptgt-nopath TESTS+= deptgt-notparallel TESTS+= deptgt-objdir TESTS+= deptgt-order TESTS+= deptgt-path TESTS+= deptgt-path-suffix TESTS+= deptgt-phony TESTS+= deptgt-precious TESTS+= deptgt-shell TESTS+= deptgt-silent TESTS+= deptgt-stale TESTS+= deptgt-suffixes TESTS+= dir TESTS+= dir-expand-path TESTS+= directive TESTS+= directive-dinclude TESTS+= directive-elif TESTS+= directive-elifdef TESTS+= directive-elifmake TESTS+= directive-elifndef TESTS+= directive-elifnmake TESTS+= directive-else TESTS+= directive-endif TESTS+= directive-error TESTS+= directive-export TESTS+= directive-export-env TESTS+= directive-export-gmake TESTS+= directive-export-literal TESTS+= directive-for TESTS+= directive-for-generating-endif TESTS+= directive-hyphen-include TESTS+= directive-if +TESTS+= directive-if-nested TESTS+= directive-ifdef TESTS+= directive-ifmake TESTS+= directive-ifndef TESTS+= directive-ifnmake TESTS+= directive-include TESTS+= directive-include-fatal TESTS+= directive-info TESTS+= directive-sinclude TESTS+= directive-undef TESTS+= directive-unexport TESTS+= directive-unexport-env TESTS+= directive-warning -TESTS+= directives TESTS+= dollar TESTS+= doterror TESTS+= dotwait TESTS+= envfirst TESTS+= error TESTS+= # escape # broken by reverting POSIX changes TESTS+= export TESTS+= export-all TESTS+= export-env TESTS+= export-variants TESTS+= forloop TESTS+= forsubst +TESTS+= gnode-submake TESTS+= hanoi-include TESTS+= impsrc TESTS+= include-main +TESTS+= job-flags #TESTS+= job-output-long-lines TESTS+= lint TESTS+= make-exported TESTS+= moderrs TESTS+= modmatch TESTS+= modmisc TESTS+= modts TESTS+= modword +TESTS+= objdir-writable TESTS+= opt TESTS+= opt-backwards TESTS+= opt-chdir TESTS+= opt-debug TESTS+= opt-debug-all TESTS+= opt-debug-archive TESTS+= opt-debug-curdir TESTS+= opt-debug-cond TESTS+= opt-debug-dir TESTS+= opt-debug-errors TESTS+= opt-debug-file TESTS+= opt-debug-for TESTS+= opt-debug-graph1 TESTS+= opt-debug-graph2 TESTS+= opt-debug-graph3 TESTS+= opt-debug-hash #TESTS+= opt-debug-jobs TESTS+= opt-debug-lint TESTS+= opt-debug-loud TESTS+= opt-debug-meta TESTS+= opt-debug-making TESTS+= opt-debug-no-rm TESTS+= opt-debug-parse TESTS+= opt-debug-suff TESTS+= opt-debug-targets TESTS+= opt-debug-varraw TESTS+= opt-debug-var TESTS+= opt-debug-x-trace TESTS+= opt-define TESTS+= opt-env TESTS+= opt-file TESTS+= opt-ignore TESTS+= opt-include-dir TESTS+= opt-jobs TESTS+= opt-jobs-internal TESTS+= opt-keep-going TESTS+= opt-m-include-dir TESTS+= opt-no-action TESTS+= opt-no-action-at-all TESTS+= opt-query TESTS+= opt-raw TESTS+= opt-silent TESTS+= opt-touch +TESTS+= opt-touch-jobs TESTS+= opt-tracefile TESTS+= opt-var-expanded TESTS+= opt-var-literal TESTS+= opt-warnings-as-errors TESTS+= opt-where-am-i TESTS+= opt-x-reduce-exported TESTS+= order TESTS+= parse-var TESTS+= phony-end TESTS+= posix TESTS+= # posix1 # broken by reverting POSIX changes TESTS+= qequals TESTS+= recursive TESTS+= sh TESTS+= sh-dots TESTS+= sh-jobs TESTS+= sh-jobs-error TESTS+= sh-leading-at TESTS+= sh-leading-hyphen TESTS+= sh-leading-plus TESTS+= sh-meta-chars TESTS+= sh-multi-line TESTS+= sh-single-line TESTS+= shell-csh TESTS+= shell-custom +.if exists(/bin/ksh) TESTS+= shell-ksh +.endif TESTS+= shell-sh TESTS+= suff-add-later TESTS+= suff-clear-regular TESTS+= suff-clear-single TESTS+= suff-lookup TESTS+= suff-main TESTS+= suff-rebuild +TESTS+= suff-self TESTS+= suff-transform-endless TESTS+= suff-transform-expand TESTS+= suff-transform-select TESTS+= sunshcmd TESTS+= ternary TESTS+= unexport TESTS+= unexport-env TESTS+= use-inference TESTS+= var-class TESTS+= var-class-cmdline TESTS+= var-class-env TESTS+= var-class-global TESTS+= var-class-local TESTS+= var-class-local-legacy TESTS+= var-op TESTS+= var-op-append TESTS+= var-op-assign TESTS+= var-op-default TESTS+= var-op-expand TESTS+= var-op-shell TESTS+= var-op-sunsh TESTS+= var-recursive TESTS+= varcmd TESTS+= vardebug TESTS+= varfind TESTS+= varmisc TESTS+= varmod TESTS+= varmod-assign TESTS+= varmod-defined TESTS+= varmod-edge TESTS+= varmod-exclam-shell TESTS+= varmod-extension TESTS+= varmod-gmtime TESTS+= varmod-hash TESTS+= varmod-head TESTS+= varmod-ifelse TESTS+= varmod-l-name-to-value TESTS+= varmod-localtime TESTS+= varmod-loop TESTS+= varmod-match TESTS+= varmod-match-escape TESTS+= varmod-no-match TESTS+= varmod-order TESTS+= varmod-order-reverse TESTS+= varmod-order-shuffle TESTS+= varmod-path TESTS+= varmod-quote TESTS+= varmod-quote-dollar TESTS+= varmod-range TESTS+= varmod-remember TESTS+= varmod-root TESTS+= varmod-select-words TESTS+= varmod-shell TESTS+= varmod-subst TESTS+= varmod-subst-regex TESTS+= varmod-sysv TESTS+= varmod-tail TESTS+= varmod-to-abs TESTS+= varmod-to-lower TESTS+= varmod-to-many-words TESTS+= varmod-to-one-word TESTS+= varmod-to-separator TESTS+= varmod-to-upper TESTS+= varmod-undefined TESTS+= varmod-unique TESTS+= varname TESTS+= varname-dollar TESTS+= varname-dot-alltargets TESTS+= varname-dot-curdir TESTS+= varname-dot-includes TESTS+= varname-dot-includedfromdir TESTS+= varname-dot-includedfromfile TESTS+= varname-dot-libs TESTS+= varname-dot-make-dependfile TESTS+= varname-dot-make-expand_variables TESTS+= varname-dot-make-exported TESTS+= varname-dot-make-jobs TESTS+= varname-dot-make-jobs-prefix TESTS+= varname-dot-make-level TESTS+= varname-dot-make-makefile_preference TESTS+= varname-dot-make-makefiles TESTS+= varname-dot-make-meta-bailiwick TESTS+= varname-dot-make-meta-created TESTS+= varname-dot-make-meta-files TESTS+= varname-dot-make-meta-ignore_filter TESTS+= varname-dot-make-meta-ignore_paths TESTS+= varname-dot-make-meta-ignore_patterns TESTS+= varname-dot-make-meta-prefix TESTS+= varname-dot-make-mode TESTS+= varname-dot-make-path_filemon TESTS+= varname-dot-make-pid TESTS+= varname-dot-make-ppid TESTS+= varname-dot-make-save_dollars TESTS+= varname-dot-makeoverrides TESTS+= varname-dot-newline TESTS+= varname-dot-objdir TESTS+= varname-dot-parsedir TESTS+= varname-dot-parsefile TESTS+= varname-dot-path TESTS+= varname-dot-shell TESTS+= varname-dot-targets TESTS+= varname-empty TESTS+= varname-make TESTS+= varname-make_print_var_on_error TESTS+= varname-make_print_var_on_error-jobs TESTS+= varname-makefile TESTS+= varname-makeflags TESTS+= varname-pwd TESTS+= varname-vpath TESTS+= varparse-dynamic +TESTS+= varparse-errors TESTS+= varparse-mod TESTS+= varparse-undef-partial TESTS+= varquote -TESTS+= varshell +# Ideas for more tests: +# char-0020-space.mk +# char-005C-backslash.mk +# escape-cond-str.mk +# escape-cond-func-arg.mk +# escape-cond-func-arg.mk +# escape-varmod.mk +# escape-varmod-define.mk +# escape-varmod-match.mk +# escape-varname.mk +# escape-varassign-varname.mk +# escape-varassign-varname-cmdline.mk +# escape-varassign-value.mk +# escape-varassign-value-cmdline.mk +# escape-dependency-source.mk +# escape-dependency-target.mk +# escape-for-varname.mk +# escape-for-item.mk +# posix-*.mk (see posix.mk and posix1.mk) + +.if ${.OBJDIR} != ${.CURDIR} +RO_OBJDIR:= ${.OBJDIR}/roobj +.else +RO_OBJDIR:= ${TMPDIR:U/tmp}/roobj +.endif # Additional environment variables for some of the tests. # The base environment is -i PATH="$PATH". +ENV.depsrc-optional+= TZ=UTC ENV.envfirst= FROM_ENV=value-from-env +ENV.objdir-writable+= RO_OBJDIR=${RO_OBJDIR} ENV.varmisc= FROM_ENV=env ENV.varmisc+= FROM_ENV_BEFORE=env ENV.varmisc+= FROM_ENV_AFTER=env ENV.varmod-localtime+= TZ=Europe/Berlin +ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2 # Override make flags for some of the tests; default is -k. # If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of # settings FLAGS.test=-dv here, since that is closer to the test code. FLAGS.cond-func-make= via-cmdline FLAGS.directive-ifmake= first second -FLAGS.doterror= # none -FLAGS.envfirst= -e -FLAGS.export= # none -FLAGS.opt-ignore= -i -FLAGS.opt-keep-going= -k -FLAGS.opt-no-action= -n -FLAGS.opt-query= -q -FLAGS.opt-var-expanded= -v VAR -v VALUE -FLAGS.opt-var-literal= -V VAR -V VALUE -FLAGS.opt-warnings-as-errors= -W -FLAGS.order= -j1 -FLAGS.recursive= -dL -FLAGS.sh-leading-plus= -n -FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmline-plain' +FLAGS.doterror= # none, especially not -k +FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain' # Some tests need extra postprocessing. SED_CMDS.export= \ -e '/^[^=_A-Za-z0-9]*=/d' # these all share the same requirement .for t in export-all export-env SED_CMDS.$t= ${SED_CMDS.export} .endfor +SED_CMDS.directive-export-gmake= \ + ${:D dash is a pain } \ + -e /non-zero/d SED_CMDS.job-output-long-lines= \ ${:D Job separators on their own line are ok. } \ -e '/^--- job-[ab] ---$$/d' \ ${:D Plain output lines are ok as well. } \ ${:D They may come in multiples of 1024 or as 10000. } \ -e '/^aa*$$/d' \ -e '/^bb*$$/d' \ ${:D The following lines should rather not occur since the job } \ ${:D marker should always be at the beginning of the line. } \ -e '/^aa*--- job-b ---$$/d' \ -e '/^bb*--- job-a ---$$/d' +SED_CMDS.objdir-writable= -e 's,${RO_OBJDIR},OBJDIR/roobj,g' SED_CMDS.opt-debug-graph1= \ -e 's,${.CURDIR},CURDIR,' SED_CMDS.opt-debug-graph1+= \ -e '/Global Variables:/,/Suffixes:/d' SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,,' SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(),' SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid ,' SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process ,' SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: ,' # The "-q" may be there or not, see jobs.c, variable shells. SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: sh\) -q,\1,' +SED_CMDS.var-op-shell+= -e 's,^${.SHELL:T}: ,,' +SED_CMDS.var-op-shell+= -e '/command/{ s,^[1-9]: ,,;s,No such.*,not found,; }' +SED_CMDS.vardebug= \ + ${:D canonicalize .SHELL } \ + -e 's,${.SHELL},,' SED_CMDS.varmod-subst-regex+= \ -e 's,\(Regex compilation error:\).*,\1 (details omitted),' SED_CMDS.varmod-edge+= -e 's, line [0-9]*:, line omitted:,' -SED_CMDS.varshell+= -e 's,^${.SHELL:T}: ,,' -SED_CMDS.varshell+= -e '/command/s,No such.*,not found,' SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: ",' SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: ",' SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g' SED_CMDS.varname-dot-shell+= -e 's,"/[^" ]*","(details omitted)",g' SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g' # Some tests need an additional round of postprocessing. POSTPROC.deptgt-suffixes= \ ${TOOL_SED} -n -e '/^\#\*\*\* Suffixes/,/^\#\*/p' -POSTPROC.varname= ${TOOL_SED} -n -e '/^MAGIC/p' -e '/^ORDER_/p' +POSTPROC.gnode-submake= awk '/Input graph/, /^$$/' POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p' # Some tests reuse other tests, which makes them unnecessarily fragile. export-all.rawout: export.mk unexport.rawout: export.mk unexport-env.rawout: export.mk # End of the configuration section. .MAIN: all .sinclude "Makefile.inc" .sinclude "Makefile.config" UNIT_TESTS:= ${srcdir} .PATH: ${UNIT_TESTS} .if ${USE_ABSOLUTE_TESTNAMES:Uno} == yes OUTFILES= ${TESTS:@test@${.CURDIR:tA}/${test}.out@} .else OUTFILES= ${TESTS:=.out} .endif all: ${OUTFILES} CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp CLEANFILES+= obj*.[och] lib*.a # posix1.mk CLEANFILES+= issue* .[ab]* # suffixes.mk CLEANDIRS= dir dummy # posix1.mk clean: rm -f ${CLEANFILES} rm -rf ${CLEANDIRS} 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 .if ${.MAKE.MODE:Unormal:Mmeta} != "" # we don't need the noise _MKMSG_TEST= : .endif MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc # Each test is run in a sub-make, to keep the tests for interfering with # each other, and because they use different environment variables and # command line options. .SUFFIXES: .mk .rawout .out .mk.rawout: @${_MKMSG_TEST:Uecho '# test '} ${.PREFIX} @set -eu; \ cd ${.OBJDIR}; \ env -i PATH="$$PATH" ${MAKE_TEST_ENV} ${ENV.${.PREFIX:T}} \ ${TEST_MAKE} \ -r -C ${.CURDIR} -f ${.IMPSRC} \ ${FLAGS.${.PREFIX:T}:U-k} \ > ${.TARGET}.tmp 2>&1 \ && status=$$? || status=$$?; \ echo $$status > ${.TARGET:R}.status @mv ${.TARGET}.tmp ${.TARGET} # Postprocess the test output so that the results can be compared. # # always pretend .MAKE was called 'make' _SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,' _SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,' +_SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}[][0-9]* warning,make warning,' +_SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,' # replace anything after 'stopped in' with unit-tests _SED_CMDS+= -e '/stopped/s, /.*, unit-tests,' # strip ${.CURDIR}/ from the output _SED_CMDS+= -e 's,${.CURDIR:S,.,\\.,g}/,,g' _SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g' .rawout.out: @${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.PREFIX:T}} \ < ${.IMPSRC} > ${.TARGET}.tmp1 @${POSTPROC.${.PREFIX:T}:Ucat} < ${.TARGET}.tmp1 > ${.TARGET}.tmp2 @rm ${.TARGET}.tmp1 @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp2 @mv ${.TARGET}.tmp2 ${.TARGET} # Compare all output files test: ${OUTFILES} .PHONY @failed= ; \ for test in ${TESTS}; do \ ${TOOL_DIFF} ${DIFF_FLAGS} ${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: @for test in ${TESTS}; 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}) ${TESTS:=.rawout}: ${TEST_MAKE} # in meta mode, we *know* if a target script is impacted # by a makefile change. .if ${.MAKE.MODE:Unormal:Mmeta} == "" ${TESTS:=.rawout}: ${.PARSEDIR}/Makefile .endif .endif .sinclude