diff --git a/contrib/bmake/ChangeLog b/contrib/bmake/ChangeLog index 5cf6f9d8fe57..35235e1f8205 100644 --- a/contrib/bmake/ChangeLog +++ b/contrib/bmake/ChangeLog @@ -1,3386 +1,3499 @@ +2021-06-21 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20210621 + Merge with NetBSD make, pick up + o var.c: only report error for unmatched regex subexpression + when linting (-dL) since we cannot tell when an unmatched + subexpression is an expected result. + o move unmatched regex subexpression tests to + varmod-subst-regex.mk and enable strict (lint) mode + +2021-06-16 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20210616 + Merge with NetBSD make, pick up + o more unit tests + o cond.c: rename If_Eval to EvalBare + improve function names for parsing conditions + o job.c: fix error handling of targets that cannot be made + o var.c: uncompress code in ApplyModifier_Unique + +2021-05-18 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20210518 + Merge with NetBSD make, pick up + o fix unit-tests/opt-chdir to cope with /nonexistent existing. + o job.c: Print -de error information when running multiple jobs + +2021-04-20 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20210420 + Merge with NetBSD make, pick up + o use C99 bool type + o convert VarEvalFlags back into an enum + o cond.c: do not complain when skipping the condition 'no >= 10' + o hash.c: avoid allocating memory for simple variable names + o job.c: use distinct wording for writing to the shell commands file + remove type name for the abort status in job handling + rename PrintOutput to PrintFilteredOutput to avoid confusion + o main.c: avoid double slash in name of temporary directory + o var.c: use straight quotes for error 'Bad conditional expression' + reduce memory allocations in the modifiers ':D' and ':U' + rename members of ModifyWord_LoopArgs + clean up pattern flags for the modifiers ':S' and ':C' + reduce memory allocation and strlen calls in modifier ':from=to' + in the ':Q' modifier, only allocate memory if necessary + improve performance for LazyBuf + remove redundant parameter from ParseVarnameLong + migrate ParseModifierPart to use Substring + avoid unnecessary calls to strlen when evaluating modifiers + migrate ModifyWord functions to use Substring + migrate handling of the modifier ':S,from,to,' to Substring + reduce debug logging and memory allocation for ${:U...} + reduce verbosity of the -dv debug logging for standard cases + clean up debug logging for ':M' and ':N' + disallow '$' in the variable name of the modifier ':@' + simplify access to the name of an expression during evaluation + +2021-03-30 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20210330 + Merge with NetBSD make, pick up + o replace enum bit-field with struct bit-field for VarEvalFlags + o rename VARE_NONE to VARE_PARSE_ONLY + o var.c: rename ApplyModifiersState to ModChain + fix double varname expansion in the variable modifier '::=' + change debug log for variable evaluation flags to lowercase + +2021-03-14 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20210314 + Merge with NetBSD make, pick up + o var.c: avoid evaluating many modifiers in parse only mode + in strict mode (-dL) many variable references are parsed twice, + the first time just to report parse errors early, so we want to + avoid side effects and wasted effort to the extent possible. + +2021-02-26 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20210226 + Merge with NetBSD make, pick up + o remove freestanding freeIt variables + link via FStr + o var.c: restructure code in ParseVarname to target human readers + improve error message for; + bad modifier in variable expression + unclosed modifier + unknown modifier + remove redundant parameter of ApplySingleModifier + explain non-obvious code around indirect variable modifiers + quote ':S' in error message about missing delimiter + extract ParseModifier_Match into separate function + add context information to error message about ':range' modifier + add quotes around variable name in an error message + reorder code in ModifyWords + use more common parameter order for VarSelectWords + make ModifyWord_Subst a little easier to understand + do not expand variable name from the command line twice + extract ExistsInCmdline from Var_SetWithFlags + save a hash map lookup when defining a cmdline variable + clean up VarAdd, Var_Delete, Var_ReexportVars + use bit-shift expressions for VarFlags constants + rename constants for VarFlags + rename ExprDefined constants for debug logging + rename ExprStatus to ExprDefined + split parameters for evaluating variable expressions + reduce redundant code around ModifyWords + print error about failed shell command before overwriting variable + clean up ValidShortVarname, ParseVarnameShort + rename VarExprStatus to ExprStatus + add functions for assigning the value of an expression + rename ApplyModifiersState_Define to Expr_Define + condense the code for parsing :S and :C modifiers + 2021-02-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210206 Merge with NetBSD make, pick up o unit-tests: use private TMPDIR to avoid errors from other users 2021-02-05 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210205 Merge with NetBSD make, pick up o avoid strdup in mkTempFile o always use vfork o rename context and ctxt to scope o rename some VAR constants to SCOPE o Var_ functions, move the scope to the front o use shortcut functions Global_Set and Global_Append o add shortcut Global_Delete for deleting a global variable o rename Var_Delete to Var_DeleteExpand, Var_DeleteVar to Var_Delete o compat.c: when exiting due to an error, print graph information o enum.c: remove overengineered Enum_ValueToString o make.c: remove unused INTERNAL flag remove unused return type of MakeBuildParent o parse.c: replace parse error "Need an operator" with better message o var.c: improve documentation about variable scopes rename Var_ValueDirect to GNode_ValueDirect rename old Var_SetWithFlags to Var_SetExpandWithFlags merge SetVar into Var_SetWithFlags split Var_Exists into plain Var_Exists and Var_ExistsExpand split Var_Append into Var_Append and Var_AppendExpand replace enum bit-set with bit-field o unit-tests/var-op-shell: use kill rather than kill -14 which broke on darwin with recent update. 2021-02-01 Simon J Gerraty * configure.in: check for sig_atomic_t and define it as 'int' if missing. * VERSION (_MAKE_VERSION): 20210201 Merge with NetBSD make, pick up o use sig_atomic_t for caught_sigchld 2021-01-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210130 Merge with NetBSD make, pick up o more unit tests o convert SearchPath to struct o split Buf_Destroy into Buf_Done and Buf_DoneData o for.c: split For_Eval into separate functions rename struct For to struct ForLoop o job.c: do not create empty shell files in jobs mode rename JobOpenTmpFile to JobWriteShellCommands reduce unnecessary calls to waitpid o parse.c: in -dp mode, print stack trace with each diagnostic 2021-01-23 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210123 Merge with NetBSD make, pick up o rename Dir_Expand to SearchPath_Expand o rename Dir_AddDir, reorder parameters of SearchPath_ToFlags o cond.c: fix debug output for comparison operators in conditionals o dir.c: split Dir_FindFile into separate functions 2021-01-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210120 Merge with NetBSD make, pick up o fix some more lint nits o refine some unit tests for portability o cond.c: rework parsing 2021-01-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210110 Merge with NetBSD make, pick up o fix lint warnings o consistently use boolean expressions in conditions 2021-01-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210108 Merge with NetBSD make, pick up o job.c: back to polling token pipe if we want a token o main.c: always print 'stopped in' on first call The execption is if we bail because of an abort token in which case just exit 6. 2021-01-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210101 Merge with NetBSD make, pick up o Happy New Year! o rename CmdOpts.lint to strict o exit 2 on technical errors o replace pointers in controlling conditions with booleans o replace global preserveUndefined with VARE_KEEP_UNDEF o compat.c: re-export variables from the actual make process if using vfork this is the effect anyway o cond.c: clean up VarParseResult constants o for.c: fix undefined behavior in SubstVarLong make control flow in SubstVarLong of .for loops more obvious clean up SubstVarShort in .for loops extract ForSubstBody from ForReadMore clean up ForReadMore simplify termination condition for .for loop add error handling for .for loop items job.c: re-export variables from the actual make process parse.c: remove mmap for loading files, only allow files < 1 GiB fix edge case in := with undefined in variable name skip variable expansion in ParseDependencyTargetWord var.c: split ExportVar into separate functions clean up code in extracted ExportVar functions remove dead code from ApplyModifiersIndirect split Var_Subst into easily understandable functions clean up VarParseResult constants 2020-12-25 Simon J Gerraty * main.c: use .MAKE.DEPENDFILE as set by makefiles 2020-12-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201222 Merge with NetBSD make, pick up o make DEBUG macro return boolean o parse.c: fix assertion failure for files without trailing newline o var.c: allow .undef to undefine multiple variables at once remove excess newline from parse errors 2020-12-21 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201221 Merge with NetBSD make, pick up o some unit-test updates 2020-12-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201220 Merge with NetBSD make, pick up o more unit tests o return FStr from Var_Parse and Var_Value o spell nonexistent consistently o add str_basename to reduce duplicate code o compat.c: fix .ERROR_TARGET in compat -k mode extract InitSignals from Compat_Run extract UseShell from Compat_RunCommand o cond.c: error out if an '.endif' or '.else' contain extraneous text o for.c: rename ForIterate to ForReadMore o hash.c: clean up hash function for HashTable o lst.c: rename Vector.priv_cap to cap o main.c: remove constant parameter from MakeMode o make.c: use symbolic time for 0 in Make_Recheck extract MakeChildren from MakeStartJobs o parse.c: clean up memory handling in VarAssign_EvalShell, Parse_DoVar fix error message for .info/.warning/.error without argument extract Var_Undef from ParseDirective extract ParseSkippedBranches, ParseForLoop from ParseReadLine rename mode constants for ParseGetLine to be more expressive reduce debugging details in Parse_SetInput fix line numbers in .for loops split ParseGetLine into separate functions fix garbled output for failed shell command var.c: remove redundant assignment in ApplyModifier_SysV error out on unknown variable modifiers at parse time remove wrong error message for indirect modifier in lint mode extract ApplySingleModifier from ApplyModifiers use FStr for memory management in Var_SetWithFlags extract SetVar from Var_SetWithFlags use FStr in VarNew extract string functions from ApplyModifier_To error out if .undef has not exactly 1 argument extract Var_DeleteVar from Var_Delete extract Var_Undef from ParseDirective clean up memory management for expanding variable expressions 2020-12-12 Simon J Gerraty * avoid %zu * lst.c: avoid anonymous union * VERSION (_MAKE_VERSION): 20201212 Merge with NetBSD make, pick up o more unit tests o inline Targ_Ignore and Targ_Silent o split JobFlags into separate fields o remove const from function parameters (left overs from refactoring) o eliminate boolean argument of Var_Export o make API of Buf_Init simpler o rename ParseRunOptions to ParseCommandFlags o replace *line with line[0] o compat.c: fix wrong exit status for multiple failed main targets refactor Compat_Run to show the error condition more clearly don't make .END if the main targets already failed (-k mode) fix exit status in -k mode if a dependency fails o for.c: clean up Buf_AddEscaped in .for loops o job.c: extract ShellWriter_ErrOn from JobPrintCommand make Job_Touch simpler refactor JobFinish rename Shell.exitFlag to errFlag move Job.xtraced to ShellWriter make printing of shell commands independent from the job rename shell flags in struct Shell extract JobOpenTmpFile from JobStart rename RunFlags to CommandFlags split various Job.* into separate fields rename commandShell to shell extract InitShellNameAndPath from Shell_Init replace signal handling macros with local functions replace macro MESSAGE with local function parse.c: error out on null bytes in makefiles error out on misspelled directives rename IFile.nextbuf to readMore fix undefined behavior in ParseEOF str.c: remove redundant call to strlen in Str_Words var.c: error out on misspelled .unexport-env error out on misspelled .export directives extract ExportVars from Var_Export extract ExportVarsExpand from Var_Export eliminate boolean argument of Var_Export fix undefined behavior when exporting ${:U } rename Var_ExportVars to Var_ReexportVars rename Var_Export1 to ExportVar 2020-12-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201206 Merge with NetBSD make, pick up o more unit tests o inline macros for debug logging o use consistent variable names for list nodes o define constants for enum zero-values o dir.c: use fixed format for debug output of the directory cache remove Dir_InitDir o lst.c: inline Lst_Enqueue, Vector_Done o meta.c: remove unused parameter from meta_needed o parse.c: rename parse functions o suff.c: extract ExpandChildrenRegular from ExpandChildren o targ.c: don't concatenate identifiers in Targ_PrintType o var.c: remove comment decoration extract UnexportVars from Var_UnExport extract GetVarnamesToUnexport from Var_UnExport extract UnexportEnv from Var_UnExport extract UnexportVar from Var_UnExport move CleanEnv to UnexportVars replace pointer comparisons with enum add FStr to var.c to make memory handling simpler use FStr in Var_UnExport move type definitions in var.c to the top extract FreeEnvVar from Var_Parse extract ShuffleStrings from ApplyModifier_Order 2020-11-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201130 Merge with NetBSD make, pick up o add unit tests for META MODE o reduce memory allocation for dirSearchPath, GNode.parents, GNode.children, OpenDirs o reduce pointer indirection for GNode.cohorts and GNode.implicitParents o remove pointer indirection from GNode.commands o inline Lst_ForEachUntil in meta mode o dir.c: fix memory leak for lstat cache in -DCLEANUP mode clean up memory management for CachedDirs fix the reference count of dotLast going negative add debug logging for OpenDirs_Done extract CacheNewDir from Dir_AddDir add debug logging for reference counting of CachedDir rename some Dir functions to SearchPath o job.c: rename some global variables o main.c: reduce memory allocation in ReadBuiltinRules reduce memory allocation in CmdOpts.create, CmdOpts.variables, CmdOpts.makefiles Add .MAKE.UID and .MAKE.GID o make.c: reduce memory allocation for/in toBeMade, Make_ProcessWait, Make_ExpandUse o meta.c: reduce memory allocation in meta_oodate o parse.c: reduce memory allocations for parsing dependencies and targets o suff.c: reduce memory allocation in suffix handling 2020-11-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201124 Merge with NetBSD make, pick up o .MAKE.{UID,GID} represent uid and gid running make. o fix error handling for .BEGIN and .END dependency in -k mode o fix missing "Stop." after failed .END node in -k mode o use properly typed comparisons in boolean contexts o replace a few HashTable_CreateEntry with HashTable_Set o add HashSet type o compat.c: split Compat_Make into smaller functions extract DebugFailedTarget from Compat_RunCommand o dir.c: refactor Dir_UpdateMTime migrate CachedDir.files from HashTable to HashSet o make.c: add high-level API for GNode.made 2020-11-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201122 Merge with NetBSD make, pick up o rename GNode.context to vars o suff.c: cleanup and refactor rename some functions and vars to better reflect usage add high-level API for CandidateSearcher o targ.c: add more debug logging for suffix handling o more unit tests o add debug logging for setting and resetting the main target 2020-11-17 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201117 Merge with NetBSD make, pick up o fix some unit-tests when .SHELL is dash 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 diff --git a/contrib/bmake/FILES b/contrib/bmake/FILES index 5e7b8301da87..dc0f6f33c763 100644 --- a/contrib/bmake/FILES +++ b/contrib/bmake/FILES @@ -1,836 +1,849 @@ 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 +str.h 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-jobs.exp unit-tests/cmd-errors-jobs.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-redirect-stdin.exp unit-tests/cmdline-redirect-stdin.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/compat-error.exp unit-tests/compat-error.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-eof.exp unit-tests/cond-eof.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-main.exp unit-tests/cond-func-make-main.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-fail-indirect.exp unit-tests/deptgt-begin-fail-indirect.mk unit-tests/deptgt-begin-fail.exp unit-tests/deptgt-begin-fail.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-fail-all.exp unit-tests/deptgt-end-fail-all.mk unit-tests/deptgt-end-fail-indirect.exp unit-tests/deptgt-end-fail-indirect.mk unit-tests/deptgt-end-fail.exp unit-tests/deptgt-end-fail.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-endfor.exp unit-tests/directive-endfor.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-impl.exp unit-tests/directive-export-impl.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-errors.exp unit-tests/directive-for-errors.mk unit-tests/directive-for-escape.exp unit-tests/directive-for-escape.mk unit-tests/directive-for-generating-endif.exp unit-tests/directive-for-generating-endif.mk unit-tests/directive-for-lines.exp unit-tests/directive-for-lines.mk unit-tests/directive-for-null.exp unit-tests/directive-for-null.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-misspellings.exp unit-tests/directive-misspellings.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/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/job-output-null.exp +unit-tests/job-output-null.mk +unit-tests/jobs-empty-commands-error.exp +unit-tests/jobs-empty-commands-error.mk unit-tests/jobs-empty-commands.exp unit-tests/jobs-empty-commands.mk unit-tests/jobs-error-indirect.exp unit-tests/jobs-error-indirect.mk unit-tests/jobs-error-nested-make.exp unit-tests/jobs-error-nested-make.mk unit-tests/jobs-error-nested.exp unit-tests/jobs-error-nested.mk unit-tests/lint.exp unit-tests/lint.mk unit-tests/make-exported.exp unit-tests/make-exported.mk unit-tests/meta-cmd-cmp.exp unit-tests/meta-cmd-cmp.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-jobs.exp +unit-tests/opt-debug-errors-jobs.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-no-action.exp unit-tests/opt-jobs-no-action.mk unit-tests/opt-jobs.exp unit-tests/opt-jobs.mk unit-tests/opt-keep-going-multiple.exp unit-tests/opt-keep-going-multiple.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-runflags.exp unit-tests/opt-no-action-runflags.mk unit-tests/opt-no-action-touch.exp unit-tests/opt-no-action-touch.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/recursive.exp unit-tests/recursive.mk unit-tests/sh-dots.exp unit-tests/sh-dots.mk unit-tests/sh-errctl.exp unit-tests/sh-errctl.mk unit-tests/sh-flags.exp unit-tests/sh-flags.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-incomplete.exp unit-tests/suff-incomplete.mk unit-tests/suff-lookup.exp unit-tests/suff-lookup.mk unit-tests/suff-main-several.exp unit-tests/suff-main-several.mk unit-tests/suff-main.exp unit-tests/suff-main.mk unit-tests/suff-phony.exp unit-tests/suff-phony.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-debug.exp unit-tests/suff-transform-debug.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-eval-short.exp +unit-tests/var-eval-short.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-indirect.exp unit-tests/varmod-indirect.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-varname.exp +unit-tests/varmod-loop-varname.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-sun-shell.exp +unit-tests/varmod-sun-shell.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-makeflags.exp unit-tests/varname-dot-makeflags.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 util.c var.c wait.h diff --git a/contrib/bmake/VERSION b/contrib/bmake/VERSION index 0af794962680..7c28f11013b7 100644 --- a/contrib/bmake/VERSION +++ b/contrib/bmake/VERSION @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20210206 +_MAKE_VERSION=20210621 diff --git a/contrib/bmake/arch.c b/contrib/bmake/arch.c index e5c0a5e4ac0f..6d9dd60dfbe9 100644 --- a/contrib/bmake/arch.c +++ b/contrib/bmake/arch.c @@ -1,1140 +1,1148 @@ -/* $NetBSD: arch.c,v 1.197 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: arch.c,v 1.200 2021/05/30 21:16:54 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. */ /* * Manipulate libraries, archives and their members. * * 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 * 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 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_UpdateMTime * Find the modification time of a member of * an archive *in the archive* and place it in the * member's GNode. * * 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 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.197 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: arch.c,v 1.200 2021/05/30 21:16:54 rillig Exp $"); 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 +/* Return "archive(member)". */ +static char * +FullName(const char *archive, const char *member) +{ + size_t len1 = strlen(archive); + size_t len3 = strlen(member); + char *result = bmake_malloc(len1 + 1 + len3 + 1 + 1); + memcpy(result, archive, len1); + memcpy(result + len1, "(", 1); + memcpy(result + len1 + 1, member, len3); + memcpy(result + len1 + 1 + len3, ")", 1 + 1); + return result; +} /* * Parse an archive specification such as "archive.a(member1 member2.${EXT})", * adding nodes for the expanded members to gns. Nodes are created as * necessary. * * Input: * pp The start of the specification. * gns The list on which to place the nodes. * scope The scope in which to expand variables. * * Output: - * return TRUE if it was a valid specification. + * return True if it was a valid specification. * *pp Points to the first non-space after the archive spec. */ -Boolean +bool Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) { char *cp; /* Pointer into line */ GNode *gn; /* New node */ MFStr libName; /* Library-part of specification */ char *memName; /* Member-part of specification */ char saveChar; /* Ending delimiter of member-name */ - Boolean expandLibName; /* Whether the parsed libName contains + bool expandLibName; /* Whether the parsed libName contains * variable expressions that need to be * expanded */ libName = MFStr_InitRefer(*pp); - expandLibName = FALSE; + expandLibName = false; for (cp = libName.str; *cp != '(' && *cp != '\0';) { if (*cp == '$') { /* Expand nested variable expressions. */ /* XXX: This code can probably be shortened. */ const char *nested_p = cp; FStr result; - Boolean isError; + bool isError; /* XXX: is expanded twice: once here and once below */ (void)Var_Parse(&nested_p, scope, - VARE_WANTRES | VARE_UNDEFERR, &result); + VARE_UNDEFERR, &result); /* TODO: handle errors */ isError = result.str == var_Error; FStr_Done(&result); if (isError) - return FALSE; + return false; - expandLibName = TRUE; + expandLibName = true; cp += nested_p - cp; } else cp++; } *cp++ = '\0'; if (expandLibName) { char *expanded; - (void)Var_Subst(libName.str, scope, - VARE_WANTRES | VARE_UNDEFERR, &expanded); + (void)Var_Subst(libName.str, scope, VARE_UNDEFERR, &expanded); /* TODO: handle errors */ libName = MFStr_InitOwn(expanded); } 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; + bool doSubst = false; pp_skip_whitespace(&cp); memName = cp; while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) { if (*cp == '$') { /* Expand nested variable expressions. */ /* XXX: This code can probably be shortened. */ FStr result; - Boolean isError; + bool isError; const char *nested_p = cp; (void)Var_Parse(&nested_p, scope, - VARE_WANTRES | VARE_UNDEFERR, - &result); + VARE_UNDEFERR, &result); /* TODO: handle errors */ isError = result.str == var_Error; FStr_Done(&result); if (isError) - return FALSE; + return false; - doSubst = TRUE; + 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') { Parse_Error(PARSE_FATAL, "No closing parenthesis " "in archive specification"); - return FALSE; + 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 *fullName; char *p; char *unexpandedMemName = memName; - (void)Var_Subst(memName, scope, - VARE_WANTRES | VARE_UNDEFERR, - &memName); + (void)Var_Subst(memName, scope, VARE_UNDEFERR, + &memName); /* TODO: handle errors */ /* * Now form an archive spec and recurse to deal with * nested variables and multi-word variable values. */ - fullName = str_concat4(libName.str, "(", memName, ")"); + fullName = FullName(libName.str, memName); p = fullName; if (strchr(memName, '$') != NULL && strcmp(memName, unexpandedMemName) == 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(fullName); gn->type |= OP_ARCHV; Lst_Append(gns, gn); } else if (!Arch_ParseArchive(&p, gns, scope)) { /* Error in nested call. */ free(fullName); /* XXX: does unexpandedMemName leak? */ - return FALSE; + return false; } free(fullName); /* XXX: does unexpandedMemName leak? */ } else if (Dir_HasWildcards(memName)) { StringList members = LST_INIT; SearchPath_Expand(&dirSearchPath, memName, &members); while (!Lst_IsEmpty(&members)) { char *member = Lst_Dequeue(&members); - char *fullname = str_concat4(libName.str, "(", - member, ")"); + char *fullname = FullName(libName.str, member); free(member); gn = Targ_GetNode(fullname); free(fullname); gn->type |= OP_ARCHV; Lst_Append(gns, gn); } Lst_Done(&members); } else { - char *fullname = str_concat4(libName.str, "(", memName, - ")"); + char *fullname = FullName(libName.str, 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(gns, gn); } if (doSubst) free(memName); *cp = saveChar; } MFStr_Done(&libName); cp++; /* skip the ')' */ /* We promised that pp would be set up at the next non-space. */ pp_skip_whitespace(&cp); *pp = cp; - return TRUE; + 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. - * addToCache TRUE if archive should be cached if not already so. + * addToCache True if archive should be cached if not already so. * * Results: * 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 addToCache) +ArchStatMember(const char *archive, const char *member, bool addToCache) { #define AR_MAX_NAME_LEN (sizeof arh.ar_name - 1) FILE *arch; 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. */ member = str_basename(member); for (ln = archives.first; ln != NULL; ln = ln->next) { 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) { snprintf(copy, sizeof copy, "%s", member); hdr = HashTable_FindValue(&ar->members, copy); } return hdr; } } if (!addToCache) { /* * 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 * 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) { (void)fclose(arch); return NULL; } 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(&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; /* * 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 > memName && *nameend == ' ') nameend--; nameend[1] = '\0'; #ifdef SVR4ARCHIVES /* * svr4 names are slash-terminated. * Also svr4 extended the AR format. */ if (memName[0] == '/') { /* svr4 magic mode; handle it */ switch (ArchSVR4Entry(ar, memName, size, arch)) { case -1: /* Invalid data */ goto badarch; case 0: /* List of files entry */ continue; default: /* Got the entry */ break; } } else { if (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])) { 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( "ArchStatMember: " "Extended format entry for %s\n", memName); } #endif { 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 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 /* * 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 *inout_name, size_t size, FILE *arch) { #define ARLONGNAMES1 "//" #define ARLONGNAMES2 "/ARFILENAMES" size_t entry; char *ptr, *eptr; 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 (inout_name[1] == ' ' || inout_name[1] == '\0') return 2; 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", inout_name, (unsigned long)ar->fnamesize); return 2; } DEBUG2(ARCH, "Replaced %s with %s\n", inout_name, &ar->fnametab[entry]); snprintf(inout_name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]); return 1; } #endif -static Boolean +static bool 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; + 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; + 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 true; - return FALSE; + 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. * out_arh Archive header to be filled in * mode "r" for read-only access, "r+" for read-write access * * 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 *out_arh, const char *mode) { FILE *arch; /* Stream to archive */ int size; /* Size of archive member */ char magic[SARMAG]; size_t len; arch = fopen(archive, mode); if (arch == NULL) return NULL; /* * We use the ARMAG string to make sure this is an archive we * can handle... */ if (fread(magic, SARMAG, 1, arch) != 1 || strncmp(magic, ARMAG, SARMAG) != 0) { fclose(arch); return NULL; } /* * Because of space constraints and similar things, files are archived * using their basename, not the entire path. */ member = str_basename(member); len = strlen(member); while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) { 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; } 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 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. */ 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(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 && (ch_isdigit(out_arh->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( "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 /* * 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. */ 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; } } fclose(arch); return NULL; } /* * 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 *f; struct ar_hdr arh; f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+"); if (f == NULL) return; 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 is also touched. * * Both the modification time of the library and of the RANLIBMAG member are * set to 'now'. */ /*ARGSUSED*/ void Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED) { #ifdef RANLIBMAG FILE *f; struct ar_hdr arh; /* Header describing table of contents */ struct utimbuf times; f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); if (f == NULL) return; 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, ×); /* TODO: handle errors */ #endif } /* * 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 *arh; - arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE); + 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 nonexistent 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) { 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; } } } /* * 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(gn, TARGET, gn->name); #else Var_Set(gn, TARGET, GNode_Path(gn)); #endif } /* * Decide if a node with the OP_LIB attribute is out-of-date. Called from * 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 * 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 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. */ -Boolean +bool Arch_LibOODate(GNode *gn) { - Boolean oodate; + bool oodate; if (gn->type & OP_PHONY) { - oodate = TRUE; + oodate = true; } else if (!GNode_IsTarget(gn) && Lst_IsEmpty(&gn->children)) { - oodate = FALSE; + oodate = false; } else if ((!Lst_IsEmpty(&gn->children) && gn->youngestChild == NULL) || (gn->mtime > now) || (gn->youngestChild != NULL && gn->mtime < gn->youngestChild->mtime)) { - oodate = TRUE; + oodate = true; } else { #ifdef RANLIBMAG struct ar_hdr *arh; /* Header for __.SYMDEF */ int modTimeTOC; /* The table-of-contents' mod time */ - arh = ArchStatMember(gn->path, RANLIBMAG, FALSE); + arh = ArchStatMember(gn->path, RANLIBMAG, false); 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; } else { /* * A library without a table of contents is out-of-date. */ if (DEBUG(ARCH) || DEBUG(MAKE)) debug_printf("no toc..."); - oodate = TRUE; + oodate = true; } #else - oodate = FALSE; + oodate = false; #endif } return oodate; } /* Initialize the archives module. */ void Arch_Init(void) { Lst_Init(&archives); } /* Clean up the archives module. */ void Arch_End(void) { #ifdef CLEANUP Lst_DoneCall(&archives, ArchFree); #endif } -Boolean +bool 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; + return false; if (read(fd, buf, sizeof buf) != sizeof buf) { (void)close(fd); - return FALSE; + return false; } (void)close(fd); return memcmp(buf, armag, sizeof buf) == 0; } diff --git a/contrib/bmake/buf.h b/contrib/bmake/buf.h index 594e9651dbfb..938820e4745f 100644 --- a/contrib/bmake/buf.h +++ b/contrib/bmake/buf.h @@ -1,121 +1,121 @@ -/* $NetBSD: buf.h,v 1.42 2021/01/30 21:25:10 rillig Exp $ */ +/* $NetBSD: buf.h,v 1.43 2021/04/03 11:08:40 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 '\0' */ size_t len; /* Number of bytes in buffer, excluding the '\0' */ char *data; /* The buffer itself (always null-terminated) */ } Buffer; void Buf_Expand(Buffer *); /* Buf_AddByte adds a single byte to a buffer. */ MAKE_INLINE void Buf_AddByte(Buffer *buf, char byte) { size_t old_len = buf->len++; char *end; if (old_len + 1 >= buf->cap) Buf_Expand(buf); end = buf->data + old_len; end[0] = byte; end[1] = '\0'; } -MAKE_INLINE Boolean +MAKE_INLINE bool 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); void Buf_Empty(Buffer *); void Buf_Init(Buffer *); void Buf_InitSize(Buffer *, size_t); void Buf_Done(Buffer *); char *Buf_DoneData(Buffer *); char *Buf_DoneDataCompact(Buffer *); #endif /* MAKE_BUF_H */ diff --git a/contrib/bmake/compat.c b/contrib/bmake/compat.c index 59190d8c4354..f8c47397f3df 100644 --- a/contrib/bmake/compat.c +++ b/contrib/bmake/compat.c @@ -1,762 +1,762 @@ -/* $NetBSD: compat.c,v 1.224 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: compat.c,v 1.227 2021/04/27 15:19:25 christos 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.224 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: compat.c,v 1.227 2021/04/27 15:19:25 christos Exp $"); static GNode *curTarg = NULL; static pid_t compatChild; static int compatSigno; /* * 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) { CompatDeleteTarget(curTarg); if (curTarg != NULL && !Targ_Precious(curTarg)) { /* * Run .INTERRUPT only if hit with interrupt signal */ if (signo == SIGINT) { 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); } } static void -DebugFailedTarget(const char *cmd, GNode *gn) +DebugFailedTarget(const char *cmd, const GNode *gn) { const char *p = cmd; debug_printf("\n*** Failed target: %s\n*** Failed command: ", gn->name); /* 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(" "); cpp_skip_whitespace(&p); } else { debug_printf("%c", *p); p++; } } debug_printf("\n"); } -static Boolean +static bool UseShell(const char *cmd MAKE_ATTR_UNUSED) { #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. */ - return TRUE; + return 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). */ return needshell(cmd); #endif } /* * 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 * gn Node from which the command came * ln List node that contains the command * * Results: * 0 if the command succeeded, 1 if an error occurred. */ int Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) { 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 */ + bool silent; /* Don't print command */ + bool doIt; /* Execute even if -n */ + volatile bool errCheck; /* Check errors */ WAIT_T reason; /* Reason for child's death */ WAIT_T status; /* Description of child's death */ pid_t cpid; /* Child actually found */ pid_t retstat; /* Result of wait */ 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 + bool 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; + doIt = false; (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart); /* TODO: handle errors */ if (cmdStart[0] == '\0') { free(cmdStart); return 0; } cmd = cmdStart; LstNode_Set(ln, cmdStart); if (gn->type & OP_SAVE_CMDS) { GNode *endNode = Targ_GetEndNode(); if (gn != endNode) { /* * Append the expanded command, to prevent the * local variables from being interpreted in the * scope of the .END node. * * A probably unintended side effect of this is that * the expanded command will be expanded again in the * .END node. Therefore, a literal '$' in these * commands must be written as '$$$$' instead of the * usual '$$'. */ Lst_Append(&endNode->commands, cmdStart); return 0; } } if (strcmp(cmdStart, "...") == 0) { gn->type |= OP_SAVE_CMDS; return 0; } for (;;) { if (*cmd == '@') silent = !DEBUG(LOUD); else if (*cmd == '-') - errCheck = FALSE; + errCheck = false; else if (*cmd == '+') { - doIt = TRUE; + doIt = true; if (shellName == NULL) /* 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[0] == '\0') return 0; useShell = UseShell(cmd); /* * 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)) 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]; /* The following work for any of the builtin shell specs. */ int shargc = 0; shargv[shargc++] = shellPath; if (errCheck && shellErrFlag != NULL) shargv[shargc++] = shellErrFlag; 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); + Words words = Str_Words(cmd, false); mav = words.words; bp = words.freeIt; av = (void *)mav; } #ifdef USE_META if (useMeta) { meta_compat_start(); } #endif Var_ReexportVars(); /* * 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) { #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(ln); #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? */ + 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)) DebugFailedTarget(cmd, gn); 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, FALSE, status); + meta_job_error(NULL, gn, false, 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 != 0) { 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, ln) != 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); } } -static Boolean +static bool MakeUnmade(GNode *gn, GNode *pgn) { assert(gn->made == UNMADE); /* * 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 to FALSE + * 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; - return FALSE; + return false; } if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) Var_Set(pgn, IMPSRC, GNode_VarTarget(gn)); /* * 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 GNode_IsOODate. */ DEBUG1(MAKE, "Examining %s...", gn->name); if (!GNode_IsOODate(gn)) { gn->made = UPTODATE; DEBUG0(MAKE, "up-to-date.\n"); - return FALSE; + return false; } /* * If the user is just seeing if something is out-of-date, exit now * to tell him/her "yes". */ DEBUG0(MAKE, "out-of-date.\n"); if (opts.queryFlag) exit(1); /* * We need to be re-made. * Ensure that $? (.OODATE) and $> (.ALLSRC) are both set. */ - Make_DoAllVar(gn); + GNode_SetLocalVars(gn); /* * Alter our type to tell if errors should be ignored or things * should not be printed so Compat_RunCommand knows what to do. */ if (opts.ignoreErrors) gn->type |= OP_IGNORE; if (opts.beSilent) 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. * This is to keep its state from affecting that of its parent. */ gn->made = MADE; if (Make_Recheck(gn) == 0) pgn->flags |= FORCE; if (!(gn->type & OP_EXEC)) { pgn->flags |= CHILDMADE; GNode_UpdateYoungestChild(pgn, gn); } } else if (opts.keepgoing) { pgn->flags &= ~(unsigned)REMAKE; } else { PrintOnError(gn, "\nStop."); exit(1); } - return TRUE; + return true; } static void MakeOther(GNode *gn, GNode *pgn) { if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) { const char *target = GNode_VarTarget(gn); Var_Set(pgn, IMPSRC, target != NULL ? target : ""); } 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)) { pgn->flags |= CHILDMADE; GNode_UpdateYoungestChild(pgn, gn); } break; case UPTODATE: if (!(gn->type & OP_EXEC)) GNode_UpdateYoungestChild(pgn, gn); break; default: break; } } /* * 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 * * Output: * gn->made * UPTODATE gn was already up-to-date. * MADE gn was recreated successfully. * ERROR An error occurred while gn was being created, * either due to missing commands or in -k mode. * ABORTED gn was not remade because one of its * dependencies could not be made due to errors. */ void Compat_Make(GNode *gn, GNode *pgn) { if (shellName == NULL) /* we came here from jobs */ Shell_Init(); if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) { if (!MakeUnmade(gn, pgn)) goto cohorts; /* XXX: Replace with GNode_IsError(gn) */ } else if (gn->made == ERROR) { /* * Already had an error when making this. * Tell the parent to abort. */ pgn->flags &= ~(unsigned)REMAKE; } else { MakeOther(gn, pgn); } cohorts: MakeNodes(&gn->cohorts, pgn); } static void MakeBeginNode(void) { GNode *gn = Targ_FindNode(".BEGIN"); if (gn == NULL) return; Compat_Make(gn, gn); if (GNode_IsError(gn)) { PrintOnError(gn, "\nStop."); exit(1); } } static void InitSignals(void) { 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); } /* * Initialize this module and start making. * * Input: * targs The target nodes to re-create */ void Compat_Run(GNodeList *targs) { GNode *errorNode = NULL; if (shellName == NULL) Shell_Init(); InitSignals(); /* 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 (!opts.queryFlag) MakeBeginNode(); /* * Expand .USE nodes right now, because they can modify the structure * of the tree. */ Make_ExpandUse(targs); while (!Lst_IsEmpty(targs)) { GNode *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); } if (GNode_IsError(gn) && errorNode == NULL) errorNode = gn; } /* If the user has defined a .END target, run its commands. */ if (errorNode == NULL) { GNode *endNode = Targ_GetEndNode(); Compat_Make(endNode, endNode); if (GNode_IsError(endNode)) errorNode = endNode; } if (errorNode != NULL) { if (DEBUG(GRAPH2)) Targ_PrintGraph(2); else if (DEBUG(GRAPH3)) Targ_PrintGraph(3); PrintOnError(errorNode, "\nStop."); exit(1); } } diff --git a/contrib/bmake/cond.c b/contrib/bmake/cond.c index 8f36fda22f12..a8d88d1d6816 100644 --- a/contrib/bmake/cond.c +++ b/contrib/bmake/cond.c @@ -1,1363 +1,1368 @@ -/* $NetBSD: cond.c,v 1.256 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.267 2021/06/11 14:52: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. */ /* * Handling of conditionals in a makefile. * * Interface: * 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.256 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.267 2021/06/11 14:52:03 rillig Exp $"); /* * The parsing of conditional expressions is based on this grammar: * Or -> And '||' Or * Or -> And * And -> Term '&&' And * And -> Term * Term -> Function '(' Argument ')' * Term -> Leaf Operator Leaf * Term -> Leaf * Term -> '(' Or ')' * Term -> '!' Term * Leaf -> "string" * Leaf -> Number * Leaf -> VariableExpression * Leaf -> Symbol * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' * * 'Symbol' is an unquoted string literal to which the default function is * applied. * * The tokens are scanned by CondToken, which returns: * TOK_AND for '&&' * TOK_OR for '||' * 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. */ typedef enum Token { TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT, TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR } Token; typedef enum CondResult { CR_FALSE, CR_TRUE, CR_ERROR } CondResult; typedef enum ComparisonOp { LT, LE, GT, GE, EQ, NE } ComparisonOp; typedef struct CondParser { /* * The plain '.if ${VAR}' evaluates to true if the value of the * expression has length > 0. The other '.if' variants delegate * to evalBare instead. */ - Boolean plain; + bool plain; /* The function to apply on unquoted bare words. */ - Boolean (*evalBare)(size_t, const char *); - Boolean negateEvalBare; + bool (*evalBare)(size_t, const char *); + bool negateEvalBare; 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; + bool printedError; } CondParser; -static CondResult CondParser_Or(CondParser *par, Boolean); +static CondResult CondParser_Or(CondParser *par, bool); static unsigned int cond_depth = 0; /* current .if nesting level */ static unsigned int cond_min_depth = 0; /* depth at makefile open */ static const char *opname[] = { "<", "<=", ">", ">=", "==", "!=" }; /* * 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 + * True when CondEvalExpression is called from Cond_EvalLine (.if etc). + * False when CondEvalExpression is called from ApplyModifier_IfElse * since lhs is already expanded, and at that point we cannot tell if * it was a variable reference or not. */ -static Boolean lhsStrict; +static bool lhsStrict; -static Boolean +static bool 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) +ToToken(bool 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, or 0 on error. */ static size_t -ParseFuncArg(CondParser *par, const char **pp, Boolean doEval, const char *func, +ParseFuncArg(CondParser *par, const char **pp, bool 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') { *out_arg = NULL; /* Missing closing parenthesis: */ return 0; /* .if defined( */ } cpp_skip_hspace(&p); Buf_InitSize(&argBuf, 16); paren_depth = 0; for (;;) { char ch = *p; if (ch == '\0' || ch == ' ' || ch == '\t') break; if ((ch == '&' || ch == '|') && paren_depth == 0) break; if (*p == '$') { /* * Parse the variable expression and install it as * part of the argument if it's valid. We tell * Var_Parse to complain on an undefined variable, * (XXX: but Var_Parse ignores that request) * so we don't need to do it. Nor do we return an * error, though perhaps we should. */ - VarEvalFlags eflags = doEval - ? VARE_WANTRES | VARE_UNDEFERR - : VARE_NONE; + VarEvalMode emode = doEval + ? VARE_UNDEFERR + : VARE_PARSE_ONLY; FStr nestedVal; - (void)Var_Parse(&p, SCOPE_CMDLINE, eflags, &nestedVal); + (void)Var_Parse(&p, SCOPE_CMDLINE, emode, &nestedVal); /* TODO: handle errors */ Buf_AddStr(&argBuf, nestedVal.str); FStr_Done(&nestedVal); continue; } if (ch == '(') paren_depth++; else if (ch == ')' && --paren_depth < 0) break; Buf_AddByte(&argBuf, *p); p++; } argLen = argBuf.len; *out_arg = Buf_DoneData(&argBuf); cpp_skip_hspace(&p); if (func != NULL && *p++ != ')') { Parse_Error(PARSE_FATAL, "Missing closing parenthesis for %s()", func); - par->printedError = TRUE; + par->printedError = true; return 0; } *pp = p; return argLen; } /* Test whether the given variable is defined. */ /*ARGSUSED*/ -static Boolean +static bool FuncDefined(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { FStr value = Var_Value(SCOPE_CMDLINE, arg); - Boolean result = value.str != NULL; + bool result = value.str != NULL; FStr_Done(&value); return result; } /* See if the given target is being made. */ /*ARGSUSED*/ -static Boolean +static bool 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; + return true; + return false; } /* See if the given file exists. */ /*ARGSUSED*/ -static Boolean +static bool FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { - Boolean result; + bool result; char *path; path = Dir_FindFile(arg, &dirSearchPath); 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. */ /*ARGSUSED*/ -static Boolean +static bool 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. */ /*ARGSUSED*/ -static Boolean +static bool 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: - * Returns TRUE if the conversion succeeded. + * Returns true if the conversion succeeded. * Sets 'out_value' to the converted number. */ -static Boolean +static bool TryParseNumber(const char *str, double *out_value) { char *end; unsigned long ul_val; double dbl_val; errno = 0; if (str[0] == '\0') { /* XXX: why is an empty string a number? */ *out_value = 0.0; - return TRUE; + return true; } 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; + return true; } if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') - return FALSE; /* skip the expensive strtod call */ + return false; /* skip the expensive strtod call */ dbl_val = strtod(str, &end); if (*end != '\0') - return FALSE; + return false; *out_value = dbl_val; - return TRUE; + return true; } -static Boolean +static bool is_separator(char ch) { - return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL; + return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' || + ch == '>' || ch == '<' || ch == ')' /* but not '(' */; } /* * In a quoted or unquoted string literal or a number, parse a variable * expression. * * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} */ -static Boolean +static bool CondParser_StringExpr(CondParser *par, const char *start, - Boolean const doEval, Boolean const quoted, + bool const doEval, bool const quoted, Buffer *buf, FStr *const inout_str) { - VarEvalFlags eflags; + VarEvalMode emode; const char *nested_p; - Boolean atStart; + bool atStart; VarParseResult parseResult; /* if we are in quotes, an undefined variable is ok */ - eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR + emode = doEval && !quoted ? VARE_UNDEFERR : doEval ? VARE_WANTRES - : VARE_NONE; + : VARE_PARSE_ONLY; nested_p = par->p; atStart = nested_p == start; - parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, eflags, inout_str); + parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, emode, inout_str); /* TODO: handle errors */ if (inout_str->str == var_Error) { if (parseResult == VPR_ERR) { /* * FIXME: Even if an error occurs, there is no * guarantee that it is reported. * * See cond-token-plain.mk $$$$$$$$. */ - par->printedError = TRUE; + par->printedError = true; } /* * XXX: Can there be any situation in which a returned - * var_Error requires freeIt? + * var_Error needs to be freed? */ FStr_Done(inout_str); /* * Even if !doEval, we still report syntax errors, which is * what getting var_Error back with !doEval means. */ *inout_str = FStr_InitRefer(NULL); - return FALSE; + return false; } 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])) - return FALSE; + return false; Buf_AddStr(buf, inout_str->str); FStr_Done(inout_str); *inout_str = FStr_InitRefer(NULL); /* not finished yet */ - return TRUE; + return true; } /* - * Parse a string from a variable reference or an optionally quoted - * string. This is called for the lhs and rhs of string comparisons. + * Parse a string from a variable expression or an optionally quoted + * string. This is called for the left-hand and right-hand sides of + * comparisons. * * Results: * Returns the string, absent any quotes, or NULL on error. - * Sets out_quoted if the string was quoted. - * Sets out_freeIt. + * Sets out_quoted if the leaf was a quoted string literal. */ static void -CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, - FStr *out_str, Boolean *out_quoted) +CondParser_Leaf(CondParser *par, bool doEval, bool strictLHS, + FStr *out_str, bool *out_quoted) { Buffer buf; FStr str; - Boolean quoted; + bool quoted; const char *start; Buf_Init(&buf); str = FStr_InitRefer(NULL); *out_quoted = quoted = par->p[0] == '"'; start = par->p; if (quoted) par->p++; while (par->p[0] != '\0' && str.str == NULL) { switch (par->p[0]) { case '\\': par->p++; if (par->p[0] != '\0') { Buf_AddByte(&buf, par->p[0]); par->p++; } continue; case '"': par->p++; if (quoted) goto got_str; /* skip the closing quote */ Buf_AddByte(&buf, '"'); continue; case ')': /* see is_separator */ case '!': case '=': case '>': case '<': case ' ': case '\t': if (!quoted) goto got_str; Buf_AddByte(&buf, par->p[0]); par->p++; continue; case '$': if (!CondParser_StringExpr(par, start, doEval, quoted, &buf, &str)) goto cleanup; continue; default: if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) { /* * The left-hand side must be quoted, * a variable reference or a number. */ str = FStr_InitRefer(NULL); goto cleanup; } Buf_AddByte(&buf, par->p[0]); par->p++; continue; } } got_str: str = FStr_InitOwn(buf.data); cleanup: - Buf_DoneData(&buf); + Buf_DoneData(&buf); /* XXX: memory leak on failure? */ *out_str = str; } -static Boolean -If_Eval(const CondParser *par, const char *arg, size_t arglen) +static bool +EvalBare(const CondParser *par, const char *arg, size_t arglen) { - Boolean res = par->evalBare(arglen, arg); + bool res = par->evalBare(arglen, arg); return par->negateEvalBare ? !res : res; } /* * Evaluate a "comparison without operator", such as in ".if ${VAR}" or * ".if 0". */ -static Boolean -EvalNotEmpty(CondParser *par, const char *value, Boolean quoted) +static bool +EvalNotEmpty(CondParser *par, const char *value, bool quoted) { double num; /* For .ifxxx "...", check for non-empty string. */ if (quoted) return value[0] != '\0'; /* For .ifxxx , compare against zero */ if (TryParseNumber(value, &num)) return num != 0.0; /* 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->plain) return value[0] != '\0'; - return If_Eval(par, value, strlen(value)); + return EvalBare(par, value, strlen(value)); } /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ -static Boolean +static bool EvalCompareNum(double lhs, ComparisonOp op, double rhs) { DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, opname[op]); switch (op) { case LT: return lhs < rhs; case LE: return lhs <= rhs; case GT: return lhs > rhs; case GE: return lhs >= rhs; case NE: return lhs != rhs; default: return lhs == rhs; } } static Token EvalCompareStr(CondParser *par, const char *lhs, ComparisonOp op, const char *rhs) { if (op != EQ && op != NE) { Parse_Error(PARSE_FATAL, "String comparison operator must be either == or !="); - par->printedError = TRUE; + par->printedError = true; return TOK_ERROR; } DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, opname[op]); return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); } /* Evaluate a comparison, such as "${VAR} == 12345". */ static Token -EvalCompare(CondParser *par, const char *lhs, Boolean lhsQuoted, - ComparisonOp op, const char *rhs, Boolean rhsQuoted) +EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, + ComparisonOp op, const char *rhs, bool rhsQuoted) { double left, right; if (!rhsQuoted && !lhsQuoted) if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) return ToToken(EvalCompareNum(left, op, right)); return EvalCompareStr(par, lhs, op, rhs); } -static Boolean +static bool CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) { const char *p = par->p; if (p[0] == '<' && p[1] == '=') { *out_op = LE; goto length_2; } else if (p[0] == '<') { *out_op = LT; goto length_1; } else if (p[0] == '>' && p[1] == '=') { *out_op = GE; goto length_2; } else if (p[0] == '>') { *out_op = GT; goto length_1; } else if (p[0] == '=' && p[1] == '=') { *out_op = EQ; goto length_2; } else if (p[0] == '!' && p[1] == '=') { *out_op = NE; goto length_2; } - return FALSE; + return false; length_2: par->p = p + 2; - return TRUE; + return true; length_1: par->p = p + 1; - return TRUE; + return true; } /* * Parse a comparison condition such as: * * 0 * ${VAR:Mpattern} * ${VAR} == value * ${VAR:U0} < 12345 */ static Token -CondParser_Comparison(CondParser *par, Boolean doEval) +CondParser_Comparison(CondParser *par, bool doEval) { Token t = TOK_ERROR; FStr lhs, rhs; ComparisonOp op; - Boolean lhsQuoted, rhsQuoted; + bool lhsQuoted, rhsQuoted; /* * Parse the variable spec and skip over it, saving its * value in lhs. */ - CondParser_String(par, doEval, lhsStrict, &lhs, &lhsQuoted); + CondParser_Leaf(par, doEval, lhsStrict, &lhs, &lhsQuoted); if (lhs.str == NULL) goto done_lhs; CondParser_SkipWhitespace(par); if (!CondParser_ComparisonOp(par, &op)) { /* Unknown operator, compare against an empty string or 0. */ t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted)); goto done_lhs; } CondParser_SkipWhitespace(par); if (par->p[0] == '\0') { Parse_Error(PARSE_FATAL, "Missing right-hand-side of operator '%s'", opname[op]); - par->printedError = TRUE; + par->printedError = true; goto done_lhs; } - CondParser_String(par, doEval, FALSE, &rhs, &rhsQuoted); + CondParser_Leaf(par, doEval, false, &rhs, &rhsQuoted); if (rhs.str == NULL) goto done_rhs; if (!doEval) { t = TOK_FALSE; goto done_rhs; } t = EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); done_rhs: FStr_Done(&rhs); done_lhs: FStr_Done(&lhs); return t; } /* * The argument to empty() is a variable name, optionally followed by * variable modifiers. */ /*ARGSUSED*/ static size_t ParseEmptyArg(CondParser *par MAKE_ATTR_UNUSED, const char **pp, - Boolean doEval, const char *func MAKE_ATTR_UNUSED, + bool doEval, const char *func MAKE_ATTR_UNUSED, char **out_arg) { FStr val; size_t magic_res; /* We do all the work here and return the result as the length */ *out_arg = NULL; (*pp)--; /* Make (*pp)[1] point to the '('. */ - (void)Var_Parse(pp, SCOPE_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE, - &val); + (void)Var_Parse(pp, SCOPE_CMDLINE, + doEval ? VARE_WANTRES : VARE_PARSE_ONLY, &val); /* TODO: handle errors */ /* If successful, *pp points beyond the closing ')' now. */ if (val.str == var_Error) { FStr_Done(&val); return (size_t)-1; } /* * A variable is empty when it just contains spaces... * 4/15/92, christos */ cpp_skip_whitespace(&val.str); /* * For consistency with the other functions we can't generate the * true/false here. */ magic_res = val.str[0] != '\0' ? 2 : 1; FStr_Done(&val); return magic_res; } /*ARGSUSED*/ -static Boolean +static bool FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) { /* Magic values ahead, see ParseEmptyArg. */ return arglen == 1; } -static Boolean -CondParser_Func(CondParser *par, Boolean doEval, Token *out_token) +/* Parse a function call expression, such as 'defined(${file})'. */ +static bool +CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) { static const struct fn_def { const char *fn_name; size_t fn_name_len; - size_t (*fn_parse)(CondParser *, const char **, Boolean, + size_t (*fn_parse)(CondParser *, const char **, bool, const char *, char **); - Boolean (*fn_eval)(size_t, const char *); + bool (*fn_eval)(size_t, const char *); } 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 } }; const struct fn_def *fn; char *arg = NULL; size_t arglen; const char *cp = par->p; const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0]; for (fn = fns; fn != fns_end; fn++) { if (!is_token(cp, fn->fn_name, fn->fn_name_len)) continue; cp += fn->fn_name_len; cpp_skip_whitespace(&cp); if (*cp != '(') break; arglen = fn->fn_parse(par, &cp, doEval, fn->fn_name, &arg); if (arglen == 0 || arglen == (size_t)-1) { par->p = cp; *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR; - return TRUE; + return true; } /* Evaluate the argument using the required function. */ *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg)); free(arg); par->p = cp; - return TRUE; + return true; } - return FALSE; + return false; } /* - * Parse a function call, a number, a variable expression or a string - * literal. + * Parse a comparison such as '${VAR} == "value"', or a simple leaf without + * operator, which is a number, a variable expression or a string literal. */ static Token -CondParser_LeafToken(CondParser *par, Boolean doEval) +CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) { Token t; char *arg = NULL; size_t arglen; const char *cp; 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]) || 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. */ + /* + * XXX: Is it possible to have a variable expression evaluated twice + * at this point? + */ arglen = ParseFuncArg(par, &cp, doEval, NULL, &arg); cp1 = cp; cpp_skip_whitespace(&cp1); - if (*cp1 == '=' || *cp1 == '!') + if (*cp1 == '=' || *cp1 == '!' || *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 = ToToken(!doEval || If_Eval(par, arg, arglen)); + t = ToToken(!doEval || EvalBare(par, arg, arglen)); free(arg); return t; } /* Return the next token or comparison result from the parser. */ static Token -CondParser_Token(CondParser *par, Boolean doEval) +CondParser_Token(CondParser *par, bool doEval) { Token t; t = par->curr; if (t != TOK_NONE) { par->curr = TOK_NONE; return t; } 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] == '|') par->p++; else if (opts.strict) { Parse_Error(PARSE_FATAL, "Unknown operator '|'"); - par->printedError = TRUE; + par->printedError = true; return TOK_ERROR; } return TOK_OR; case '&': par->p++; if (par->p[0] == '&') par->p++; else if (opts.strict) { Parse_Error(PARSE_FATAL, "Unknown operator '&'"); - par->printedError = TRUE; + par->printedError = true; return TOK_ERROR; } return TOK_AND; case '!': par->p++; return TOK_NOT; 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_LeafToken(par, doEval); + if (CondParser_FuncCall(par, doEval, &t)) + return t; + return CondParser_ComparisonOrLeaf(par, doEval); } } /* * Term -> '(' Or ')' * Term -> '!' Term * Term -> Leaf Operator Leaf * Term -> Leaf */ static CondResult -CondParser_Term(CondParser *par, Boolean doEval) +CondParser_Term(CondParser *par, bool doEval) { CondResult res; Token t; t = CondParser_Token(par, doEval); if (t == TOK_TRUE) return CR_TRUE; if (t == TOK_FALSE) return CR_FALSE; if (t == TOK_LPAREN) { res = CondParser_Or(par, doEval); if (res == CR_ERROR) return CR_ERROR; if (CondParser_Token(par, doEval) != TOK_RPAREN) return CR_ERROR; return res; } if (t == TOK_NOT) { res = CondParser_Term(par, doEval); if (res == CR_TRUE) res = CR_FALSE; else if (res == CR_FALSE) res = CR_TRUE; return res; } return CR_ERROR; } /* * And -> Term '&&' And * And -> Term */ static CondResult -CondParser_And(CondParser *par, Boolean doEval) +CondParser_And(CondParser *par, bool doEval) { CondResult res; Token op; res = CondParser_Term(par, doEval); if (res == CR_ERROR) return CR_ERROR; op = CondParser_Token(par, doEval); if (op == TOK_AND) { if (res == CR_TRUE) return CondParser_And(par, doEval); - if (CondParser_And(par, FALSE) == CR_ERROR) + if (CondParser_And(par, false) == CR_ERROR) return CR_ERROR; return res; } CondParser_PushBack(par, op); return res; } /* * Or -> And '||' Or * Or -> And */ static CondResult -CondParser_Or(CondParser *par, Boolean doEval) +CondParser_Or(CondParser *par, bool doEval) { CondResult res; Token op; res = CondParser_And(par, doEval); if (res == CR_ERROR) return CR_ERROR; op = CondParser_Token(par, doEval); if (op == TOK_OR) { if (res == CR_FALSE) return CondParser_Or(par, doEval); - if (CondParser_Or(par, FALSE) == CR_ERROR) + if (CondParser_Or(par, false) == CR_ERROR) return CR_ERROR; return res; } CondParser_PushBack(par, op); return res; } static CondEvalResult -CondParser_Eval(CondParser *par, Boolean *out_value) +CondParser_Eval(CondParser *par, bool *out_value) { CondResult res; DEBUG1(COND, "CondParser_Eval: %s\n", par->p); - res = CondParser_Or(par, TRUE); + res = CondParser_Or(par, true); if (res == CR_ERROR) return COND_INVALID; - if (CondParser_Token(par, FALSE) != TOK_EOF) + if (CondParser_Token(par, false) != TOK_EOF) return COND_INVALID; *out_value = res == CR_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 char *cond, Boolean *out_value, Boolean plain, - Boolean (*evalBare)(size_t, const char *), Boolean negate, - Boolean eprint, Boolean strictLHS) +CondEvalExpression(const char *cond, bool *out_value, bool plain, + bool (*evalBare)(size_t, const char *), bool negate, + bool eprint, bool strictLHS) { CondParser par; CondEvalResult rval; lhsStrict = strictLHS; cpp_skip_hspace(&cond); par.plain = plain; par.evalBare = evalBare; par.negateEvalBare = negate; par.p = cond; par.curr = TOK_NONE; - par.printedError = FALSE; + par.printedError = false; rval = CondParser_Eval(&par, out_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) +Cond_EvalCondition(const char *cond, bool *out_value) { - return CondEvalExpression(cond, out_value, TRUE, - FuncDefined, FALSE, FALSE, FALSE); + return CondEvalExpression(cond, out_value, true, + FuncDefined, false, false, false); } -static Boolean +static bool IsEndif(const char *p) { return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); } -static Boolean -DetermineKindOfConditional(const char **pp, Boolean *out_plain, - Boolean (**out_evalBare)(size_t, const char *), - Boolean *out_negate) +static bool +DetermineKindOfConditional(const char **pp, bool *out_plain, + bool (**out_evalBare)(size_t, const char *), + bool *out_negate) { const char *p = *pp; p += 2; - *out_plain = FALSE; + *out_plain = false; *out_evalBare = FuncDefined; - *out_negate = FALSE; + *out_negate = false; if (*p == 'n') { p++; - *out_negate = TRUE; + *out_negate = true; } if (is_token(p, "def", 3)) { /* .ifdef and .ifndef */ p += 3; } else if (is_token(p, "make", 4)) { /* .ifmake and .ifnmake */ p += 4; *out_evalBare = FuncMake; } else if (is_token(p, "", 0) && !*out_negate) { /* plain .if */ - *out_plain = TRUE; + *out_plain = true; } else { /* * TODO: Add error message about unknown directive, * since there is no other known directive that starts * with 'el' or 'if'. * * Example: .elifx 123 */ - return FALSE; + return false; } *pp = p; - return TRUE; + return true; } /* * Evaluate the conditional directive in the line, which is one of: * * .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 that follow the - * conditional (when evaluates to TRUE) + * conditional (when evaluates to true) * COND_SKIP to skip the lines after the conditional - * (when evaluates to 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) { typedef enum IfState { - /* None of the previous evaluated to TRUE. */ + /* None of the previous evaluated to true. */ IFS_INITIAL = 0, - /* The previous evaluated to TRUE. + /* 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. */ + /* 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; - Boolean plain; - Boolean (*evalBare)(size_t, const char *); - Boolean negate; - Boolean isElif; - Boolean value; + bool plain; + bool (*evalBare)(size_t, const char *); + bool negate; + bool isElif; + bool value; IfState state; const char *p = line; if (cond_states == NULL) { cond_states = bmake_malloc( cond_states_cap * sizeof *cond_states); cond_states[0] = IFS_ACTIVE; } p++; /* skip the leading '.' */ cpp_skip_hspace(&p); if (IsEndif(p)) { /* It is an '.endif'. */ if (p[5] != '\0') { Parse_Error(PARSE_FATAL, "The .endif directive does not take arguments."); } 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_states[cond_depth] & IFS_ACTIVE ? COND_PARSE : COND_SKIP; } /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ if (p[0] == 'e') { if (p[1] != 'l') { /* * Unknown directive. It might still be a * transformation rule like '.elisp.scm', * therefore no error message here. */ return COND_INVALID; } /* Quite likely this is 'else' or 'elif' */ p += 2; if (is_token(p, "se", 2)) { /* It is an 'else'. */ if (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_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_states[cond_depth] = state; return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; } /* Assume for now it is an elif */ - isElif = TRUE; + isElif = true; } else - isElif = FALSE; + isElif = false; 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 */ } if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) return COND_INVALID; if (isElif) { if (cond_depth == cond_min_depth) { Parse_Error(PARSE_FATAL, "if-less elif"); return COND_PARSE; } state = cond_states[cond_depth]; if (state & IFS_SEEN_ELSE) { Parse_Error(PARSE_WARNING, "extra elif"); cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; return COND_SKIP; } if (state != IFS_INITIAL) { cond_states[cond_depth] = IFS_WAS_ACTIVE; return COND_SKIP; } } else { /* Normal .if */ 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. */ cond_states_cap += 32; cond_states = bmake_realloc(cond_states, cond_states_cap * sizeof *cond_states); } state = cond_states[cond_depth]; cond_depth++; if (!(state & IFS_ACTIVE)) { /* * If we aren't parsing the data, * treat as always false. */ cond_states[cond_depth] = IFS_WAS_ACTIVE; return COND_SKIP; } } /* And evaluate the conditional expression */ if (CondEvalExpression(p, &value, plain, evalBare, negate, - TRUE, TRUE) == COND_INVALID) { + true, true) == COND_INVALID) { /* Syntax error in conditional, error message already output. */ /* Skip everything to matching .endif */ /* XXX: An extra '.else' is not detected in this case. */ cond_states[cond_depth] = IFS_WAS_ACTIVE; return COND_SKIP; } if (!value) { cond_states[cond_depth] = IFS_INITIAL; return COND_SKIP; } 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; } diff --git a/contrib/bmake/dir.c b/contrib/bmake/dir.c index 026983e3ec10..627e654387f8 100644 --- a/contrib/bmake/dir.c +++ b/contrib/bmake/dir.c @@ -1,1730 +1,1730 @@ -/* $NetBSD: dir.c,v 1.270 2021/02/05 05:48:19 rillig Exp $ */ +/* $NetBSD: dir.c,v 1.272 2021/04/04 10:13:09 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. */ /* * Directory searching using wildcards and/or normal names. * Used both for source wildcarding in the makefile and for finding * implicit sources. * * The interface for this module is: * Dir_Init Initialize the module. * * Dir_InitCur Set the cur CachedDir. * * Dir_InitDot Set the dot CachedDir. * * Dir_End Clean up the module. * * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath. * * Dir_HasWildcards - * Returns TRUE if the name given it needs to + * Returns true if the name given it needs to * be wildcard-expanded. * * SearchPath_Expand * Expand a filename pattern to find all matching files * from the search path. * * Dir_FindFile Searches for a file on a given search path. * If it exists, the entire path is returned. * Otherwise NULL is returned. * * Dir_FindHereOrAbove * Search for a path in the current directory and * then all the directories above it in turn until * the path is found or we reach the root ("/"). * * Dir_UpdateMTime * Update the modification time and path of a node with * data from the file corresponding to the node. * * SearchPath_Add Add a directory to a search path. * * SearchPath_ToFlags * Given a search path and a command flag, create * a string with each of the directories in the path * preceded by the command flag and all of them * separated by a space. * * Dir_Destroy Destroy an element of a search path. Frees up all * things that can be freed for the element as long * as the element is no longer referenced by any other * search path. * * SearchPath_Clear * Resets a search path to the empty list. * * For debugging: * Dir_PrintDirectories * Print stats about the directory cache. */ #include #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.270 2021/02/05 05:48:19 rillig Exp $"); +MAKE_RCSID("$NetBSD: dir.c,v 1.272 2021/04/04 10:13:09 rillig Exp $"); /* * 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 (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 (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_UpdateMTime was actually called. */ /* A cache for the filenames in a directory. */ struct CachedDir { /* * 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. + * Parse_Var. */ char *name; /* * The number of SearchPaths that refer to this directory. * Plus the number of global variables that refer to this directory. * References from openDirs do not count though. */ int refCount; /* The number of times a file in this directory has been found. */ int hits; /* The names of the directory entries. */ HashSet files; }; typedef List CachedDirList; typedef ListNode CachedDirListNode; typedef ListNode SearchPathNode; /* A list of cached directories, with fast lookup by directory name. */ typedef struct OpenDirs { CachedDirList list; HashTable /* of CachedDirListNode */ table; } OpenDirs; typedef enum CachedStatsFlags { CST_NONE = 0, CST_LSTAT = 1 << 0, /* call lstat(2) instead of stat(2) */ CST_UPDATE = 1 << 1 /* ignore existing cached entry */ } CachedStatsFlags; SearchPath dirSearchPath = { LST_INIT }; /* main search path */ static OpenDirs openDirs; /* all cached directories */ /* * 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 */ /* The cached contents of ".", the relative current directory. */ static CachedDir *dot = NULL; /* The cached contents of the absolute current directory. */ static CachedDir *cur = NULL; /* A fake path entry indicating we need to look for '.' last. */ static CachedDir *dotLast = NULL; /* * 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 */ static void OpenDirs_Remove(OpenDirs *, const char *); static CachedDir * CachedDir_New(const char *name) { CachedDir *dir = bmake_malloc(sizeof *dir); dir->name = bmake_strdup(name); dir->refCount = 0; dir->hits = 0; HashSet_Init(&dir->files); #ifdef DEBUG_REFCNT DEBUG2(DIR, "CachedDir %p new for \"%s\"\n", dir, dir->name); #endif return dir; } static CachedDir * CachedDir_Ref(CachedDir *dir) { dir->refCount++; #ifdef DEBUG_REFCNT DEBUG3(DIR, "CachedDir %p ++ %d for \"%s\"\n", dir, dir->refCount, dir->name); #endif return dir; } static void CachedDir_Unref(CachedDir *dir) { dir->refCount--; #ifdef DEBUG_REFCNT DEBUG3(DIR, "CachedDir %p -- %d for \"%s\"\n", dir, dir->refCount, dir->name); #endif if (dir->refCount > 0) return; #ifdef DEBUG_REFCNT DEBUG2(DIR, "CachedDir %p free for \"%s\"\n", dir, dir->name); #endif OpenDirs_Remove(&openDirs, dir->name); free(dir->name); HashSet_Done(&dir->files); free(dir); } /* Update the value of the CachedDir variable, updating the reference counts. */ static void CachedDir_Assign(CachedDir **var, CachedDir *dir) { CachedDir *prev; prev = *var; *var = dir; if (dir != NULL) CachedDir_Ref(dir); if (prev != NULL) CachedDir_Unref(prev); } static void OpenDirs_Init(OpenDirs *odirs) { Lst_Init(&odirs->list); HashTable_Init(&odirs->table); } #ifdef CLEANUP static void OpenDirs_Done(OpenDirs *odirs) { CachedDirListNode *ln = odirs->list.first; DEBUG1(DIR, "OpenDirs_Done: %u entries to remove\n", odirs->table.numEntries); while (ln != NULL) { CachedDirListNode *next = ln->next; CachedDir *dir = ln->datum; DEBUG2(DIR, "OpenDirs_Done: refCount %d for \"%s\"\n", dir->refCount, dir->name); CachedDir_Unref(dir); /* removes the dir from odirs->list */ ln = next; } Lst_Done(&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) { if (HashTable_FindEntry(&odirs->table, cdir->name) != NULL) return; Lst_Append(&odirs->list, cdir); 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); } /* * Returns 0 and the result of stat(2) or lstat(2) in *out_cst, * or -1 on error. */ static int cached_stats(const char *pathname, struct cached_stat *out_cst, CachedStatsFlags flags) { HashTable *tbl = flags & CST_LSTAT ? &lmtimes : &mtimes; struct stat sys_st; struct cached_stat *cst; int rc; if (pathname == NULL || pathname[0] == '\0') return -1; /* This can happen in meta mode. */ cst = HashTable_FindValue(tbl, pathname); if (cst != NULL && !(flags & CST_UPDATE)) { *out_cst = *cst; DEBUG2(DIR, "Using cached time %s for %s\n", Targ_FmtTime(cst->cst_mtime), pathname); return 0; } rc = (flags & CST_LSTAT ? lstat : stat)(pathname, &sys_st); if (rc == -1) return -1; /* don't cache negative lookups */ if (sys_st.st_mtime == 0) sys_st.st_mtime = 1; /* avoid confusion with missing file */ if (cst == NULL) { cst = bmake_malloc(sizeof *cst); HashTable_Set(tbl, pathname, cst); } cst->cst_mtime = sys_st.st_mtime; cst->cst_mode = sys_st.st_mode; *out_cst = *cst; DEBUG2(DIR, " Caching %s for %s\n", Targ_FmtTime(sys_st.st_mtime), pathname); return 0; } int cached_stat(const char *pathname, struct cached_stat *cst) { return cached_stats(pathname, cst, CST_NONE); } int cached_lstat(const char *pathname, struct cached_stat *cst) { return cached_stats(pathname, cst, CST_LSTAT); } /* Initialize the directories module. */ void Dir_Init(void) { OpenDirs_Init(&openDirs); HashTable_Init(&mtimes); HashTable_Init(&lmtimes); CachedDir_Assign(&dotLast, CachedDir_New(".DOTLAST")); } /* * Called by Dir_InitDir and whenever .CURDIR is assigned to. */ void Dir_InitCur(const char *newCurdir) { CachedDir *dir; if (newCurdir == NULL) return; /* * Our build directory is not the same as our source directory. * Keep this one around too. */ dir = SearchPath_Add(NULL, newCurdir); if (dir == NULL) return; CachedDir_Assign(&cur, dir); } /* * (Re)initialize "dot" (current/object directory) path hash. * Some directories may be cached. */ void Dir_InitDot(void) { CachedDir *dir; dir = SearchPath_Add(NULL, "."); if (dir == NULL) { Error("Cannot open `.' (%s)", strerror(errno)); exit(2); /* Not 1 so -q can distinguish error */ } CachedDir_Assign(&dot, dir); Dir_SetPATH(); /* initialize */ } /* Clean up the directories module. */ void Dir_End(void) { #ifdef CLEANUP CachedDir_Assign(&cur, NULL); CachedDir_Assign(&dot, NULL); CachedDir_Assign(&dotLast, NULL); SearchPath_Clear(&dirSearchPath); OpenDirs_Done(&openDirs); HashTable_Done(&mtimes); HashTable_Done(&lmtimes); #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 seenDotLast = FALSE; /* true if we should search '.' last */ + bool seenDotLast = false; /* true if we should search '.' last */ Global_Delete(".PATH"); if ((ln = dirSearchPath.dirs.first) != NULL) { CachedDir *dir = ln->datum; if (dir == dotLast) { - seenDotLast = TRUE; + seenDotLast = true; Global_Append(".PATH", dotLast->name); } } if (!seenDotLast) { if (dot != NULL) Global_Append(".PATH", dot->name); if (cur != NULL) Global_Append(".PATH", cur->name); } for (ln = dirSearchPath.dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (dir == dotLast) continue; if (dir == dot && seenDotLast) continue; Global_Append(".PATH", dir->name); } if (seenDotLast) { if (dot != NULL) Global_Append(".PATH", dot->name); if (cur != NULL) Global_Append(".PATH", cur->name); } } /* * 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. + * Return true if the word should be expanded, false otherwise. */ -Boolean +bool Dir_HasWildcards(const char *name) { const char *p; - Boolean wild = FALSE; + bool wild = false; int braces = 0, brackets = 0; for (p = name; *p != '\0'; p++) { switch (*p) { case '{': braces++; - wild = TRUE; + wild = true; break; case '}': braces--; break; case '[': brackets++; - wild = TRUE; + wild = true; break; case ']': brackets--; break; case '?': case '*': - wild = TRUE; + 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, * delegate the work to the shell, using the '!=' variable assignment * operator, the ':sh' variable modifier or the ':!...!' 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'; + bool 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_InitSet(&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 NetBSD sh, NetBSD ksh, * bash, dash, csh and probably many other shells as well. */ 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 +static bool contains_wildcard(const char *p) { for (; *p != '\0'; p++) { switch (*p) { case '*': case '?': case '{': case '[': - return TRUE; + return true; } } - return FALSE; + 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)) { SearchPath_Expand(path, file, expansions); free(file); } else { Lst_Append(expansions, file); } /* skip over the comma or closing brace */ piece = piece_end + 1; } } /* 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->dirs.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"); } /* * The wildcard isn't in the first component. * Find all the components up to the one with the wildcard. */ static void SearchPath_ExpandMiddle(SearchPath *path, const char *pattern, const char *wildcardComponent, StringList *expansions) { char *prefix, *dirpath, *end; SearchPath *partPath; prefix = bmake_strsedup(pattern, wildcardComponent + 1); /* * XXX: Check the "the directory is added to the path" part. * It is probably surprising that the directory before a * wildcard gets added to the path. */ /* * XXX: Only the first match of the prefix in the path is * taken, any others are ignored. The expectation may be * that the pattern is expanded in the whole path. */ 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. * * XXX: Check whether the above comment is still true. */ if (dirpath == NULL) return; end = &dirpath[strlen(dirpath) - 1]; /* XXX: What about multiple trailing slashes? */ if (*end == '/') *end = '\0'; partPath = SearchPath_New(); (void)SearchPath_Add(partPath, dirpath); DirExpandPath(wildcardComponent + 1, partPath, expansions); SearchPath_Free(partPath); } /* * Expand the given pattern into a list of existing filenames by globbing it, * looking in each directory from the search path. * * Input: * path the directories in which to find the files * pattern the pattern to expand * expansions the list on which to place the results */ void SearchPath_Expand(SearchPath *path, const char *pattern, StringList *expansions) { const char *brace, *slash, *wildcard, *wildcardComponent; assert(path != NULL); assert(expansions != NULL); DEBUG1(DIR, "Expanding \"%s\"... ", pattern); brace = strchr(pattern, '{'); if (brace != NULL) { DirExpandCurly(pattern, brace, path, expansions); goto done; } /* At this point, the pattern does not contain '{'. */ slash = strchr(pattern, '/'); if (slash == NULL) { /* The pattern has no directory component. */ /* First the files in dot. */ DirMatchFiles(pattern, dot, expansions); /* Then the files in every other directory on the path. */ DirExpandPath(pattern, path, expansions); goto done; } /* At this point, the pattern has a directory component. */ /* Find the first wildcard in the pattern. */ for (wildcard = pattern; *wildcard != '\0'; wildcard++) if (*wildcard == '?' || *wildcard == '[' || *wildcard == '*') break; if (*wildcard == '\0') { /* * No directory component and no wildcard at all -- this * should never happen as in such a simple case there is no * need to expand anything. */ DirExpandPath(pattern, path, expansions); goto done; } /* Back up to the start of the component containing the wildcard. */ /* XXX: This handles '///' and '/' differently. */ wildcardComponent = wildcard; while (wildcardComponent > pattern && *wildcardComponent != '/') wildcardComponent--; if (wildcardComponent == pattern) { /* The first component contains the wildcard. */ /* Start the search from the local directory */ DirExpandPath(pattern, path, expansions); } else { SearchPath_ExpandMiddle(path, pattern, wildcardComponent, expansions); } done: 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 */ DEBUG1(DIR, " %s ...\n", dir->name); if (!HashSet_Contains(&dir->files, base)) return NULL; file = str_concat3(dir->name, "/", base); DEBUG1(DIR, " 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 cached_stat cst; char *file = dir == dot ? bmake_strdup(name) : str_concat3(dir->name, "/", name); DEBUG1(DIR, "checking %s ...\n", file); 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 */ DEBUG1(DIR, " %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 (!HashSet_Contains(&dir->files, cp)) { DEBUG0(DIR, " must be here but isn't -- returning\n"); return bmake_strdup(""); /* to terminate the search */ } dir->hits++; hits++; DEBUG1(DIR, " 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 (HashSet_Contains(&dot->files, base)) { DEBUG0(DIR, " in '.'\n"); hits++; dot->hits++; return bmake_strdup(name); } if (cur != NULL && HashSet_Contains(&cur->files, base)) { DEBUG1(DIR, " in ${.CURDIR} = %s\n", cur->name); hits++; cur->hits++; return str_concat3(cur->name, "/", base); } return NULL; } -static Boolean -FindFileRelative(SearchPath *path, Boolean seenDotLast, +static bool +FindFileRelative(SearchPath *path, bool seenDotLast, const char *name, char **out_file) { SearchPathNode *ln; - Boolean checkedDot = FALSE; + bool checkedDot = false; char *file; DEBUG0(DIR, " Trying subdirectories...\n"); if (!seenDotLast) { if (dot != NULL) { - checkedDot = TRUE; + checkedDot = true; if ((file = DirLookupSubdir(dot, name)) != NULL) goto found; } if (cur != NULL && (file = DirLookupSubdir(cur, name)) != NULL) goto found; } for (ln = path->dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (dir == dotLast) continue; if (dir == dot) { if (checkedDot) continue; - checkedDot = TRUE; + checkedDot = true; } if ((file = DirLookupSubdir(dir, name)) != NULL) goto found; } if (seenDotLast) { if (dot != NULL && !checkedDot) { - checkedDot = TRUE; + checkedDot = true; if ((file = DirLookupSubdir(dot, name)) != NULL) goto found; } if (cur != NULL && (file = DirLookupSubdir(cur, name)) != NULL) goto found; } if (checkedDot) { /* * Already checked by the given name, since . was in * the path, so no point in proceeding. */ DEBUG0(DIR, " Checked . already, returning NULL\n"); file = NULL; goto found; } - return FALSE; + return false; found: *out_file = file; - return TRUE; + return true; } -static Boolean -FindFileAbsolute(SearchPath *path, Boolean const seenDotLast, +static bool +FindFileAbsolute(SearchPath *path, bool const seenDotLast, const char *const name, const char *const base, char **out_file) { char *file; SearchPathNode *ln; /* * 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. */ DEBUG0(DIR, " Trying exact path matches...\n"); if (!seenDotLast && cur != NULL && ((file = DirLookupAbs(cur, name, base)) != NULL)) goto found; for (ln = path->dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (dir == dotLast) continue; if ((file = DirLookupAbs(dir, name, base)) != NULL) goto found; } if (seenDotLast && cur != NULL && ((file = DirLookupAbs(cur, name, base)) != NULL)) goto found; - return FALSE; + return false; found: if (file[0] == '\0') { free(file); file = NULL; } *out_file = file; - return TRUE; + return true; } /* * 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) { char *file; /* the current filename to check */ - Boolean seenDotLast = FALSE; /* true if we should search dot last */ + bool seenDotLast = false; /* true if we should search dot last */ struct cached_stat cst; /* Buffer for stat, if necessary */ const char *trailing_dot = "."; const char *base = str_basename(name); DEBUG1(DIR, "Searching for %s ...", name); if (path == NULL) { DEBUG0(DIR, "couldn't open path, file not found\n"); misses++; return NULL; } if (path->dirs.first != NULL) { CachedDir *dir = path->dirs.first->datum; if (dir == dotLast) { - seenDotLast = TRUE; + seenDotLast = true; DEBUG0(DIR, "[dot last]..."); } } DEBUG0(DIR, "\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 (base == name || (base - name == 2 && *name == '.')) { SearchPathNode *ln; /* * We look through all the directories on the path seeking one * which contains the final component of the given name. If * such a file 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 (!seenDotLast && (file = DirFindDot(name, base)) != NULL) return file; for (ln = path->dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (dir == dotLast) continue; if ((file = DirLookup(dir, base)) != NULL) return file; } if (seenDotLast && (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 file, 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 (base == name) { DEBUG0(DIR, " failed.\n"); misses++; return NULL; } if (*base == '\0') { /* we were given a trailing "/" */ base = trailing_dot; } if (name[0] != '/') { if (FindFileRelative(path, seenDotLast, name, &file)) return file; } else { if (FindFileAbsolute(path, seenDotLast, name, base, &file)) 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... */ #if 0 { CachedDir *dir; char *prefix; if (base == trailing_dot) { base = strrchr(name, '/'); base++; } prefix = bmake_strsedup(name, base - 1); (void)SearchPath_Add(path, prefix); free(prefix); bigmisses++; if (path->last == NULL) return NULL; dir = path->last->datum; if (HashSet_Contains(&dir->files, base)) return bmake_strdup(name); return NULL; } #else DEBUG1(DIR, " Looking for \"%s\" ...\n", name); bigmisses++; if (cached_stat(name, &cst) == 0) { return bmake_strdup(name); } DEBUG0(DIR, " failed. Returning NULL\n"); return NULL; #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 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, &cst) != -1) { /* * success! if we found a file, chop off * the filename so we return a directory. */ 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; } /* * This is an implied source, and it may have moved, * see if we can find it via the current .PATH */ static char * ResolveMovedDepends(GNode *gn) { char *fullName; const char *base = str_basename(gn->name); if (base == gn->name) return NULL; fullName = Dir_FindFile(base, Suff_FindPath(gn)); if (fullName == NULL) return NULL; /* * Put the found file in gn->path so that we give that to the compiler. */ /* * XXX: Better just reset gn->path to NULL; updating it is already done * by Dir_UpdateMTime. */ gn->path = bmake_strdup(fullName); if (!Job_RunTarget(".STALE", gn->fname)) fprintf(stdout, /* XXX: Why stdout? */ "%s: %s, %d: ignoring stale %s for %s, found %s\n", progname, gn->fname, gn->lineno, makeDependfile, gn->name, fullName); return fullName; } static char * ResolveFullName(GNode *gn) { char *fullName; fullName = gn->path; if (fullName == NULL && !(gn->type & OP_NOPATH)) { fullName = Dir_FindFile(gn->name, Suff_FindPath(gn)); if (fullName == NULL && gn->flags & FROM_DEPEND && !Lst_IsEmpty(&gn->implicitParents)) fullName = ResolveMovedDepends(gn); DEBUG2(DIR, "Found '%s' as '%s'\n", gn->name, fullName != NULL ? fullName : "(not found)"); } if (fullName == NULL) fullName = bmake_strdup(gn->name); /* XXX: Is every piece of memory freed as it should? */ return fullName; } /* * Search gn along dirSearchPath and store its modification time in gn->mtime. * If no file is found, store 0 instead. * * The found file is stored in gn->path, unless the node already had a path. */ void -Dir_UpdateMTime(GNode *gn, Boolean recheck) +Dir_UpdateMTime(GNode *gn, bool recheck) { char *fullName; struct cached_stat cst; if (gn->type & OP_ARCHV) { Arch_UpdateMTime(gn); return; } if (gn->type & OP_PHONY) { gn->mtime = 0; return; } fullName = ResolveFullName(gn); if (cached_stats(fullName, &cst, recheck ? CST_UPDATE : CST_NONE) < 0) { if (gn->type & OP_MEMBER) { if (fullName != gn->path) free(fullName); Arch_UpdateMemberMTime(gn); return; } cst.cst_mtime = 0; } if (fullName != NULL && gn->path == NULL) gn->path = fullName; /* XXX: else free(fullName)? */ gn->mtime = cst.cst_mtime; } /* * Read the directory and add it to the cache in openDirs. * If a path is given, add the directory to that path as well. */ static CachedDir * CacheNewDir(const char *name, SearchPath *path) { CachedDir *dir = NULL; DIR *d; struct dirent *dp; if ((d = opendir(name)) == NULL) { DEBUG1(DIR, "Caching %s ... not found\n", name); return dir; } DEBUG1(DIR, "Caching %s ...\n", name); dir = CachedDir_New(name); 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)HashSet_Add(&dir->files, dp->d_name); } (void)closedir(d); OpenDirs_Add(&openDirs, dir); if (path != NULL) Lst_Append(&path->dirs, CachedDir_Ref(dir)); DEBUG1(DIR, "Caching %s done\n", name); return dir; } /* * Read the list of filenames in the directory and store the result * in openDirs. * * 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 openDirs * name The name of the directory to add. * The name is not normalized in any way. * Output: * result If no path is given and the directory exists, the * returned CachedDir has a reference count of 0. It * must either be assigned to a variable using * CachedDir_Assign or be appended to a SearchPath using * Lst_Append and CachedDir_Ref. */ CachedDir * SearchPath_Add(SearchPath *path, const char *name) { if (path != NULL && strcmp(name, ".DOTLAST") == 0) { SearchPathNode *ln; /* XXX: Linear search gets slow with thousands of entries. */ for (ln = path->dirs.first; ln != NULL; ln = ln->next) { CachedDir *pathDir = ln->datum; if (strcmp(pathDir->name, name) == 0) return pathDir; } Lst_Prepend(&path->dirs, CachedDir_Ref(dotLast)); } if (path != NULL) { /* XXX: Why is OpenDirs only checked if path != NULL? */ CachedDir *dir = OpenDirs_Find(&openDirs, name); if (dir != NULL) { if (Lst_FindDatum(&path->dirs, dir) == NULL) Lst_Append(&path->dirs, CachedDir_Ref(dir)); return dir; } } return CacheNewDir(name, path); } /* * Return a copy of dirSearchPath, incrementing the reference counts for * the contained directories. */ SearchPath * Dir_CopyDirSearchPath(void) { SearchPath *path = SearchPath_New(); SearchPathNode *ln; for (ln = dirSearchPath.dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; Lst_Append(&path->dirs, CachedDir_Ref(dir)); } return path; } /* * 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. */ char * SearchPath_ToFlags(SearchPath *path, const char *flag) { Buffer buf; SearchPathNode *ln; Buf_Init(&buf); if (path != NULL) { for (ln = path->dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; Buf_AddStr(&buf, " "); Buf_AddStr(&buf, flag); Buf_AddStr(&buf, dir->name); } } return Buf_DoneData(&buf); } /* Free the search path and all directories mentioned in it. */ void SearchPath_Free(SearchPath *path) { SearchPathNode *ln; for (ln = path->dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; CachedDir_Unref(dir); } Lst_Done(&path->dirs); free(path); } /* * Clear out all elements from the given search path. * The path is set to the empty list but is not destroyed. */ void SearchPath_Clear(SearchPath *path) { while (!Lst_IsEmpty(&path->dirs)) { CachedDir *dir = Lst_Dequeue(&path->dirs); CachedDir_Unref(dir); } } /* * Concatenate two paths, adding the second to the end of the first, * skipping duplicates. */ void SearchPath_AddAll(SearchPath *dst, SearchPath *src) { SearchPathNode *ln; for (ln = src->dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; if (Lst_FindDatum(&dst->dirs, dir) == NULL) Lst_Append(&dst->dirs, CachedDir_Ref(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("# refs hits directory\n"); for (ln = openDirs.list.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; debug_printf("# %4d %4d %s\n", dir->refCount, dir->hits, dir->name); } } void SearchPath_Print(const SearchPath *path) { SearchPathNode *ln; for (ln = path->dirs.first; ln != NULL; ln = ln->next) { const CachedDir *dir = ln->datum; debug_printf("%s ", dir->name); } } diff --git a/contrib/bmake/dir.h b/contrib/bmake/dir.h index f4bf32ab5b83..d96393c62ebb 100644 --- a/contrib/bmake/dir.h +++ b/contrib/bmake/dir.h @@ -1,107 +1,107 @@ -/* $NetBSD: dir.h,v 1.43 2021/02/05 05:48:19 rillig Exp $ */ +/* $NetBSD: dir.h,v 1.44 2021/04/03 11:08:40 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 typedef struct CachedDir CachedDir; void Dir_Init(void); void Dir_InitCur(const char *); void Dir_InitDot(void); void Dir_End(void); void Dir_SetPATH(void); -Boolean Dir_HasWildcards(const char *); +bool Dir_HasWildcards(const char *); void SearchPath_Expand(SearchPath *, const char *, StringList *); char *Dir_FindFile(const char *, SearchPath *); char *Dir_FindHereOrAbove(const char *, const char *); -void Dir_UpdateMTime(GNode *, Boolean); +void Dir_UpdateMTime(GNode *, bool); CachedDir *SearchPath_Add(SearchPath *, const char *); char *SearchPath_ToFlags(SearchPath *, const char *); void SearchPath_Clear(SearchPath *); void SearchPath_AddAll(SearchPath *, SearchPath *); void Dir_PrintDirectories(void); void SearchPath_Print(const SearchPath *); SearchPath *Dir_CopyDirSearchPath(void); /* Stripped-down variant of struct stat. */ struct cached_stat { time_t cst_mtime; mode_t cst_mode; }; int cached_lstat(const char *, struct cached_stat *); int cached_stat(const char *, struct cached_stat *); #endif /* MAKE_DIR_H */ diff --git a/contrib/bmake/enum.h b/contrib/bmake/enum.h index b5f4630414ef..e10fcae045f6 100755 --- a/contrib/bmake/enum.h +++ b/contrib/bmake/enum.h @@ -1,239 +1,179 @@ -/* $NetBSD: enum.h,v 1.18 2021/02/02 21:26:51 rillig Exp $ */ +/* $NetBSD: enum.h,v 1.19 2021/03/15 16:00:05 rillig Exp $ */ /* Copyright (c) 2020 Roland Illig 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 HOLDER 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_ENUM_H #define MAKE_ENUM_H /* Generate string representations for bitmasks and simple enums. */ #include typedef struct EnumToStringSpec { int es_value; const char *es_name; } EnumToStringSpec; const char *Enum_FlagsToString(char *, size_t, int, const EnumToStringSpec *); /* For Enum_FlagsToString, the separator between flags. */ #define ENUM__SEP "|" /* * Generate the string that joins all possible flags, to see how large the * buffer must be. */ #define ENUM__JOIN_STR_1(v1) \ #v1 #define ENUM__JOIN_STR_2(v1, v2) \ ENUM__JOIN_STR_1(v1) ENUM__SEP \ ENUM__JOIN_STR_1(v2) #define ENUM__JOIN_STR_4(v1, v2, v3, v4) \ ENUM__JOIN_STR_2(v1, v2) ENUM__SEP \ ENUM__JOIN_STR_2(v3, v4) #define ENUM__JOIN_STR_8(v1, v2, v3, v4, v5, v6, v7, v8) \ ENUM__JOIN_STR_4(v1, v2, v3, v4) ENUM__SEP \ ENUM__JOIN_STR_4(v5, v6, v7, v8) #define ENUM__JOIN_STR_16(v01, v02, v03, v04, v05, v06, v07, v08, \ v09, v10, v11, v12, v13, v14, v15, v16) \ ENUM__JOIN_STR_8(v01, v02, v03, v04, v05, v06, v07, v08) ENUM__SEP \ ENUM__JOIN_STR_8(v09, v10, v11, v12, v13, v14, v15, v16) #define ENUM__JOIN_2(part1, part2) \ part1 ENUM__SEP part2 #define ENUM__JOIN_3(part1, part2, part3) \ part1 ENUM__SEP part2 ENUM__SEP part3 #define ENUM__JOIN_4(part1, part2, part3, part4) \ part1 ENUM__SEP part2 ENUM__SEP part3 ENUM__SEP part4 #define ENUM__JOIN_5(part1, part2, part3, part4, part5) \ part1 ENUM__SEP part2 ENUM__SEP part3 ENUM__SEP part4 ENUM__SEP part5 /* List the pairs of enum value and corresponding name. */ #define ENUM__SPEC_1(v1) \ { v1, #v1 } #define ENUM__SPEC_2(v1, v2) \ ENUM__SPEC_1(v1), \ ENUM__SPEC_1(v2) #define ENUM__SPEC_4(v1, v2, v3, v4) \ ENUM__SPEC_2(v1, v2), \ ENUM__SPEC_2(v3, v4) #define ENUM__SPEC_8(v1, v2, v3, v4, v5, v6, v7, v8) \ ENUM__SPEC_4(v1, v2, v3, v4), \ ENUM__SPEC_4(v5, v6, v7, v8) #define ENUM__SPEC_16(v01, v02, v03, v04, v05, v06, v07, v08, \ v09, v10, v11, v12, v13, v14, v15, v16) \ ENUM__SPEC_8(v01, v02, v03, v04, v05, v06, v07, v08), \ ENUM__SPEC_8(v09, v10, v11, v12, v13, v14, v15, v16) #define ENUM__SPECS_2(part1, part2) \ { part1, part2, { 0, "" } } #define ENUM__SPECS_3(part1, part2, part3) \ { part1, part2, part3, { 0, "" } } #define ENUM__SPECS_4(part1, part2, part3, part4) \ { part1, part2, part3, part4, { 0, "" } } #define ENUM__SPECS_5(part1, part2, part3, part4, part5) \ { part1, part2, part3, part4, part5, { 0, "" } } /* Declare the necessary data structures for calling Enum_FlagsToString. */ #define ENUM__FLAGS_RTTI(typnam, specs, joined) \ static const EnumToStringSpec typnam ## _ ## ToStringSpecs[] = specs; \ enum { typnam ## _ ## ToStringSize = sizeof (joined) }; \ MAKE_INLINE const char *typnam ## _ToString(char *buf, typnam value) \ { return Enum_FlagsToString(buf, typnam ## _ ## ToStringSize, \ value, typnam ## _ ## ToStringSpecs); \ } \ extern void enum_flags_rtti_dummy(void) -/* - * Declare the necessary data structures for calling Enum_FlagsToString - * for an enum with 2 flags. - */ -#define ENUM_FLAGS_RTTI_2(typnam, v1, v2) \ - ENUM__FLAGS_RTTI(typnam, \ - ENUM__SPECS_2( \ - ENUM__SPEC_1(v1), \ - ENUM__SPEC_1(v2)), \ - ENUM__JOIN_2( \ - ENUM__JOIN_STR_1(v1), \ - ENUM__JOIN_STR_1(v2))) - /* * Declare the necessary data structures for calling Enum_FlagsToString * for an enum with 3 flags. */ #define ENUM_FLAGS_RTTI_3(typnam, v1, v2, v3) \ ENUM__FLAGS_RTTI(typnam, \ ENUM__SPECS_2( \ ENUM__SPEC_2(v1, v2), \ ENUM__SPEC_1(v3)), \ ENUM__JOIN_2( \ ENUM__JOIN_STR_2(v1, v2), \ ENUM__JOIN_STR_1(v3))) -/* - * Declare the necessary data structures for calling Enum_FlagsToString - * for an enum with 4 flags. - */ -#define ENUM_FLAGS_RTTI_4(typnam, v1, v2, v3, v4) \ - ENUM__FLAGS_RTTI(typnam, \ - ENUM__SPECS_2( \ - ENUM__SPEC_2(v1, v2), \ - ENUM__SPEC_2(v3, v4)), \ - ENUM__JOIN_2( \ - ENUM__JOIN_STR_2(v1, v2), \ - ENUM__JOIN_STR_2(v3, v4))) - /* * Declare the necessary data structures for calling Enum_FlagsToString * for an enum with 6 flags. */ #define ENUM_FLAGS_RTTI_6(typnam, v1, v2, v3, v4, v5, v6) \ ENUM__FLAGS_RTTI(typnam, \ ENUM__SPECS_2( \ ENUM__SPEC_4(v1, v2, v3, v4), \ ENUM__SPEC_2(v5, v6)), \ ENUM__JOIN_2( \ ENUM__JOIN_STR_4(v1, v2, v3, v4), \ ENUM__JOIN_STR_2(v5, v6))) -/* - * Declare the necessary data structures for calling Enum_FlagsToString - * for an enum with 8 flags. - */ -#define ENUM_FLAGS_RTTI_8(typnam, v1, v2, v3, v4, v5, v6, v7, v8) \ - ENUM__FLAGS_RTTI(typnam, \ - ENUM__SPECS_2( \ - ENUM__SPEC_4(v1, v2, v3, v4), \ - ENUM__SPEC_4(v5, v6, v7, v8)), \ - ENUM__JOIN_2( \ - ENUM__JOIN_STR_4(v1, v2, v3, v4), \ - ENUM__JOIN_STR_4(v5, v6, v7, v8))) - /* * Declare the necessary data structures for calling Enum_FlagsToString * for an enum with 9 flags. */ #define ENUM_FLAGS_RTTI_9(typnam, v1, v2, v3, v4, v5, v6, v7, v8, v9) \ ENUM__FLAGS_RTTI(typnam, \ ENUM__SPECS_2( \ ENUM__SPEC_8(v1, v2, v3, v4, v5, v6, v7, v8), \ ENUM__SPEC_1(v9)), \ ENUM__JOIN_2( \ ENUM__JOIN_STR_8(v1, v2, v3, v4, v5, v6, v7, v8), \ ENUM__JOIN_STR_1(v9))) /* * Declare the necessary data structures for calling Enum_FlagsToString * for an enum with 31 flags. */ #define ENUM_FLAGS_RTTI_31(typnam, \ v01, v02, v03, v04, v05, v06, v07, v08, \ v09, v10, v11, v12, v13, v14, v15, v16, \ v17, v18, v19, v20, v21, v22, v23, v24, \ v25, v26, v27, v28, v29, v30, v31) \ ENUM__FLAGS_RTTI(typnam, \ ENUM__SPECS_5( \ ENUM__SPEC_16(v01, v02, v03, v04, v05, v06, v07, v08, \ v09, v10, v11, v12, v13, v14, v15, v16), \ ENUM__SPEC_8(v17, v18, v19, v20, v21, v22, v23, v24), \ ENUM__SPEC_4(v25, v26, v27, v28), \ ENUM__SPEC_2(v29, v30), \ ENUM__SPEC_1(v31)), \ ENUM__JOIN_5( \ ENUM__JOIN_STR_16(v01, v02, v03, v04, v05, v06, v07, v08, \ v09, v10, v11, v12, v13, v14, v15, v16), \ ENUM__JOIN_STR_8(v17, v18, v19, v20, v21, v22, v23, v24), \ ENUM__JOIN_STR_4(v25, v26, v27, v28), \ ENUM__JOIN_STR_2(v29, v30), \ ENUM__JOIN_STR_1(v31))) -/* - * Declare the necessary data structures for calling Enum_FlagsToString - * for an enum with 32 flags. - */ -#define ENUM_FLAGS_RTTI_32(typnam, \ - v01, v02, v03, v04, v05, v06, v07, v08, \ - v09, v10, v11, v12, v13, v14, v15, v16, \ - v17, v18, v19, v20, v21, v22, v23, v24, \ - v25, v26, v27, v28, v29, v30, v31, v32) \ - ENUM__FLAGS_RTTI(typnam, \ - ENUM__SPECS_2( \ - ENUM__SPEC_16(v01, v02, v03, v04, v05, v06, v07, v08, \ - v09, v10, v11, v12, v13, v14, v15, v16), \ - ENUM__SPEC_16(v17, v18, v19, v20, v21, v22, v23, v24, \ - v25, v26, v27, v28, v29, v30, v31, v32)), \ - ENUM__JOIN_2( \ - ENUM__JOIN_STR_16(v01, v02, v03, v04, v05, v06, v07, v08, \ - v09, v10, v11, v12, v13, v14, v15, v16), \ - ENUM__JOIN_STR_16(v17, v18, v19, v20, v21, v22, v23, v24, \ - v25, v26, v27, v28, v29, v30, v31, v32))) - #endif diff --git a/contrib/bmake/for.c b/contrib/bmake/for.c index 5705d9c5de0d..615efb7634c9 100644 --- a/contrib/bmake/for.c +++ b/contrib/bmake/for.c @@ -1,517 +1,517 @@ -/* $NetBSD: for.c,v 1.141 2021/02/04 21:33:13 rillig Exp $ */ +/* $NetBSD: for.c,v 1.142 2021/04/03 11:08:40 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 have the form: * * .for in * # the body * .endfor * * When a .for line is parsed, the following lines are copied to the body of * the .for loop, until the corresponding .endfor line is reached. In this * phase, the body is not yet evaluated. This also applies to any nested * .for loops. * * After reaching the .endfor, the values from the .for line are grouped * according to the number of variables. For each such group, the unexpanded * body is scanned for variable expressions, and those that match the variable * names are replaced with expressions of the form ${:U...} or $(:U...). * After that, the body is treated like a file from an .include directive. * * 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.141 2021/02/04 21:33:13 rillig Exp $"); +MAKE_RCSID("$NetBSD: for.c,v 1.142 2021/04/03 11:08:40 rillig Exp $"); /* One of the variables to the left of the "in" in a .for loop. */ typedef struct ForVar { char *name; size_t nameLen; } ForVar; typedef struct ForLoop { 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; + bool short_var; unsigned int sub_next; /* Where to continue iterating */ } ForLoop; static ForLoop *accumFor; /* Loop being accumulated */ static int forLevel = 0; /* Nesting level */ static ForLoop * ForLoop_New(void) { ForLoop *f = bmake_malloc(sizeof *f); Buf_Init(&f->body); Vector_Init(&f->vars, sizeof(ForVar)); f->items.words = NULL; f->items.freeIt = NULL; Buf_Init(&f->curBody); - f->short_var = FALSE; + f->short_var = false; f->sub_next = 0; return f; } static void ForLoop_Free(ForLoop *f) { Buf_Done(&f->body); while (f->vars.len > 0) { ForVar *var = Vector_Pop(&f->vars); free(var->name); } Vector_Done(&f->vars); Words_Free(f->items); Buf_Done(&f->curBody); free(f); } static void ForLoop_AddVar(ForLoop *f, const char *name, size_t len) { ForVar *var = Vector_Push(&f->vars); var->name = bmake_strldup(name, len); var->nameLen = len; } -static Boolean +static bool ForLoop_ParseVarnames(ForLoop *f, const char **pp) { const char *p = *pp; for (;;) { size_t len; cpp_skip_whitespace(&p); if (*p == '\0') { Parse_Error(PARSE_FATAL, "missing `in' in for"); - return FALSE; + return false; } /* * 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; + f->short_var = true; ForLoop_AddVar(f, p, len); p += len; } if (f->vars.len == 0) { Parse_Error(PARSE_FATAL, "no iteration variables in for"); - return FALSE; + return false; } *pp = p; - return TRUE; + return true; } -static Boolean +static bool ForLoop_ParseItems(ForLoop *f, const char *p) { char *items; cpp_skip_whitespace(&p); if (Var_Subst(p, SCOPE_GLOBAL, VARE_WANTRES, &items) != VPR_OK) { Parse_Error(PARSE_FATAL, "Error in .for loop items"); - return FALSE; + return false; } - f->items = Str_Words(items, FALSE); + 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} */ if (f->items.len != 0 && f->items.len % f->vars.len != 0) { Parse_Error(PARSE_FATAL, "Wrong number of words (%u) in .for " "substitution list with %u variables", (unsigned)f->items.len, (unsigned)f->vars.len); - return FALSE; + return false; } - return TRUE; + return true; } -static Boolean +static bool IsFor(const char *p) { return p[0] == 'f' && p[1] == 'o' && p[2] == 'r' && ch_isspace(p[3]); } -static Boolean +static bool 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) { ForLoop *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; f = ForLoop_New(); if (!ForLoop_ParseVarnames(f, &p)) { ForLoop_Free(f); return -1; } if (!ForLoop_ParseItems(f, p)) { /* Continue parsing the .for loop, but don't iterate. */ f->items.len = 0; } accumFor = f; forLevel = 1; return 1; } /* * Add another line to the .for loop that is being built up. - * Returns FALSE when the matching .endfor is reached. + * Returns false when the matching .endfor is reached. */ -Boolean +bool For_Accum(const char *line) { const char *p = line; if (*p == '.') { p++; cpp_skip_whitespace(&p); if (IsEndfor(p)) { DEBUG1(FOR, "For: end for %d\n", forLevel); if (--forLevel <= 0) - return FALSE; + return false; } else if (IsFor(p)) { forLevel++; DEBUG1(FOR, "For: new loop %d\n", forLevel); } } Buf_AddStr(&accumFor->body, line); Buf_AddByte(&accumFor->body, '\n'); - return TRUE; + 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') /* just escape the $ */ return 0; if (var_start == '(') var_end = ')'; else if (var_start == '{') var_end = '}'; else return 1; /* Single char variable */ depth = 1; for (len = 1; (ch = var[len++]) != '\0';) { if (ch == var_start) depth++; else if (ch == var_end && --depth == 0) return len; } /* Variable end not found, escape the $ */ return 0; } /* * The .for loop substitutes the items as ${:U...}, which means * that characters that break this syntax must be backslash-escaped. */ -static Boolean +static bool NeedsEscapes(const char *value, char endc) { const char *p; for (p = value; *p != '\0'; p++) { if (*p == ':' || *p == '$' || *p == '\\' || *p == endc) - return TRUE; + return true; } - return FALSE; + return false; } /* * While expanding the body of a .for loop, write the item in the ${:U...} * expression, escaping characters as needed. * * The result is later unescaped by ApplyModifier_Defined. */ static void Buf_AddEscaped(Buffer *cmds, const char *item, char endc) { char ch; if (!NeedsEscapes(item, endc)) { Buf_AddStr(cmds, item); return; } /* Escape ':', '$', '\\' and 'endc' - 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 == endc) Buf_AddByte(cmds, '\\'); Buf_AddByte(cmds, ch); } } /* * While expanding the body of a .for loop, replace the variable name of an * expression like ${i} or ${i:...} or $(i) or $(i:...) with ":Uvalue". */ static void ForLoop_SubstVarLong(ForLoop *f, const char **pp, const char *bodyEnd, char endc, const char **inout_mark) { size_t i; const char *p = *pp; for (i = 0; i < f->vars.len; i++) { ForVar *forVar = Vector_Get(&f->vars, i); char *varname = forVar->name; size_t varnameLen = forVar->nameLen; if (varnameLen >= (size_t)(bodyEnd - p)) continue; if (memcmp(p, varname, varnameLen) != 0) continue; /* XXX: why test for backslash here? */ if (p[varnameLen] != ':' && p[varnameLen] != endc && p[varnameLen] != '\\') continue; /* * Found a variable match. Skip over the variable name and * instead add ':U' to the current body. */ Buf_AddBytesBetween(&f->curBody, *inout_mark, p); Buf_AddStr(&f->curBody, ":U"); Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], endc); p += varnameLen; *inout_mark = p; *pp = p; return; } } /* * While expanding the body of a .for loop, replace single-character * variable expressions like $i with their ${:U...} expansion. */ static void ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark) { const char ch = *p; ForVar *vars; size_t i; /* Skip $$ and stupid ones. */ if (!f->short_var || strchr("}):$", ch) != NULL) return; vars = Vector_Get(&f->vars, 0); for (i = 0; i < f->vars.len; i++) { const char *varname = vars[i].name; if (varname[0] == ch && varname[1] == '\0') goto found; } return; found: /* Replace $ with ${:U} */ Buf_AddBytesBetween(&f->curBody, *inout_mark, p), *inout_mark = p + 1; Buf_AddStr(&f->curBody, "{:U"); Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], '}'); Buf_AddByte(&f->curBody, '}'); } /* * Compute the body for the current iteration by copying the unexpanded body, * replacing the expressions for the iteration variables on the way. * * Using variable expressions ensures that the .for loop can't generate * syntax, and that the later parsing will still see a variable. * This code assumes that the variable with the empty name will never be * defined, see unit-tests/varname-empty.mk for more details. * * The detection of substitutions of the loop control variables is naive. * Many of the modifiers use '\' to escape '$' (not '$'), so it is possible * to contrive a makefile where an unwanted substitution happens. */ static void ForLoop_SubstBody(ForLoop *f) { const char *p, *bodyEnd; const char *mark; /* where the last replacement left off */ Buf_Empty(&f->curBody); mark = f->body.data; bodyEnd = f->body.data + f->body.len; for (p = mark; (p = strchr(p, '$')) != NULL;) { if (p[1] == '{' || p[1] == '(') { p += 2; ForLoop_SubstVarLong(f, &p, bodyEnd, p[-1] == '{' ? '}' : ')', &mark); } else if (p[1] != '\0') { ForLoop_SubstVarShort(f, p + 1, &mark); p += 2; } else break; } Buf_AddBytesBetween(&f->curBody, mark, bodyEnd); } /* * Compute the body for the current iteration by copying the unexpanded body, * replacing the expressions for the iteration variables on the way. */ static char * ForReadMore(void *v_arg, size_t *out_len) { ForLoop *f = v_arg; if (f->sub_next == f->items.len) { /* No more iterations */ ForLoop_Free(f); return NULL; } ForLoop_SubstBody(f); DEBUG1(FOR, "For: loop body:\n%s", f->curBody.data); f->sub_next += (unsigned int)f->vars.len; *out_len = f->curBody.len; return f->curBody.data; } /* Run the .for loop, imitating the actions of an include file. */ void For_Run(int lineno) { ForLoop *f = accumFor; accumFor = NULL; if (f->items.len == 0) { /* * Nothing to expand - possibly due to an earlier syntax * error. */ ForLoop_Free(f); return; } Parse_SetInput(NULL, lineno, -1, ForReadMore, f); } diff --git a/contrib/bmake/hash.c b/contrib/bmake/hash.c index 3afc4ac7ec4e..8b503ac31fb5 100644 --- a/contrib/bmake/hash.c +++ b/contrib/bmake/hash.c @@ -1,332 +1,374 @@ -/* $NetBSD: hash.c,v 1.61 2021/02/01 17:32:10 rillig Exp $ */ +/* $NetBSD: hash.c,v 1.64 2021/04/11 12:46:54 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.61 2021/02/01 17:32:10 rillig Exp $"); +MAKE_RCSID("$NetBSD: hash.c,v 1.64 2021/04/11 12:46:54 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) +Hash_String(const char *key, size_t *out_keylen) { unsigned int h; const char *p; h = 0; for (p = key; *p != '\0'; p++) h = 31 * h + (unsigned char)*p; if (out_keylen != NULL) *out_keylen = (size_t)(p - key); return h; } +/* This hash function matches Gosling's Emacs and java.lang.String. */ unsigned int -Hash_Hash(const char *key) +Hash_Substring(Substring key) { - return hash(key, NULL); + unsigned int h; + const char *p; + + h = 0; + for (p = key.start; p != key.end; p++) + h = 31 * h + (unsigned char)*p; + return h; } 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; } +static bool +HashEntry_KeyEquals(const HashEntry *he, Substring key) +{ + const char *heKey, *p; + + heKey = he->key; + for (p = key.start; p != key.end; p++, heKey++) + if (*p != *heKey || *heKey == '\0') + return false; + return *heKey == '\0'; +} + +static HashEntry * +HashTable_FindEntryBySubstring(HashTable *t, Substring key, unsigned int h) +{ + HashEntry *e; + unsigned int chainlen = 0; + +#ifdef DEBUG_HASH_LOOKUP + DEBUG4(HASH, "%s: %p h=%08x key=%.*s\n", __func__, t, h, + (int)Substring_Length(key), key.start); +#endif + + for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { + chainlen++; + if (e->key_hash == h && HashEntry_KeyEquals(e, key)) + 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); 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 free up the memory for the keys * of the hash table, but not for the values associated to these keys. */ 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); + unsigned int h = Hash_String(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) +HashTable_FindValueBySubstringHash(HashTable *t, Substring key, unsigned int h) { - HashEntry *he = HashTable_Find(t, h, key); + HashEntry *he = HashTable_FindEntryBySubstring(t, key, h); 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); 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); + __func__, (void *)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) +HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew) { size_t keylen; - unsigned int h = hash(key, &keylen); + unsigned int h = Hash_String(key, &keylen); HashEntry *he = HashTable_Find(t, h, key); if (he != NULL) { if (out_isNew != NULL) - *out_isNew = FALSE; + *out_isNew = false; return he; } if (t->numEntries >= rebuildLimit * t->bucketsSize) HashTable_Enlarge(t); 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; + *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); } diff --git a/contrib/bmake/hash.h b/contrib/bmake/hash.h index b101137aa0ce..8e7a567b6dba 100644 --- a/contrib/bmake/hash.h +++ b/contrib/bmake/hash.h @@ -1,170 +1,170 @@ -/* $NetBSD: hash.h,v 1.38 2020/12/15 01:23:55 rillig Exp $ */ +/* $NetBSD: hash.h,v 1.40 2021/04/11 12:46:54 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; /* A set of strings. */ typedef struct HashSet { HashTable tbl; } HashSet; MAKE_INLINE void * HashEntry_Get(HashEntry *h) { return h->value; } 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 *); +unsigned int Hash_Substring(Substring); +void *HashTable_FindValueBySubstringHash(HashTable *, Substring, unsigned int); +HashEntry *HashTable_CreateEntry(HashTable *, const char *, bool *); 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 *); MAKE_INLINE void HashSet_Init(HashSet *set) { HashTable_Init(&set->tbl); } MAKE_INLINE void HashSet_Done(HashSet *set) { HashTable_Done(&set->tbl); } -MAKE_INLINE Boolean +MAKE_INLINE bool HashSet_Add(HashSet *set, const char *key) { - Boolean isNew; + bool isNew; (void)HashTable_CreateEntry(&set->tbl, key, &isNew); return isNew; } -MAKE_INLINE Boolean +MAKE_INLINE bool HashSet_Contains(HashSet *set, const char *key) { return HashTable_FindEntry(&set->tbl, key) != NULL; } MAKE_INLINE void HashIter_InitSet(HashIter *hi, HashSet *set) { HashIter_Init(hi, &set->tbl); } #endif /* MAKE_HASH_H */ diff --git a/contrib/bmake/import.sh b/contrib/bmake/import.sh index d4554a078951..b80e120daab1 100755 --- a/contrib/bmake/import.sh +++ b/contrib/bmake/import.sh @@ -1,87 +1,92 @@ #!/bin/sh # Import bmake ECHO= GIT=${GIT:-git} # For consistency... Error() { echo ERROR: ${1+"$@"} >&2 exit 1 } Cd() { [ $# -eq 1 ] || Error "Cd() takes a single parameter." cd $1 || Error "cannot \"cd $1\" from $PWD" } # Call this function and then follow it by any specific import script additions option_parsing() { local _shift=$# # Parse command line options while : do case "$1" in *=*) eval "$1"; shift;; --) shift; break;; -a) TARBALL=$2; shift 2;; -n) ECHO=echo; shift;; -P) PR=$2; shift 2;; -r) REVIEWER=$2; shift 2;; -u) url=$2; shift 2;; -h) echo "Usage:"; echo " "$0 '[-ahnPr] [TARBALL=] [PR=] [REVIEWER=]' echo " "$0 '-a # (a)rchive' echo " "$0 '-h # print usage' echo " "$0 '-n # do not import, check only.' echo " "$0 '-P # Use PR' echo " "$0 '-r # (r)eviewed by' echo " "$0 'PR=' echo " "$0 'REVIEWER=' exit 1;; *) break;; esac done return $(($_shift - $#)) } ### option_parsing "$@" shift $? TF=/tmp/.$USER.$$ Cd `dirname $0` test -s ${TARBALL:-/dev/null} || Error need TARBALL here=`pwd` # thing should match what the TARBALL contains thing=`basename $here` case "$thing" in bmake) (cd .. && tar zxf $TARBALL);; *) Error "we should be in bmake";; esac VERSION=`grep '^_MAKE_VERSION' VERSION | sed 's,.*=[[:space:]]*,,'` rm -f *~ mkdir -p ../tmp +# new files are handled automatically +# but we need to rm if needed +# FILES are kept sorted so we can determine what was added and deleted +# but we need to take care dealing with re-ordering +(${GIT} diff FILES | sed -n '/^[+-][^+-]/p'; \ + ${GIT} diff mk/FILES | sed -n '/^[+-][^+-]/s,.,&mk/,p' ) > $TF.diffs +grep '^+' $TF.diffs | sed 's,^.,,' | sort > $TF.adds +grep '^-' $TF.diffs | sed 's,^.,,' | sort > $TF.rms +comm -13 $TF.adds $TF.rms > $TF.rm + if [ -z "$ECHO" ]; then - # new files are handled automatically - # but we need to rm if needed - $GIT diff FILES | sed -n '/^-[^-]/s,^-,,p' > $TF.rm test -s $TF.rm && xargs rm -f < $TF.rm $GIT add -A $GIT diff --staged | tee ../tmp/bmake-import.diff echo "$GIT tag -a vendor/NetBSD/bmake/$VERSION" > ../tmp/bmake-post.sh echo "After you commit, run $here/../tmp/bmake-post.sh" else - # FILES is kept sorted so we can determine what was added and deleted - $GIT diff FILES | sed -n '/^+[^+]/s,^+,,p' > $TF.add - $GIT diff FILES | sed -n '/^-[^-]/s,^-,,p' > $TF.rm + comm -23 $TF.adds $TF.rms > $TF.add test -s $TF.rm && { echo Removing:; cat $TF.rm; } test -s $TF.add && { echo Adding:; cat $TF.add; } $GIT diff fi diff --git a/contrib/bmake/job.c b/contrib/bmake/job.c index eb5454cde574..c27c47d0b054 100644 --- a/contrib/bmake/job.c +++ b/contrib/bmake/job.c @@ -1,3012 +1,3049 @@ -/* $NetBSD: job.c,v 1.420 2021/02/05 22:15:44 sjg Exp $ */ +/* $NetBSD: job.c,v 1.435 2021/06/16 09:47:51 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, * the .BEGIN target is made including all of its * dependencies 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 a special dependency line with target '.SHELL', * define the shell that is used for the creation * commands in jobs mode. * * 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. Do not handle * output or do anything for the jobs, just kill them. * 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.420 2021/02/05 22:15:44 sjg Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.435 2021/06/16 09:47:51 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, * errOn, errOff and runChkTmpl. * * In case a shell doesn't have error control, echoTmpl is a printf template * for echoing the command, should echoing be on; runIgnTmpl is another * printf template for executing the command while ignoring the return * status. Finally runChkTmpl 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 + * hasErrCtl is false, the command will be executed anyway as is, and if it * causes an error, so be it. Any templates set up to echo the command will * escape any '$ ` \ "' characters in the command string to avoid unwanted * shell code injection, the escaped command is safe to use in double quotes. * * 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; /* whether both echoOff and echoOn are there */ + bool hasEchoCtl; /* whether both echoOff and echoOn are there */ const char *echoOff; /* command to turn echoing off */ const char *echoOn; /* command to turn echoing back on */ const char *noPrint; /* text to skip when printing output from the * shell. This is usually the same as echoOff */ size_t noPrintLen; /* length of noPrint command */ - Boolean hasErrCtl; /* whether error checking can be controlled + bool hasErrCtl; /* whether error checking can be controlled * for individual commands */ const char *errOn; /* command to turn on error checking */ const char *errOff; /* command to turn off error checking */ const char *echoTmpl; /* template to echo a command */ const char *runIgnTmpl; /* template to run a command * without error checking */ const char *runChkTmpl; /* template to run a command * with error checking */ /* 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 */ const char *echoFlag; /* shell flag to echo commands */ const char *errFlag; /* shell flag to exit on error */ } Shell; typedef struct CommandFlags { /* Whether to echo the command before or instead of running it. */ - Boolean echo; + bool echo; /* Run the command even in -n or -N mode. */ - Boolean always; + bool always; /* - * true if we turned error checking off before printing the command - * and need to turn it back on + * true if we turned error checking off before writing the command to + * the commands file and need to turn it back on */ - Boolean ignerr; + bool ignerr; } CommandFlags; /* * Write shell commands to a file. * * TODO: keep track of whether commands are echoed. * TODO: keep track of whether error checking is active. */ typedef struct ShellWriter { FILE *f; /* we've sent 'set -x' */ - Boolean xtraced; + bool xtraced; } ShellWriter; /* * 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 job_errors = 0; /* number of errors reported */ -typedef enum AbortReason { /* why is the make aborting? */ +static enum { /* Why is the make aborting? */ ABORT_NONE, - ABORT_ERROR, /* Because of an error */ - ABORT_INTERRUPT, /* Because it was interrupted */ + ABORT_ERROR, /* Aborted because of an error */ + ABORT_INTERRUPT, /* Aborted because it was interrupted */ ABORT_WAIT /* Waiting for jobs to finish */ - /* XXX: "WAIT" is not a _reason_ for aborting, it's rather a status. */ -} AbortReason; -static AbortReason aborting = ABORT_NONE; +} 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; 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 */ + false, /* .hasEchoCtl */ "", /* .echoOff */ "", /* .echoOn */ "", /* .noPrint */ 0, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ + false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ "%s\n", /* .runIgnTmpl */ "{ %s \n} || exit $?\n", /* .runChkTmpl */ "'\n'", /* .newline */ '#', /* .commentChar */ "", /* .echoFlag */ "", /* .errFlag */ }, #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 */ + false, /* .hasEchoCtl */ "", /* .echoOff */ "", /* .echoOn */ "", /* .noPrint */ 0, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ + false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ "%s\n", /* .runIgnTmpl */ "{ %s \n} || exit $?\n", /* .runChkTmpl */ "'\n'", /* .newline */ '#', /* .commentChar*/ #if defined(MAKE_NATIVE) && defined(__NetBSD__) /* XXX: -q is not really echoFlag, it's more like noEchoInSysFlag. */ "q", /* .echoFlag */ #else "", /* .echoFlag */ #endif "", /* .errFlag */ }, /* * KSH description. */ { "ksh", /* .name */ - TRUE, /* .hasEchoCtl */ + true, /* .hasEchoCtl */ "set +v", /* .echoOff */ "set -v", /* .echoOn */ "set +v", /* .noPrint */ 6, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ + false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ "%s\n", /* .runIgnTmpl */ "{ %s \n} || exit $?\n", /* .runChkTmpl */ "'\n'", /* .newline */ '#', /* .commentChar */ "v", /* .echoFlag */ "", /* .errFlag */ }, /* * 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 */ + true, /* .hasEchoCtl */ "unset verbose", /* .echoOff */ "set verbose", /* .echoOn */ "unset verbose", /* .noPrint */ 13, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ + false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ "csh -c \"%s || exit 0\"\n", /* .runIgnTmpl */ "", /* .runChkTmpl */ "'\\\n'", /* .newline */ '#', /* .commentChar */ "v", /* .echoFlag */ "e", /* .errFlag */ } }; /* * This is the shell to which we pass all commands in the Makefile. * It is set by the Job_ParseShell function. */ static Shell *shell = &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 *shell_freeIt = NULL; /* Allocated memory for custom .SHELL */ 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 Boolean lurking_children = FALSE; -static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */ +static bool lurking_children = false; +static bool 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 **jobByFdIndex = NULL; static nfds_t fdsLen = 0; static void watchfd(Job *); static void clearfd(Job *); -static Boolean readyfd(Job *); +static bool readyfd(Job *); static char *targPrefix = NULL; /* To identify a job change in the output. */ 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 */ }; static sigset_t caught_signals; /* Set of signals we handle */ static volatile sig_atomic_t caught_sigchld; -static void JobDoOutput(Job *, Boolean); -static void JobInterrupt(Boolean, int) MAKE_ATTR_DEAD; +static void CollectOutput(Job *, bool); +static void JobInterrupt(bool, int) MAKE_ATTR_DEAD; static void JobRestartJobs(void); static void JobSigReset(void); static void SwitchOutputTo(GNode *gn) { /* The node for which output was most recently produced. */ static GNode *lastNode = NULL; if (gn == lastNode) return; lastNode = gn; if (opts.maxJobs != 1 && targPrefix != NULL && targPrefix[0] != '\0') (void)fprintf(stdout, "%s %s ---\n", targPrefix, gn->name); } static unsigned nfds_per_job(void) { #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) return 2; #endif return 1; } void Job_FlagsToString(const Job *job, char *buf, size_t bufsize) { snprintf(buf, bufsize, "%c%c%c", job->ignerr ? 'i' : '-', !job->echo ? 's' : '-', job->special ? 'S' : '-'); } static void -job_table_dump(const char *where) +DumpJobs(const char *where) { Job *job; char flags[4]; debug_printf("job table @ %s\n", where); for (job = job_table; job < job_table_end; job++) { Job_FlagsToString(job, flags, sizeof flags); debug_printf("job %d, status %d, flags %s, pid %d\n", (int)(job - job_table), job->status, 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->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(). */ /*ARGSUSED*/ static void JobChildSig(int signo MAKE_ATTR_UNUSED) { caught_sigchld = 1; while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN) continue; } /* Resume all stopped jobs. */ /*ARGSUSED*/ 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); + 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); + JobInterrupt(false, signo); } static void JobPassSig_suspend(int signo) { sigset_t nmask, omask; struct sigaction act; /* Suppress job started/continued messages */ - make_suspended = TRUE; + 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); DEBUG1(JOB, "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, JobStatus status, Boolean isJobs) +JobFindPid(int pid, JobStatus status, bool isJobs) { Job *job; for (job = job_table; job < job_table_end; job++) { if (job->status == status && job->pid == pid) return job; } if (DEBUG(JOB) && isJobs) - job_table_dump("no pid"); + DumpJobs("no pid"); return NULL; } /* Parse leading '@', '-' and '+', which control the exact execution mode. */ static void ParseCommandFlags(char **pp, CommandFlags *out_cmdFlags) { char *p = *pp; - out_cmdFlags->echo = TRUE; - out_cmdFlags->ignerr = FALSE; - out_cmdFlags->always = FALSE; + out_cmdFlags->echo = true; + out_cmdFlags->ignerr = false; + out_cmdFlags->always = false; for (;;) { if (*p == '@') out_cmdFlags->echo = DEBUG(LOUD); else if (*p == '-') - out_cmdFlags->ignerr = TRUE; + out_cmdFlags->ignerr = true; else if (*p == '+') - out_cmdFlags->always = TRUE; + out_cmdFlags->always = 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 -ShellWriter_PrintFmt(ShellWriter *wr, const char *fmt, const char *arg) +ShellWriter_WriteFmt(ShellWriter *wr, const char *fmt, const char *arg) { DEBUG1(JOB, fmt, arg); (void)fprintf(wr->f, fmt, arg); /* XXX: Is flushing needed in any case, or only if f == stdout? */ (void)fflush(wr->f); } static void -ShellWriter_Println(ShellWriter *wr, const char *line) +ShellWriter_WriteLine(ShellWriter *wr, const char *line) { - ShellWriter_PrintFmt(wr, "%s\n", line); + ShellWriter_WriteFmt(wr, "%s\n", line); } static void ShellWriter_EchoOff(ShellWriter *wr) { if (shell->hasEchoCtl) - ShellWriter_Println(wr, shell->echoOff); + ShellWriter_WriteLine(wr, shell->echoOff); } static void ShellWriter_EchoCmd(ShellWriter *wr, const char *escCmd) { - ShellWriter_PrintFmt(wr, shell->echoTmpl, escCmd); + ShellWriter_WriteFmt(wr, shell->echoTmpl, escCmd); } static void ShellWriter_EchoOn(ShellWriter *wr) { if (shell->hasEchoCtl) - ShellWriter_Println(wr, shell->echoOn); + ShellWriter_WriteLine(wr, shell->echoOn); } static void ShellWriter_TraceOn(ShellWriter *wr) { if (!wr->xtraced) { - ShellWriter_Println(wr, "set -x"); - wr->xtraced = TRUE; + ShellWriter_WriteLine(wr, "set -x"); + wr->xtraced = true; } } static void -ShellWriter_ErrOff(ShellWriter *wr, Boolean echo) +ShellWriter_ErrOff(ShellWriter *wr, bool echo) { if (echo) ShellWriter_EchoOff(wr); - ShellWriter_Println(wr, shell->errOff); + ShellWriter_WriteLine(wr, shell->errOff); if (echo) ShellWriter_EchoOn(wr); } static void -ShellWriter_ErrOn(ShellWriter *wr, Boolean echo) +ShellWriter_ErrOn(ShellWriter *wr, bool echo) { if (echo) ShellWriter_EchoOff(wr); - ShellWriter_Println(wr, shell->errOn); + ShellWriter_WriteLine(wr, shell->errOn); if (echo) ShellWriter_EchoOn(wr); } /* * The shell has no built-in error control, so emulate error control by * enclosing each shell command in a template like "{ %s \n } || exit $?" * (configurable per shell). */ static void -JobPrintSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, +JobWriteSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, const char *escCmd, const char **inout_cmdTemplate) { /* XXX: Why is the job modified at this point? */ - job->ignerr = TRUE; + job->ignerr = true; if (job->echo && inout_cmdFlags->echo) { ShellWriter_EchoOff(wr); ShellWriter_EchoCmd(wr, escCmd); /* * Leave echoing off so the user doesn't see the commands * for toggling the error checking. */ - inout_cmdFlags->echo = FALSE; + inout_cmdFlags->echo = false; } else { if (inout_cmdFlags->echo) ShellWriter_EchoCmd(wr, escCmd); } *inout_cmdTemplate = shell->runIgnTmpl; /* * The template runIgnTmpl already takes care of ignoring errors, * so pretend error checking is still on. * XXX: What effects does this have, and why is it necessary? */ - inout_cmdFlags->ignerr = FALSE; + inout_cmdFlags->ignerr = false; } static void -JobPrintSpecials(Job *job, ShellWriter *wr, const char *escCmd, Boolean run, +JobWriteSpecials(Job *job, ShellWriter *wr, const char *escCmd, bool run, CommandFlags *inout_cmdFlags, const char **inout_cmdTemplate) { if (!run) { /* * If there is no command to run, there is no need to switch * error checking off and on again for nothing. */ - inout_cmdFlags->ignerr = FALSE; + inout_cmdFlags->ignerr = false; } else if (shell->hasErrCtl) ShellWriter_ErrOff(wr, job->echo && inout_cmdFlags->echo); else if (shell->runIgnTmpl != NULL && shell->runIgnTmpl[0] != '\0') { - JobPrintSpecialsEchoCtl(job, wr, inout_cmdFlags, escCmd, + JobWriteSpecialsEchoCtl(job, wr, inout_cmdFlags, escCmd, inout_cmdTemplate); } else - inout_cmdFlags->ignerr = FALSE; + inout_cmdFlags->ignerr = false; } /* - * Put out another command for the given job. + * Write a shell command to the job's commands file, to be run later. * * If the command starts with '@' and neither the -s nor the -n flag was - * given to make, we stick a shell-specific echoOff command in the script. + * given to make, stick a shell-specific echoOff command in the script. * * If the command starts with '-' and the shell has no error control (none - * of the predefined shells has that), we ignore errors for the entire job. - * XXX: Why ignore errors for the entire job? - * XXX: Even ignore errors for the commands before this command? + * of the predefined shells has that), ignore errors for the entire job. * - * If the command is just "...", all further commands of this job are skipped - * for now. They are attached to the .END node and will be run by Job_Finish - * after all other targets have been made. + * XXX: Why ignore errors for the entire job? This is even documented in the + * manual page, but without any rationale since there is no known rationale. + * + * XXX: The manual page says the '-' "affects the entire job", but that's not + * accurate. The '-' does not affect the commands before the '-'. + * + * If the command is just "...", skip all further commands of this job. These + * commands are attached to the .END node instead and will be run by + * Job_Finish after all other targets have been made. */ static void -JobPrintCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) +JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) { - Boolean run; + bool run; CommandFlags cmdFlags; - /* Template for printing a command to the shell file */ + /* Template for writing a command to the shell file */ const char *cmdTemplate; char *xcmd; /* The expanded command */ char *xcmdStart; char *escCmd; /* xcmd escaped to be used in double quotes */ run = GNode_ShouldExecute(job->node); Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd); /* TODO: handle errors */ xcmdStart = xcmd; cmdTemplate = "%s\n"; ParseCommandFlags(&xcmd, &cmdFlags); /* The '+' command flag overrides the -n or -N options. */ if (cmdFlags.always && !run) { /* * We're not actually executing anything... * but this one needs to be - use compat mode just for it. */ Compat_RunCommand(ucmd, job->node, ln); free(xcmdStart); return; } /* * If the shell doesn't have error control, the alternate echoing * will be done (to avoid showing additional error checking code) * and this needs some characters escaped. */ escCmd = shell->hasErrCtl ? NULL : EscapeShellDblQuot(xcmd); if (!cmdFlags.echo) { if (job->echo && run && shell->hasEchoCtl) { ShellWriter_EchoOff(wr); } else { if (shell->hasErrCtl) - cmdFlags.echo = TRUE; + cmdFlags.echo = true; } } if (cmdFlags.ignerr) { - JobPrintSpecials(job, wr, escCmd, run, &cmdFlags, &cmdTemplate); + JobWriteSpecials(job, wr, escCmd, run, &cmdFlags, &cmdTemplate); } else { /* * If errors are being checked and the shell doesn't have * error control but does supply an runChkTmpl template, then * set up commands to run through it. */ if (!shell->hasErrCtl && shell->runChkTmpl != NULL && shell->runChkTmpl[0] != '\0') { if (job->echo && cmdFlags.echo) { ShellWriter_EchoOff(wr); ShellWriter_EchoCmd(wr, escCmd); - cmdFlags.echo = FALSE; + cmdFlags.echo = false; } /* * If it's a comment line or blank, avoid the possible * syntax error generated by "{\n} || exit $?". */ cmdTemplate = escCmd[0] == shell->commentChar || escCmd[0] == '\0' ? shell->runIgnTmpl : shell->runChkTmpl; - cmdFlags.ignerr = FALSE; + cmdFlags.ignerr = false; } } if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0) ShellWriter_TraceOn(wr); - ShellWriter_PrintFmt(wr, cmdTemplate, xcmd); + ShellWriter_WriteFmt(wr, cmdTemplate, xcmd); free(xcmdStart); free(escCmd); if (cmdFlags.ignerr) ShellWriter_ErrOn(wr, cmdFlags.echo && job->echo); if (!cmdFlags.echo) ShellWriter_EchoOn(wr); } /* - * Print all commands to the shell file that is later executed. + * Write all commands to the shell file that is later executed. * - * The special command "..." stops printing and saves the remaining commands + * The special command "..." stops writing and saves the remaining commands * to be executed later, when the target '.END' is made. * * Return whether at least one command was written to the shell file. */ -static Boolean -JobPrintCommands(Job *job) +static bool +JobWriteCommands(Job *job) { StringListNode *ln; - Boolean seen = FALSE; - ShellWriter wr = { job->cmdFILE, FALSE }; + bool seen = false; + ShellWriter wr; + + wr.f = job->cmdFILE; + wr.xtraced = false; 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; job->tailCmds = ln->next; break; } - JobPrintCommand(job, &wr, ln, ln->datum); - seen = TRUE; + JobWriteCommand(job, &wr, ln, ln->datum); + seen = true; } return seen; } /* * Save the delayed commands (those after '...'), to be executed later in * the '.END' node, when everything else is done. */ static void JobSaveCommands(Job *job) { StringListNode *ln; for (ln = job->tailCmds; ln != NULL; ln = ln->next) { const char *cmd = ln->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 JobClosePipes(Job *job) { clearfd(job); (void)close(job->outPipe); job->outPipe = -1; - JobDoOutput(job, TRUE); + CollectOutput(job, true); (void)close(job->inPipe); job->inPipe = -1; } +static void +DebugFailedJob(const Job *job) +{ + const StringListNode *ln; + + if (!DEBUG(ERROR)) + return; + + debug_printf("\n*** Failed target: %s\n*** Failed commands:\n", + job->node->name); + for (ln = job->node->commands.first; ln != NULL; ln = ln->next) + debug_printf("\t%s\n", (const char *)ln->datum); +} + static void JobFinishDoneExitedError(Job *job, WAIT_T *inout_status) { SwitchOutputTo(job->node); #ifdef USE_META if (useMeta) { meta_job_error(job, job->node, job->ignerr, WEXITSTATUS(*inout_status)); } #endif if (!shouldDieQuietly(job->node, -1)) { + DebugFailedJob(job); (void)printf("*** [%s] Error code %d%s\n", job->node->name, WEXITSTATUS(*inout_status), job->ignerr ? " (ignored)" : ""); } if (job->ignerr) WAIT_STATUS(*inout_status) = 0; else { if (deleteOnError) JobDeleteTarget(job->node); PrintOnError(job->node, NULL); } } static void JobFinishDoneExited(Job *job, WAIT_T *inout_status) { DEBUG2(JOB, "Process %d [%s] exited.\n", job->pid, job->node->name); if (WEXITSTATUS(*inout_status) != 0) JobFinishDoneExitedError(job, inout_status); else if (DEBUG(JOB)) { SwitchOutputTo(job->node); (void)printf("*** [%s] Completed successfully\n", job->node->name); } } static void JobFinishDoneSignaled(Job *job, WAIT_T status) { SwitchOutputTo(job->node); + DebugFailedJob(job); (void)printf("*** [%s] Signal %d\n", job->node->name, WTERMSIG(status)); if (deleteOnError) JobDeleteTarget(job->node); } static void JobFinishDone(Job *job, WAIT_T *inout_status) { if (WIFEXITED(*inout_status)) JobFinishDoneExited(job, inout_status); else JobFinishDoneSignaled(job, *inout_status); (void)fflush(stdout); } /* * 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 (job_errors != 0; not an ignored one), no more * jobs will be started. * * Input: * job job to finish * status sub-why job went away */ static void JobFinish (Job *job, WAIT_T status) { - Boolean done, return_job_token; + bool 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->ignerr))) || WIFSIGNALED(status)) { /* Finished because of an error. */ JobClosePipes(job); if (job->cmdFILE != NULL && job->cmdFILE != stdout) { (void)fclose(job->cmdFILE); job->cmdFILE = NULL; } - done = TRUE; + 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 to run * the next command. */ done = WEXITSTATUS(status) != 0; JobClosePipes(job); } else { /* No need to close things down or anything. */ - done = FALSE; + done = false; } if (done) JobFinishDone(job, &status); #ifdef USE_META if (useMeta) { int meta_status = meta_job_finish(job); if (meta_status != 0 && status == 0) status = meta_status; } #endif - return_job_token = FALSE; + return_job_token = false; Trace_Log(JOBEND, job); if (!job->special) { if (WAIT_STATUS(status) != 0 || (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT) - return_job_token = TRUE; + return_job_token = true; } if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT && (WAIT_STATUS(status) == 0)) { /* * As long as we aren't aborting and the job didn't return a * non-zero status that we shouldn't ignore, we call * Make_Update to update the parents. */ JobSaveCommands(job); job->node->made = MADE; if (!job->special) - return_job_token = TRUE; + return_job_token = true; Make_Update(job->node); job->status = JOB_ST_FREE; } else if (status != 0) { job_errors++; job->status = JOB_ST_FREE; } if (job_errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT) { /* Prevent more jobs from getting started. */ aborting = ABORT_ERROR; } if (return_job_token) Job_TokenReturn(); if (aborting == ABORT_ERROR && jobTokensRunning == 0) Finish(job_errors); } static void TouchRegular(GNode *gn) { const char *file = GNode_Path(gn); - struct utimbuf times = { now, now }; + struct utimbuf times; int fd; char c; + times.actime = now; + times.modtime = now; 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 echo) +Job_Touch(GNode *gn, bool echo) { if (gn->type & (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC | OP_OPTIONAL | OP_SPECIAL | OP_PHONY)) { /* * These are "virtual" targets and should not really be * created. */ return; } if (echo || !GNode_ShouldExecute(gn)) { (void)fprintf(stdout, "touch %s\n", gn->name); (void)fflush(stdout); } if (!GNode_ShouldExecute(gn)) return; if (gn->type & OP_ARCHV) Arch_Touch(gn); else if (gn->type & OP_LIB) Arch_TouchLib(gn); else 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. + * true if the commands list is/was ok. */ -Boolean +bool Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) { if (GNode_IsTarget(gn)) - return TRUE; + return true; if (!Lst_IsEmpty(&gn->commands)) - return TRUE; + return true; if ((gn->type & OP_LIB) && !Lst_IsEmpty(&gn->children)) - return TRUE; + return true; /* * No commands. Look for .DEFAULT rule from which we might infer * commands. */ if (defaultNode != NULL && !Lst_IsEmpty(&defaultNode->commands) && !(gn->type & OP_SPECIAL)) { /* * 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(defaultNode, gn); Var_Set(gn, IMPSRC, GNode_VarTarget(gn)); - return TRUE; + return true; } - Dir_UpdateMTime(gn, FALSE); + Dir_UpdateMTime(gn, false); if (gn->mtime != 0 || (gn->type & OP_SPECIAL)) - return TRUE; + 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; + 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; + 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; + return false; } abortProc("%s: don't know how to make %s. Stop", progname, gn->name); - return FALSE; + return false; } /* * Execute the shell for the given job. * * 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; if (DEBUG(JOB)) { int i; 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 (job->echo) SwitchOutputTo(job->node); /* No interruptions until this job is on the `jobs' list */ JobSigLock(&mask); /* Pre-emptively mark job running, pid still zero though */ job->status = JOB_ST_RUNNING; Var_ReexportVars(); 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, 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"); } /* * 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 (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 that 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"); + DumpJobs("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 ((shell->errFlag != NULL && shell->errFlag[0] != '-') || (shell->echoFlag != NULL && shell->echoFlag[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. * * TODO: Research until when the above comments were * practically relevant. */ (void)snprintf(args, sizeof args, "-%s%s", (job->ignerr ? "" : (shell->errFlag != NULL ? shell->errFlag : "")), (!job->echo ? "" : (shell->echoFlag != NULL ? shell->echoFlag : ""))); if (args[1] != '\0') { argv[argc] = args; argc++; } } else { if (!job->ignerr && shell->errFlag != NULL) { argv[argc] = UNCONST(shell->errFlag); argc++; } if (job->echo && shell->echoFlag != NULL) { argv[argc] = UNCONST(shell->echoFlag); argc++; } } argv[argc] = NULL; } static void -JobWriteShellCommands(Job *job, GNode *gn, Boolean cmdsOK, Boolean *out_run) +JobWriteShellCommands(Job *job, GNode *gn, bool *out_run) { /* * 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[MAXPATHLEN]; int tfd; /* File descriptor to the temp file */ - /* - * We're serious here, but if the commands were bogus, we're - * also dead... - */ - if (!cmdsOK) { - PrintOnError(gn, NULL); /* provide some clue */ - DieHorribly(); - } - tfd = Job_TempFile(TMPPAT, tfile, sizeof tfile); job->cmdFILE = fdopen(tfd, "w+"); if (job->cmdFILE == NULL) Punt("Could not fdopen %s", tfile); (void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC); #ifdef USE_META if (useMeta) { meta_job_start(job, gn); if (gn->type & OP_SILENT) /* might have changed */ - job->echo = FALSE; + job->echo = false; } #endif - *out_run = JobPrintCommands(job); + *out_run = JobWriteCommands(job); } /* - * 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. - * previous The previous Job structure for this node, if any. + * Start a target-creation process going for the target described by gn. * * 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: + * Details: * A new Job node is created and added to the list of running * jobs. PMake is forked and a child shell created. * * NB: The return value is ignored by everyone. */ static JobStartResult -JobStart(GNode *gn, Boolean special) +JobStart(GNode *gn, bool special) { Job *job; /* new job descriptor */ char *argv[10]; /* Argument vector to shell */ - Boolean cmdsOK; /* true if the nodes commands were all right */ - Boolean run; + bool cmdsOK; /* true if the nodes commands were all right */ + bool run; for (job = job_table; job < job_table_end; job++) { if (job->status == JOB_ST_FREE) break; } if (job >= job_table_end) Punt("JobStart no job slots vacant"); memset(job, 0, sizeof *job); job->node = gn; job->tailCmds = NULL; job->status = JOB_ST_SET_UP; job->special = special || gn->type & OP_SPECIAL; job->ignerr = opts.ignoreErrors || gn->type & OP_IGNORE; job->echo = !(opts.beSilent || gn->type & OP_SILENT); /* * 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 (Lst_IsEmpty(&gn->commands)) { job->cmdFILE = stdout; - run = FALSE; + run = false; + + /* + * We're serious here, but if the commands were bogus, we're + * also dead... + */ + if (!cmdsOK) { + PrintOnError(gn, NULL); /* provide some clue */ + DieHorribly(); + } } else if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) || (!opts.noExecute && !opts.touchFlag)) { /* * The above condition looks very similar to * GNode_ShouldExecute but is subtly different. It prevents * that .MAKE targets are touched since these are usually * virtual targets. */ - JobWriteShellCommands(job, gn, cmdsOK, &run); + /* + * We're serious here, but if the commands were bogus, we're + * also dead... + */ + if (!cmdsOK) { + PrintOnError(gn, NULL); /* provide some clue */ + DieHorribly(); + } + + JobWriteShellCommands(job, gn, &run); (void)fflush(job->cmdFILE); } else if (!GNode_ShouldExecute(gn)) { /* - * Just print all the commands to stdout in one fell swoop. + * Just write all the commands to stdout in one fell swoop. * This still sets up job->tailCmds correctly. */ SwitchOutputTo(gn); job->cmdFILE = stdout; if (cmdsOK) - JobPrintCommands(job); - run = FALSE; + JobWriteCommands(job); + run = false; (void)fflush(job->cmdFILE); } else { Job_Touch(gn, job->echo); - run = FALSE; + run = false; } /* If we're not supposed to execute a shell, don't. */ if (!run) { if (!job->special) Job_TokenReturn(); /* Unlink and close the command file if we opened one */ 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->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 text of the - * shell, if any. The default shell does not have noPrint though, which means - * that in all practical cases, handling the output is left to the caller. + * If the shell has an output filter (which only csh and ksh have by default), + * print the output of the child process, skipping the noPrint text of the + * shell. + * + * Return the part of the output that the calling function needs to output by + * itself. */ static char * -JobOutput(char *cp, char *endp) /* XXX: should all be const */ +PrintFilteredOutput(char *cp, char *endp) /* XXX: should all be const */ { char *ecp; /* XXX: should be const */ if (shell->noPrint == NULL || shell->noPrint[0] == '\0') return cp; /* * XXX: What happens if shell->noPrint occurs on the boundary of * the buffer? To work correctly in all cases, this should rather * be a proper stream filter instead of doing string matching on * selected chunks of the output. */ while ((ecp = strstr(cp, shell->noPrint)) != NULL) { if (ecp != cp) { *ecp = '\0'; /* XXX: avoid writing to the buffer */ /* * The only way there wouldn't be a newline after * this line is if it were the last in the buffer. * however, since the noPrint output comes after it, * there must be a newline, so we don't print one. */ /* XXX: What about null bytes in the output? */ (void)fprintf(stdout, "%s", cp); (void)fflush(stdout); } cp = ecp + shell->noPrintLen; if (cp == endp) break; cp++; /* skip over the (XXX: assumed) newline */ pp_skip_whitespace(&cp); } return cp; } /* * 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 + * finish true if this is the last time we'll be called * for this job */ static void -JobDoOutput(Job *job, Boolean finish) +CollectOutput(Job *job, bool finish) { - Boolean gotNL; /* true if got a newline */ - Boolean fbuf; /* true if our buffer filled up */ + bool gotNL; /* true if got a newline */ + bool 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. */ again: - gotNL = FALSE; - fbuf = FALSE; + gotNL = false; + fbuf = false; nRead = read(job->inPipe, &job->outBuf[job->curPos], JOB_BUFSIZE - job->curPos); if (nRead < 0) { if (errno == EAGAIN) return; if (DEBUG(JOB)) { - perror("JobDoOutput(piperead)"); + perror("CollectOutput(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) { job->outBuf[job->curPos] = '\n'; nr = 1; - finish = FALSE; + finish = false; } else if (nr == 0) { - finish = FALSE; + 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. + * 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; + gotNL = true; break; } else if (job->outBuf[i] == '\0') { /* - * Why? + * FIXME: The null characters are only replaced with + * space _after_ the last '\n'. Everywhere else they + * hide the rest of the command output. */ 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; + 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->outBuf, &job->outBuf[i]); + /* + * FIXME: SwitchOutputTo should be here, according to + * the comment above. But since PrintOutput does not + * do anything in the default shell, this bug has gone + * unnoticed until now. + */ + cp = PrintFilteredOutput(job->outBuf, &job->outBuf[i]); /* - * There's still more in that thar buffer. This time, + * There's still more in the output 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) SwitchOutputTo(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 + * we do get an EOF, finish will be set false and we'll fall * through and out. */ 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. */ GNodeList lst = LST_INIT; Lst_Append(&lst, targ); (void)Make_Run(&lst); Lst_Done(&lst); - JobStart(targ, TRUE); + JobStart(targ, true); while (jobTokensRunning != 0) { Job_CatchOutput(); } #else Compat_Make(targ, targ); /* XXX: Replace with GNode_IsError(gn) */ 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; /* Have we received SIGCHLD since last call? */ if (caught_sigchld == 0) return; caught_sigchld = 0; 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); + 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) +JobReapChild(pid_t pid, WAIT_T status, bool 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->suspended = TRUE; + job->suspended = true; } (void)fflush(stdout); return; } 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, fdsLen - 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); if (count == 1) { if (token == DO_JOB_RESUME[0]) /* * Complete relay requested from our SIGCONT * handler */ JobRestartJobs(); } else if (count == 0) Punt("unexpected eof on token pipe"); else Punt("token pipe read: %s", strerror(errno)); nready--; } Job_CatchChildren(); if (nready == 0) return; for (i = npseudojobs * nfds_per_job(); i < fdsLen; i++) { if (fds[i].revents == 0) continue; job = jobByFdIndex[i]; if (job->status == JOB_ST_RUNNING) - JobDoOutput(job, FALSE); + CollectOutput(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, FALSE); + (void)JobStart(gn, false); } static void InitShellNameAndPath(void) { shellName = shell->name; #ifdef DEFSHELL_CUSTOM if (shellName[0] == '/') { shellPath = shellName; shellName = str_basename(shellPath); return; } #endif shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName); } void Shell_Init(void) { if (shellPath == NULL) InitShellNameAndPath(); Var_SetWithFlags(SCOPE_CMDLINE, ".SHELL", shellPath, VAR_SET_READONLY); if (shell->errFlag == NULL) shell->errFlag = ""; if (shell->echoFlag == NULL) shell->echoFlag = ""; if (shell->hasErrCtl && shell->errFlag[0] != '\0') { if (shellErrFlag != NULL && strcmp(shell->errFlag, &shellErrFlag[1]) != 0) { free(shellErrFlag); shellErrFlag = NULL; } if (shellErrFlag == NULL) { size_t n = strlen(shell->errFlag) + 2; shellErrFlag = bmake_malloc(n); if (shellErrFlag != NULL) snprintf(shellErrFlag, n, "-%s", shell->errFlag); } } else if (shellErrFlag != NULL) { 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 shell->newline; } void Job_SetPrefix(void) { if (targPrefix != NULL) { free(targPrefix); } else if (!Var_Exists(SCOPE_GLOBAL, MAKE_JOB_PREFIX)) { Global_Set(MAKE_JOB_PREFIX, "---"); } (void)Var_Subst("${" MAKE_JOB_PREFIX "}", SCOPE_GLOBAL, VARE_WANTRES, &targPrefix); /* TODO: handle errors */ } static void AddSig(int sig, SignalProc handler) { if (bmake_signal(sig, SIG_IGN) != SIG_IGN) { sigaddset(&caught_signals, sig); (void)bmake_signal(sig, handler); } } /* 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; caught_sigchld = 0; aborting = ABORT_NONE; job_errors = 0; Always_pass_job_queue = GetBooleanVar(MAKE_ALWAYS_PASS_JOB_QUEUE, Always_pass_job_queue); 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 = TRUE; + lurking_children = true; break; } Shell_Init(); JobCreatePipe(&childExitJob, 3); { /* Preallocate enough for the maximum number of jobs. */ size_t nfds = (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job(); fds = bmake_malloc(sizeof *fds * nfds); jobByFdIndex = bmake_malloc(sizeof *jobByFdIndex * nfds); } /* 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); /* * 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); (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 DelSig(int sig) { if (sigismember(&caught_signals, sig) != 0) (void)bmake_signal(sig, SIG_DFL); } static void JobSigReset(void) { DelSig(SIGINT); DelSig(SIGHUP); DelSig(SIGQUIT); DelSig(SIGTERM); DelSig(SIGTSTP); DelSig(SIGTTOU); DelSig(SIGTTIN); DelSig(SIGWINCH); DelSig(SIGCONT); (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; } /* * Parse a shell specification and set up 'shell', shellPath and * shellName appropriately. * * Input: * line The shell spec * * Results: - * FALSE if the specification was incorrect. + * false if the specification was incorrect. * * Side Effects: * 'shell' points to a Shell structure (either predefined or * created from the shell spec), shellPath is the full path of the * shell described by 'shell', 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 + * is true or template of command to echo a command * for which error checking is off if hasErrCtl is - * FALSE. + * false. * ignore Command to turn off error checking if hasErrCtl - * is TRUE or template of command to execute a + * is true or template of command to execute a * command so as to ignore any errors it returns if - * hasErrCtl is FALSE. + * hasErrCtl is false. */ -Boolean +bool Job_ParseShell(char *line) { Words wordsList; char **words; char **argv; size_t argc; char *path; Shell newShell; - Boolean fullSpec = FALSE; + bool fullSpec = false; Shell *sh; /* XXX: don't use line as an iterator variable */ pp_skip_whitespace(&line); free(shell_freeIt); memset(&newShell, 0, sizeof newShell); /* * Parse the specification by keyword */ - wordsList = Str_Words(line, TRUE); + 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; + return false; } shell_freeIt = 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.echoFlag = arg + 9; } else if (strncmp(arg, "errFlag=", 8) == 0) { newShell.errFlag = 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) { /* Before 2020-12-10, these two variables * had been a single variable. */ newShell.errOn = arg + 6; newShell.echoTmpl = arg + 6; } else if (strncmp(arg, "ignore=", 7) == 0) { /* Before 2020-12-10, these two variables * had been a single variable. */ newShell.errOff = arg + 7; newShell.runIgnTmpl = arg + 7; } else if (strncmp(arg, "errout=", 7) == 0) { newShell.runChkTmpl = 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; + return false; } - fullSpec = TRUE; + 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; + return false; } else { if ((sh = FindShellByName(newShell.name)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", newShell.name); free(words); - return FALSE; + return false; } shell = sh; shellName = newShell.name; if (shellPath != NULL) { /* * 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 + * (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; + return false; } shell = sh; } else { shell = bmake_malloc(sizeof *shell); *shell = newShell; } /* this will take care of shellErrFlag */ Shell_Init(); } if (shell->echoOn != NULL && shell->echoOff != NULL) - shell->hasEchoCtl = TRUE; + shell->hasEchoCtl = true; if (!shell->hasErrCtl) { if (shell->echoTmpl == NULL) shell->echoTmpl = ""; if (shell->runIgnTmpl == NULL) shell->runIgnTmpl = "%s\n"; } /* * Do not free up the words themselves, since they might be in use * by the shell specification. */ free(words); - return TRUE; + 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(Boolean runINTERRUPT, int signo) +JobInterrupt(bool 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->status != JOB_ST_RUNNING) continue; gn = job->node; JobDeleteTarget(gn); if (job->pid != 0) { 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; + opts.ignoreErrors = false; JobRun(interrupt); } } Trace_Log(MAKEINTR, NULL); exit(signo); /* XXX: why 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 (job_errors != 0) { Error("Errors reported so .END ignored"); } else { JobRun(endNode); } } return job_errors; } /* Clean up any memory used by the jobs module. */ void Job_End(void) { #ifdef CLEANUP free(shell_freeIt); #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 != 0) { for (job = job_table; job < job_table_end; job++) { 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->status == JOB_ST_RUNNING && (make_suspended || job->suspended)) { DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid); if (job->suspended) { (void)printf("*** [%s] Continued\n", job->node->name); (void)fflush(stdout); } - job->suspended = FALSE; + job->suspended = false; if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { debug_printf("Failed to send SIGCONT to %d\n", job->pid); } } if (job->status == JOB_ST_FINISHED) { /* * Job exit deferred after calling waitpid() in a * signal handler */ JobFinish(job, job->exit_status); } } - make_suspended = FALSE; + make_suspended = false; } static void watchfd(Job *job) { if (job->inPollfd != NULL) Punt("Watching watched job"); fds[fdsLen].fd = job->inPipe; fds[fdsLen].events = POLLIN; jobByFdIndex[fdsLen] = job; job->inPollfd = &fds[fdsLen]; fdsLen++; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) { fds[fdsLen].fd = meta_job_fd(job); fds[fdsLen].events = fds[fdsLen].fd == -1 ? 0 : POLLIN; jobByFdIndex[fdsLen] = job; fdsLen++; } #endif } static void clearfd(Job *job) { size_t i; if (job->inPollfd == NULL) Punt("Unwatching unwatched job"); i = (size_t)(job->inPollfd - fds); fdsLen--; #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 != 0) Punt("odd-numbered fd with meta"); fdsLen--; } #endif /* * Move last job in table into hole made by dead job. */ if (fdsLen != i) { fds[i] = fds[fdsLen]; jobByFdIndex[i] = jobByFdIndex[fdsLen]; jobByFdIndex[i]->inPollfd = &fds[i]; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) { fds[i + 1] = fds[fdsLen + 1]; jobByFdIndex[i + 1] = jobByFdIndex[fdsLen + 1]; } #endif } job->inPollfd = NULL; } -static Boolean +static bool 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; } /* Get a temp file */ int Job_TempFile(const char *pattern, char *tfile, size_t tfile_sz) { int fd; sigset_t mask; JobSigLock(&mask); fd = mkTempFile(pattern, tfile, tfile_sz); if (tfile != NULL && !DEBUG(SCRIPT)) unlink(tfile); JobSigUnlock(&mask); return fd; } /* 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", tokenWaitJob.inPipe, tokenWaitJob.outPipe); Global_Append(MAKEFLAGS, "-J"); Global_Append(MAKEFLAGS, jobarg); /* * 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 != 0 || 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 + * Returns true if a token was withdrawn, and false if the pool is currently * empty. */ -Boolean +bool 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; + 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()); wantToken = 1; - return FALSE; + return false; } if (count == 1 && tok != '+') { /* make being aborted - 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 (shouldDieQuietly(NULL, 1)) exit(6); /* we aborted */ 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; + 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 +bool Job_RunTarget(const char *target, const char *fname) { GNode *gn = Targ_FindNode(target); if (gn == NULL) - return FALSE; + return false; if (fname != NULL) Var_Set(gn, ALLSRC, fname); JobRun(gn); /* XXX: Replace with GNode_IsError(gn) */ if (gn->made == ERROR) { PrintOnError(gn, "\n\nStop."); exit(1); } - return TRUE; + 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, 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 */ diff --git a/contrib/bmake/job.h b/contrib/bmake/job.h index 0d7d7313b9d4..ef66602518d7 100644 --- a/contrib/bmake/job.h +++ b/contrib/bmake/job.h @@ -1,210 +1,210 @@ -/* $NetBSD: job.h,v 1.72 2021/02/05 19:19:17 sjg Exp $ */ +/* $NetBSD: job.h,v 1.73 2021/04/03 11:08:40 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 JobStatus { JOB_ST_FREE = 0, /* Job is available */ 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) */ } JobStatus; /* * 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; /* This is where the shell commands go. */ FILE *cmdFILE; int exit_status; /* from wait4() in signal handler */ JobStatus status; - Boolean suspended; + bool suspended; /* Ignore non-zero exits */ - Boolean ignerr; + bool ignerr; /* Output the command before or instead of running it. */ - Boolean echo; + bool echo; /* Target is a special one. */ - Boolean special; + bool special; 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_Touch(GNode *, bool); +bool 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 *); +bool 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); +bool Job_TokenWithdraw(void); void Job_ServerStart(int, int, int); void Job_SetPrefix(void); -Boolean Job_RunTarget(const char *, const char *); +bool Job_RunTarget(const char *, const char *); void Job_FlagsToString(const Job *, char *, size_t); int Job_TempFile(const char *, char *, size_t); #endif /* MAKE_JOB_H */ diff --git a/contrib/bmake/lst.c b/contrib/bmake/lst.c index 8f3c32ef72e6..372973112783 100644 --- a/contrib/bmake/lst.c +++ b/contrib/bmake/lst.c @@ -1,288 +1,292 @@ -/* $NetBSD: lst.c,v 1.104 2021/02/01 19:39:31 rillig Exp $ */ +/* $NetBSD: lst.c,v 1.105 2021/03/15 16:45:30 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.104 2021/02/01 19:39:31 rillig Exp $"); +MAKE_RCSID("$NetBSD: lst.c,v 1.105 2021/03/15 16:45:30 rillig Exp $"); static ListNode * LstNodeNew(ListNode *prev, ListNode *next, void *datum) { 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); Lst_Init(list); return list; } void Lst_Done(List *list) { ListNode *ln, *next; for (ln = list->first; ln != NULL; ln = next) { next = ln->next; free(ln); } } void Lst_DoneCall(List *list, LstFreeProc freeProc) { ListNode *ln, *next; for (ln = list->first; ln != NULL; ln = next) { next = ln->next; freeProc(ln->datum); free(ln); } } /* Free a list and all its nodes. The node data are not freed though. */ void Lst_Free(List *list) { Lst_Done(list); free(list); } /* Insert a new node with the datum before the given node. */ void Lst_InsertBefore(List *list, ListNode *ln, void *datum) { ListNode *newNode; assert(datum != NULL); newNode = LstNodeNew(ln->prev, ln, datum); if (ln->prev != NULL) ln->prev->next = newNode; ln->prev = newNode; 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 *ln; assert(datum != NULL); ln = LstNodeNew(NULL, list->first, datum); if (list->first == NULL) { list->first = ln; list->last = ln; } else { 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 *ln; assert(datum != NULL); ln = LstNodeNew(list->last, NULL, datum); if (list->last == NULL) { list->first = ln; list->last = ln; } else { 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 *ln) { /* unlink it from its neighbors */ 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 == 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 *ln, void *datum) { assert(datum != NULL); ln->datum = datum; } /* * Replace the datum in the given node with NULL. * Having NULL values in a list is unusual though. */ void LstNode_SetNull(ListNode *ln) { ln->datum = 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 *ln; assert(datum != NULL); for (ln = list->first; ln != NULL; ln = ln->next) if (ln->datum == datum) return ln; return NULL; } /* * Move all nodes from src to the end of dst. - * The source list becomes empty but is not freed. + * The source list becomes indeterminate. */ void Lst_MoveAll(List *dst, List *src) { if (src->first != NULL) { src->first->prev = dst->last; if (dst->last != NULL) dst->last->next = src->first; else dst->first = src->first; dst->last = src->last; } +#ifdef CLEANUP + src->first = NULL; + src->last = NULL; +#endif } /* Copy the element data from src to the start of dst. */ void Lst_PrependAll(List *dst, List *src) { ListNode *ln; for (ln = src->last; ln != NULL; ln = ln->prev) Lst_Prepend(dst, ln->datum); } /* Copy the element data from src to the end of dst. */ void Lst_AppendAll(List *dst, List *src) { ListNode *ln; for (ln = src->first; ln != NULL; ln = ln->next) Lst_Append(dst, ln->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->cap = 10; v->itemSize = itemSize; v->items = bmake_malloc(v->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->cap) { v->cap *= 2; v->items = bmake_realloc(v->items, v->cap * v->itemSize); } v->len++; return Vector_Get(v, v->len - 1); } /* * Remove the last item from the vector, return the pointer to it. * 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); } diff --git a/contrib/bmake/lst.h b/contrib/bmake/lst.h index fe854a7647a8..cddd6439f611 100644 --- a/contrib/bmake/lst.h +++ b/contrib/bmake/lst.h @@ -1,203 +1,205 @@ -/* $NetBSD: lst.h,v 1.96 2021/02/01 18:55:15 rillig Exp $ */ +/* $NetBSD: lst.h,v 1.98 2021/04/03 11:08:40 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 #ifdef HAVE_INTTYPES_H #include #elif defined(HAVE_STDINT_H) #include #endif #ifdef HAVE_STDLIB_H #include #endif /* 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 */ void *datum; /* datum associated with this element */ }; struct List { ListNode *first; ListNode *last; }; /* Free the datum of a node, called before freeing the node itself. */ typedef void LstFreeProc(void *); /* Create or destroy a list */ /* Create a new list. */ List *Lst_New(void); /* Free the list nodes, but not the list itself. */ void Lst_Done(List *); /* Free the list nodes, freeing the node data using the given function. */ void Lst_DoneCall(List *, LstFreeProc); /* Free the list, leaving the node data unmodified. */ void Lst_Free(List *); #define LST_INIT { NULL, NULL } /* Initialize a list, without memory allocation. */ MAKE_INLINE void Lst_Init(List *list) { - list->first = NULL; - list->last = NULL; + list->first = NULL; + list->last = NULL; } /* Get information about a list */ -MAKE_INLINE Boolean +MAKE_INLINE bool Lst_IsEmpty(List *list) -{ return list->first == NULL; } +{ + 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 *); /* Using the list as a queue */ /* Add a datum at the tail of the queue. */ MAKE_INLINE void Lst_Enqueue(List *list, void *datum) { Lst_Append(list, datum); } /* 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 */ size_t len; /* number of actually usable elements */ size_t 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. */ 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 *); MAKE_INLINE void Vector_Done(Vector *v) { free(v->items); } #endif /* MAKE_LST_H */ diff --git a/contrib/bmake/main.c b/contrib/bmake/main.c index d25bb00d3716..85a8a1cce7a1 100644 --- a/contrib/bmake/main.c +++ b/contrib/bmake/main.c @@ -1,2289 +1,2289 @@ -/* $NetBSD: main.c,v 1.533 2021/02/05 19:19:17 sjg Exp $ */ +/* $NetBSD: main.c,v 1.540 2021/06/18 12:54:17 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. */ /* * The main file for this entire program. Exit routines etc. reside here. * * Utility functions defined in this file: * * Main_ParseArgLine * Parse and process command line arguments from a * single string. Used to implement the special targets * .MFLAGS and .MAKEFLAGS. * * Error Print a tagged error message. * * 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 * that 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.533 2021/02/05 19:19:17 sjg Exp $"); +MAKE_RCSID("$NetBSD: main.c,v 1.540 2021/06/18 12:54:17 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 __arraycount # define __arraycount(__x) (sizeof(__x) / sizeof(__x[0])) #endif CmdOpts opts; 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 */ +bool allPrecious; /* .PRECIOUS given on line by itself */ +bool deleteOnError; /* .DELETE_ON_ERROR: set */ static int maxJobTokens; /* -j argument */ -Boolean enterFlagObj; /* -w and objdir != srcdir */ +bool enterFlagObj; /* -w and objdir != srcdir */ 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 */ +bool doing_depend; /* Set while reading .depend */ +static bool 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 bool ignorePWD; /* if we use -C, PWD is meaningless */ static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ char curdir[MAXPATHLEN + 1]; /* Startup directory */ const char *progname; char *makeDependfile; pid_t myPid; int makelevel; -Boolean forceJobs = FALSE; +bool forceJobs = false; static int main_errors = 0; static HashTable cached_realpaths; /* * For compatibility with the POSIX version of MAKEFLAGS that includes * 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; if (flags == NULL) return NULL; for (f = flags; *f != '\0'; f++) if (!ch_isalpha(*f)) break; if (*f != '\0') return bmake_strdup(flags); len = strlen(flags); st = nf = bmake_malloc(len * 3 + 1); while (*flags != '\0') { *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 MainParseArgDebugFile(const char *arg) { const char *mode; size_t len; char *fname; if (opts.debug_file != stdout && opts.debug_file != stderr) fclose(opts.debug_file); if (*arg == '+') { arg++; mode = "a"; } else mode = "w"; if (strcmp(arg, "stdout") == 0) { opts.debug_file = stdout; return; } if (strcmp(arg, "stderr") == 0) { opts.debug_file = stderr; return; } len = strlen(arg); fname = bmake_malloc(len + 20); memcpy(fname, arg, len + 1); /* 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 == NULL) { fprintf(stderr, "Cannot open debug file %s\n", fname); usage(); } free(fname); } static void MainParseArgDebug(const char *argvalue) { const char *modules; DebugFlags debug = opts.debug; for (modules = argvalue; *modules != '\0'; modules++) { switch (*modules) { case '0': /* undocumented, only intended for tests */ debug = DEBUG_NONE; break; case 'A': debug = DEBUG_ALL; break; case 'a': debug |= DEBUG_ARCH; break; case 'C': debug |= DEBUG_CWD; break; case 'c': debug |= DEBUG_COND; break; case 'd': debug |= DEBUG_DIR; break; case 'e': debug |= DEBUG_ERROR; break; case 'f': debug |= DEBUG_FOR; break; case 'g': if (modules[1] == '1') { debug |= DEBUG_GRAPH1; modules++; } else if (modules[1] == '2') { debug |= DEBUG_GRAPH2; modules++; } else if (modules[1] == '3') { debug |= DEBUG_GRAPH3; modules++; } break; case 'h': debug |= DEBUG_HASH; break; case 'j': debug |= DEBUG_JOB; break; case 'L': - opts.strict = TRUE; + opts.strict = true; break; case 'l': debug |= DEBUG_LOUD; break; case 'M': debug |= DEBUG_META; break; case 'm': debug |= DEBUG_MAKE; break; case 'n': debug |= DEBUG_SCRIPT; break; case 'p': debug |= DEBUG_PARSE; break; case 's': debug |= DEBUG_SUFF; break; case 't': debug |= DEBUG_TARG; break; case 'V': - opts.debugVflag = TRUE; + opts.debugVflag = true; break; case 'v': debug |= DEBUG_VAR; break; case 'x': debug |= DEBUG_SHELL; break; case 'F': MainParseArgDebugFile(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); } } /* Is path relative, or does it contain any relative component "." or ".."? */ -static Boolean +static bool IsRelativePath(const char *path) { - const char *cp; + const char *p; 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; - } - return FALSE; + return true; + p = path; + while ((p = strstr(p, "/.")) != NULL) { + p += 2; + if (*p == '.') + p++; + if (*p == '/' || *p == '\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(2); /* Not 1 so -q can distinguish error */ } if (getcwd(curdir, MAXPATHLEN) == NULL) { (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno)); exit(2); } if (!IsRelativePath(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; + ignorePWD = true; } static void MainParseArgJobsInternal(const char *argvalue) { 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); 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; + opts.compatMake = true; } else { Global_Append(MAKEFLAGS, "-J"); Global_Append(MAKEFLAGS, argvalue); } } static void MainParseArgJobs(const char *argvalue) { char *p; - forceJobs = TRUE; + 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(2); /* Not 1 so -q can distinguish error */ } Global_Append(MAKEFLAGS, "-j"); Global_Append(MAKEFLAGS, argvalue); Global_Set(".MAKE.JOBS", argvalue); 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)SearchPath_Add(sysIncPath, found_path); free(found_path); } else { (void)SearchPath_Add(sysIncPath, argvalue); } Global_Append(MAKEFLAGS, "-m"); Global_Append(MAKEFLAGS, argvalue); } -static Boolean +static bool MainParseArg(char c, const char *argvalue) { switch (c) { case '\0': break; case 'B': - opts.compatMake = TRUE; + opts.compatMake = true; Global_Append(MAKEFLAGS, "-B"); Global_Set(MAKE_MODE, "compat"); break; case 'C': MainParseArgChdir(argvalue); break; case 'D': - if (argvalue[0] == '\0') return FALSE; + if (argvalue[0] == '\0') return false; Global_SetExpand(argvalue, "1"); Global_Append(MAKEFLAGS, "-D"); Global_Append(MAKEFLAGS, argvalue); break; case 'I': Parse_AddIncludeDir(argvalue); Global_Append(MAKEFLAGS, "-I"); Global_Append(MAKEFLAGS, argvalue); break; case 'J': MainParseArgJobsInternal(argvalue); break; case 'N': - opts.noExecute = TRUE; - opts.noRecursiveExecute = TRUE; + opts.noExecute = true; + opts.noRecursiveExecute = true; Global_Append(MAKEFLAGS, "-N"); break; case 'S': - opts.keepgoing = FALSE; + opts.keepgoing = false; Global_Append(MAKEFLAGS, "-S"); break; case 'T': tracefile = bmake_strdup(argvalue); Global_Append(MAKEFLAGS, "-T"); Global_Append(MAKEFLAGS, argvalue); break; case 'V': case 'v': opts.printVars = c == 'v' ? PVM_EXPANDED : PVM_UNEXPANDED; Lst_Append(&opts.variables, bmake_strdup(argvalue)); /* XXX: Why always -V? */ Global_Append(MAKEFLAGS, "-V"); Global_Append(MAKEFLAGS, argvalue); break; case 'W': - opts.parseWarnFatal = TRUE; + opts.parseWarnFatal = true; /* XXX: why no Var_Append? */ break; case 'X': - opts.varNoExportEnv = TRUE; + opts.varNoExportEnv = true; Global_Append(MAKEFLAGS, "-X"); break; case 'd': /* If '-d-opts' don't pass to children */ if (argvalue[0] == '-') argvalue++; else { Global_Append(MAKEFLAGS, "-d"); Global_Append(MAKEFLAGS, argvalue); } MainParseArgDebug(argvalue); break; case 'e': - opts.checkEnvFirst = TRUE; + opts.checkEnvFirst = true; Global_Append(MAKEFLAGS, "-e"); break; case 'f': Lst_Append(&opts.makefiles, bmake_strdup(argvalue)); break; case 'i': - opts.ignoreErrors = TRUE; + opts.ignoreErrors = true; Global_Append(MAKEFLAGS, "-i"); break; case 'j': MainParseArgJobs(argvalue); break; case 'k': - opts.keepgoing = TRUE; + opts.keepgoing = true; Global_Append(MAKEFLAGS, "-k"); break; case 'm': MainParseArgSysInc(argvalue); /* XXX: why no Var_Append? */ break; case 'n': - opts.noExecute = TRUE; + opts.noExecute = true; Global_Append(MAKEFLAGS, "-n"); break; case 'q': - opts.queryFlag = TRUE; + opts.queryFlag = true; /* Kind of nonsensical, wot? */ Global_Append(MAKEFLAGS, "-q"); break; case 'r': - opts.noBuiltins = TRUE; + opts.noBuiltins = true; Global_Append(MAKEFLAGS, "-r"); break; case 's': - opts.beSilent = TRUE; + opts.beSilent = true; Global_Append(MAKEFLAGS, "-s"); break; case 't': - opts.touchFlag = TRUE; + opts.touchFlag = true; Global_Append(MAKEFLAGS, "-t"); break; case 'w': - opts.enterFlag = TRUE; + opts.enterFlag = true; Global_Append(MAKEFLAGS, "-w"); break; default: case '?': usage(); } - return TRUE; + 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; + bool 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; + 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--; - inOption = FALSE; + inOption = false; continue; } } else { if (c != '-' || dashDash) break; - inOption = TRUE; + 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; + 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; + inOption = false; break; case '-': - dashDash = TRUE; + dashDash = true; break; default: if (!MainParseArg(c, argvalue)) goto noarg; } argv += arginc; argc -= arginc; } /* * 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, SCOPE_CMDLINE); + Parse_Var(&var, SCOPE_CMDLINE); } else { if (argv[1][0] == '\0') Punt("illegal (null) argument."); 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; char *buf; if (line == NULL) return; /* XXX: don't use line as an iterator variable */ for (; *line == ' '; line++) continue; 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 { FStr argv0 = Var_Value(SCOPE_GLOBAL, ".MAKE"); buf = str_concat3(argv0.str, " ", line); FStr_Done(&argv0); } - words = Str_Words(buf, TRUE); + 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(Boolean writable, const char *fmt, ...) +bool +Main_SetObjdir(bool writable, const char *fmt, ...) { struct stat sb; char *path; char buf[MAXPATHLEN + 1]; char buf2[MAXPATHLEN + 1]; - Boolean rc = FALSE; + bool 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 ((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); Global_Set(".OBJDIR", objdir); setenv("PWD", objdir, 1); Dir_InitDot(); purge_relative_cached_realpaths(); - rc = TRUE; + rc = true; if (opts.enterFlag && strcmp(objdir, curdir) != 0) - enterFlagObj = TRUE; + enterFlagObj = true; } } return rc; } -static Boolean -SetVarObjdir(Boolean writable, const char *var, const char *suffix) +static bool +SetVarObjdir(bool writable, const char *var, const char *suffix) { FStr path = Var_Value(SCOPE_CMDLINE, var); FStr xpath; if (path.str == NULL || path.str[0] == '\0') { FStr_Done(&path); - return FALSE; + return false; } /* expand variable substitutions */ xpath = FStr_InitRefer(path.str); if (strchr(path.str, '$') != 0) { char *expanded; (void)Var_Subst(path.str, SCOPE_GLOBAL, VARE_WANTRES, &expanded); /* TODO: handle errors */ xpath = FStr_InitOwn(expanded); } (void)Main_SetObjdir(writable, "%s%s", xpath.str, suffix); FStr_Done(&xpath); FStr_Done(&path); - return TRUE; + return true; } /* * 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) { char *cp; int n; const char *sep = " \t"; for (n = 0, cp = strtok(str, sep); cp != NULL; 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) return; len = snprintf(str, sizeof str, "%s: Working in: %s\n", progname, dir); if (len > 0) (void)write(STDERR_FILENO, str, (size_t)len); } #endif /* Allow makefiles some control over the mode we run in. */ static void MakeMode(void) { char *mode; (void)Var_Subst("${" MAKE_MODE ":tl}", SCOPE_GLOBAL, VARE_WANTRES, &mode); /* TODO: handle errors */ if (mode[0] != '\0') { if (strstr(mode, "compat") != NULL) { - opts.compatMake = TRUE; - forceJobs = FALSE; + opts.compatMake = true; + forceJobs = false; } #if USE_META if (strstr(mode, "meta") != NULL) meta_mode_init(mode); #endif } free(mode); } static void -PrintVar(const char *varname, Boolean expandVars) +PrintVar(const char *varname, bool expandVars) { if (strchr(varname, '$') != NULL) { char *evalue; (void)Var_Subst(varname, SCOPE_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, SCOPE_GLOBAL, VARE_WANTRES, &evalue); /* TODO: handle errors */ free(expr); printf("%s\n", evalue); bmake_free(evalue); } else { FStr value = Var_Value(SCOPE_GLOBAL, varname); printf("%s\n", value.str != NULL ? value.str : ""); FStr_Done(&value); } } /* - * Return a Boolean based on a variable. + * Return a bool 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. + * is false, otherwise true. */ -Boolean -GetBooleanVar(const char *varname, Boolean fallback) +bool +GetBooleanExpr(const char *expr, bool fallback) { - char *expr = str_concat3("${", varname, ":U}"); char *value; - Boolean res; + bool res; (void)Var_Subst(expr, SCOPE_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; + bool expandVars; if (opts.printVars == PVM_EXPANDED) - expandVars = TRUE; + expandVars = true; else if (opts.debugVflag) - expandVars = FALSE; + expandVars = false; else - expandVars = GetBooleanVar(".MAKE.EXPAND_VARIABLES", FALSE); + expandVars = GetBooleanExpr("${.MAKE.EXPAND_VARIABLES}", + false); for (ln = opts.variables.first; ln != NULL; ln = ln->next) { const char *varname = ln->datum; PrintVar(varname, expandVars); } } -static Boolean +static bool runTargets(void) { GNodeList targs = LST_INIT; /* target nodes to create */ - Boolean outOfDate; /* FALSE if all targets up to date */ + bool 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)) Parse_MainName(&targs); else Targ_FindList(&targs, &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; + 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; + outOfDate = false; } Lst_Done(&targs); /* Don't free the targets themselves. */ 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)) { Global_Set(".TARGETS", ""); return; } for (ln = opts.create.first; ln != NULL; ln = ln->next) { const char *name = ln->datum; Global_Append(".TARGETS", name); } } static void InitRandom(void) { struct timeval tv; gettimeofday(&tv, NULL); srandom((unsigned int)(tv.tv_sec + tv.tv_usec)); } static const char * InitVarMachine(const struct utsname *utsname MAKE_ATTR_UNUSED) { #ifdef FORCE_MACHINE return FORCE_MACHINE; #else const char *machine = getenv("MACHINE"); if (machine != NULL) return machine; #if defined(MAKE_NATIVE) return utsname->machine; #elif defined(MAKE_MACHINE) return MAKE_MACHINE; #else return "unknown"; #endif #endif } static const char * 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]; const int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; size_t len = sizeof machine_arch_buf; if (sysctl(mib, (unsigned int)__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; } #elif defined(MACHINE_ARCH) return MACHINE_ARCH; #elif defined(MAKE_MACHINE_ARCH) return MAKE_MACHINE_ARCH; #else return "unknown"; #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. * * XXX: Make no longer has "local" and "remote" mode. Is this code still * necessary? * * 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; FStr prefix, makeobjdir; struct stat pwd_st; if (ignorePWD || (pwd = getenv("PWD")) == NULL) return; prefix = Var_Value(SCOPE_CMDLINE, "MAKEOBJDIRPREFIX"); if (prefix.str != NULL) { FStr_Done(&prefix); return; } makeobjdir = Var_Value(SCOPE_CMDLINE, "MAKEOBJDIR"); if (makeobjdir.str != NULL && strchr(makeobjdir.str, '$') != 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: FStr_Done(&makeobjdir); } #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; + bool writable; Dir_InitCur(curdir); - writable = GetBooleanVar("MAKE_OBJDIR_CHECK_WRITABLE", TRUE); - (void)Main_SetObjdir(FALSE, "%s", curdir); + writable = GetBooleanExpr("${MAKE_OBJDIR_CHECK_WRITABLE}", true); + (void)Main_SetObjdir(false, "%s", 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; + opts.compatMake = false; opts.debug = DEBUG_NONE; /* opts.debug_file has already been initialized earlier */ - opts.strict = FALSE; - opts.debugVflag = FALSE; - opts.checkEnvFirst = FALSE; + opts.strict = false; + opts.debugVflag = false; + opts.checkEnvFirst = false; Lst_Init(&opts.makefiles); - opts.ignoreErrors = FALSE; /* Pay attention to non-zero returns */ + opts.ignoreErrors = false; /* Pay attention to non-zero returns */ opts.maxJobs = 1; - opts.keepgoing = FALSE; /* Stop on error */ - opts.noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ - opts.noExecute = FALSE; /* Execute all commands */ - opts.queryFlag = FALSE; - opts.noBuiltins = FALSE; /* Read the built-in rules */ - opts.beSilent = FALSE; /* Print commands as executed */ - opts.touchFlag = FALSE; + opts.keepgoing = false; /* Stop on error */ + opts.noRecursiveExecute = false; /* Execute all .MAKE targets */ + opts.noExecute = false; /* Execute all commands */ + opts.queryFlag = false; + opts.noBuiltins = false; /* Read the built-in rules */ + opts.beSilent = false; /* Print commands as executed */ + opts.touchFlag = false; opts.printVars = PVM_NONE; Lst_Init(&opts.variables); - opts.parseWarnFatal = FALSE; - opts.enterFlag = FALSE; - opts.varNoExportEnv = FALSE; + opts.parseWarnFatal = false; + opts.enterFlag = false; + opts.varNoExportEnv = false; Lst_Init(&opts.create); } /* * 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 *abspath = cached_realpath(argv0, pathbuf); struct stat st; if (abspath != NULL && abspath[0] == '/' && stat(make, &st) == 0) make = abspath; } Global_Set("MAKE", make); Global_Set(".MAKE", make); } /* * 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. */ 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 == ':') *cp++ = '\0'; /* look for magic parent directory search string */ if (strncmp(start, ".../", 4) == 0) { char *dir = Dir_FindHereOrAbove(curdir, start + 4); if (dir != NULL) { (void)SearchPath_Add(defSysIncPath, dir); free(dir); } } else { (void)SearchPath_Add(defSysIncPath, start); } } if (syspath != defsyspath) free(syspath); } static void ReadBuiltinRules(void) { StringListNode *ln; StringList sysMkFiles = LST_INIT; SearchPath_Expand( Lst_IsEmpty(&sysIncPath->dirs) ? defSysIncPath : sysIncPath, _PATH_DEFSYSMK, &sysMkFiles); if (Lst_IsEmpty(&sysMkFiles)) Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); for (ln = sysMkFiles.first; ln != NULL; ln = ln->next) if (ReadMakefile(ln->datum) == 0) break; if (ln == NULL) Fatal("%s: cannot open %s.", progname, (const char *)sysMkFiles.first->datum); /* Free the list nodes but not the actual filenames since these may * still be used in GNodes. */ Lst_Done(&sysMkFiles); } static void InitMaxJobs(void) { char *value; int n; if (forceJobs || opts.compatMake || !Var_Exists(SCOPE_GLOBAL, ".MAKE.JOBS")) return; (void)Var_Subst("${.MAKE.JOBS}", SCOPE_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); exit(2); /* Not 1 so -q can distinguish error */ } if (n != opts.maxJobs) { Global_Append(MAKEFLAGS, "-j"); Global_Append(MAKEFLAGS, value); } opts.maxJobs = n; maxJobTokens = opts.maxJobs; - forceJobs = TRUE; + 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(SCOPE_CMDLINE, "VPATH")) return; (void)Var_Subst("${VPATH}", SCOPE_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)SearchPath_Add(&dirSearchPath, path); *cp = savec; path = cp + 1; } while (savec == ':'); free(vpath); } static void ReadAllMakefiles(StringList *makefiles) { StringListNode *ln; 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 ReadFirstDefaultMakefile(void) { StringListNode *ln; char *prefs; (void)Var_Subst("${" MAKE_MAKEFILE_PREFERENCE "}", SCOPE_CMDLINE, VARE_WANTRES, &prefs); /* TODO: handle errors */ /* 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); for (ln = opts.makefiles.first; ln != NULL; ln = ln->next) if (ReadMakefile(ln->datum) == 0) break; free(prefs); } /* * 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) { 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(); progname = str_basename(argv[0]); UnlimitFiles(); if (uname(&utsname) == -1) { (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, strerror(errno)); exit(2); } /* * Get the name of this type of MACHINE from utsname * so we can share an executable for similar machines. * (i.e. m68k: amiga hp300, mac68k, sun3, ...) * * Note that both MACHINE and MACHINE_ARCH are decided at * run-time. */ machine = InitVarMachine(&utsname); machine_arch = InitVarMachineArch(); myPid = getpid(); /* remember this for vFork() */ /* * Just in case MAKEOBJDIR wants us to do something tricky. */ Targ_Init(); Var_Init(); Global_Set(".MAKE.OS", utsname.sysname); Global_Set("MACHINE", machine); Global_Set("MACHINE_ARCH", machine_arch); #ifdef MAKE_VERSION Global_Set("MAKE_VERSION", MAKE_VERSION); #endif Global_Set(".newline", "\n"); /* handy for :@ loops */ /* * This is the traditional preference for makefiles. */ #ifndef MAKEFILE_PREFERENCE_LIST # define MAKEFILE_PREFERENCE_LIST "makefile Makefile" #endif Global_Set(MAKE_MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST); Global_Set(MAKE_DEPENDFILE, ".depend"); CmdOpts_Init(); - allPrecious = FALSE; /* Remove targets when interrupted */ - deleteOnError = FALSE; /* Historical default behavior */ - jobsRunning = FALSE; + allPrecious = false; /* Remove targets when interrupted */ + deleteOnError = false; /* Historical default behavior */ + jobsRunning = false; maxJobTokens = opts.maxJobs; - ignorePWD = FALSE; + 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]); Global_Set(MAKEFLAGS, ""); Global_Set(MAKEOVERRIDES, ""); Global_Set("MFLAGS", ""); Global_Set(".ALLTARGETS", ""); /* some makefiles need to know this */ Var_Set(SCOPE_CMDLINE, MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV); /* Set some other useful variables. */ { char tmp[64], *ep = getenv(MAKE_LEVEL_ENV); makelevel = ep != NULL && ep[0] != '\0' ? atoi(ep) : 0; if (makelevel < 0) makelevel = 0; snprintf(tmp, sizeof tmp, "%d", makelevel); Global_Set(MAKE_LEVEL, tmp); snprintf(tmp, sizeof tmp, "%u", myPid); Global_Set(".MAKE.PID", tmp); snprintf(tmp, sizeof tmp, "%u", getppid()); Global_Set(".MAKE.PPID", tmp); snprintf(tmp, sizeof tmp, "%u", getuid()); Global_Set(".MAKE.UID", tmp); snprintf(tmp, sizeof tmp, "%u", getgid()); Global_Set(".MAKE.GID", tmp); } if (makelevel > 0) { char pn[1024]; 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); } #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); } #ifndef NO_PWD_OVERRIDE HandlePWD(&sa); #endif Global_Set(".CURDIR", curdir); InitObjdir(machine, machine_arch); /* * Initialize archive, target and suffix modules in preparation for * parsing the makefile(s) */ Arch_Init(); Suff_Init(); Trace_Init(tracefile); defaultNode = NULL; (void)time(&now); Trace_Log(MAKESTART, NULL); InitVarTargets(); InitDefSysIncPath(syspath); } /* * 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(); 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 == PVM_NONE) { (void)Var_Subst("${.MAKE.DEPENDFILE}", SCOPE_CMDLINE, VARE_WANTRES, &makeDependfile); if (makeDependfile[0] != '\0') { /* TODO: handle errors */ - doing_depend = TRUE; + doing_depend = true; (void)ReadMakefile(makeDependfile); - doing_depend = FALSE; + doing_depend = false; } } if (enterFlagObj) printf("%s: Entering directory `%s'\n", progname, objdir); MakeMode(); { FStr makeflags = Var_Value(SCOPE_GLOBAL, MAKEFLAGS); Global_Append("MFLAGS", makeflags.str); FStr_Done(&makeflags); } InitMaxJobs(); /* * Be compatible if the user did not specify -j and did not explicitly * turn compatibility on. */ if (!opts.compatMake && !forceJobs) - opts.compatMake = TRUE; + opts.compatMake = true; if (!opts.compatMake) 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); if (opts.printVars == PVM_NONE) - Main_ExportMAKEFLAGS(TRUE); /* initial export */ + 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(); + Suff_ExtendPaths(); /* * Propagate attributes through :: dependency lists. */ Targ_Propagate(); /* print the initial graph, if the user requested it */ if (DEBUG(GRAPH1)) Targ_PrintGraph(1); } /* * 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 +static bool main_Run(void) { if (opts.printVars != PVM_NONE) { /* print the values of any variables requested by the user */ doPrintVars(); - return FALSE; + return false; } else { return runTargets(); } } /* Clean up after making the targets. */ static void main_CleanUp(void) { #ifdef CLEANUP Lst_DoneCall(&opts.variables, free); /* * Don't free the actual strings from opts.makefiles, they may be * used in GNodes. */ Lst_Done(&opts.makefiles); Lst_DoneCall(&opts.create, free); #endif /* 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) +main_Exit(bool outOfDate) { if (opts.strict && (main_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; + bool 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, "-") == 0) { Parse_File(NULL /*stdin*/, -1); Var_Set(SCOPE_INTERNAL, "MAKEFILE", ""); } else { /* if we've chdir'd, rebuild the path name */ 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 == NULL) { SearchPath *sysInc = Lst_IsEmpty(&sysIncPath->dirs) ? defSysIncPath : sysIncPath; name = Dir_FindFile(fname, sysInc); } 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(SCOPE_INTERNAL, "MAKEFILE", fname); 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 pipefds[2]; 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; if (shellName == NULL) 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(pipefds) == -1) { *errfmt = "Couldn't create pipe for \"%s\""; goto bad; } Var_ReexportVars(); /* * Fork */ switch (cpid = vfork()) { case 0: (void)close(pipefds[0]); /* Close input side of pipe */ /* * 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(pipefds[1], 1); (void)close(pipefds[1]); (void)execv(shellPath, UNCONST(args)); _exit(1); /*NOTREACHED*/ case -1: *errfmt = "Couldn't exec \"%s\""; goto bad; default: (void)close(pipefds[1]); /* No need for the writing half */ savederr = 0; Buf_Init(&buf); do { char result[BUFSIZ]; bytes_read = read(pipefds[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; (void)close(pipefds[0]); /* Close the input side of the pipe. */ /* Wait for the process to exit. */ while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0) - JobReapChild(pid, status, FALSE); + JobReapChild(pid, status, false); res_len = buf.len; res = Buf_DoneData(&buf); 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(""); } /* * Print a printf-style error message. * * 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; } main_errors++; } /* * Wait for any running jobs to finish, then produce an error message, * finally exit immediately. * * 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; 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, 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, 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 (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); 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.data, buf.len); Buf_Done(&buf); _exit(1); } /* purge any relative paths */ static void purge_relative_cached_realpaths(void) { HashEntry *he, *nhe; HashIter hi; 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; } } char * cached_realpath(const char *pathname, char *resolved) { const char *rp; if (pathname == NULL || pathname[0] == '\0') return NULL; rp = HashTable_FindValue(&cached_realpaths, pathname); if (rp != NULL) { /* a hit */ strncpy(resolved, rp, MAXPATHLEN); resolved[MAXPATHLEN - 1] = '\0'; return resolved; } 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; } /* 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 happened elsewhere. */ -Boolean +bool shouldDieQuietly(GNode *gn, int bf) { static int quietly = -1; if (quietly < 0) { - if (DEBUG(JOB) || !GetBooleanVar(".MAKE.DIE_QUIETLY", TRUE)) + if (DEBUG(JOB) || + !GetBooleanExpr("${.MAKE.DIE_QUIETLY}", true)) quietly = 0; else if (bf >= 0) quietly = bf; else quietly = (gn != NULL && (gn->type & OP_MAKE)) ? 1 : 0; } return quietly != 0; } static void SetErrorVars(GNode *gn) { StringListNode *ln; /* * We can print this even if there is no .ERROR target. */ Global_Set(".ERROR_TARGET", gn->name); Global_Delete(".ERROR_CMD"); for (ln = gn->commands.first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; if (cmd == NULL) break; Global_Append(".ERROR_CMD", cmd); } } /* * Print some helpful information in case of an error. * The caller should exit soon after calling this function. */ void PrintOnError(GNode *gn, const char *msg) { static GNode *errorNode = NULL; if (DEBUG(HASH)) { Targ_Stats(); Var_Stats(); } if (errorNode != NULL) return; /* we've been here! */ if (msg != NULL) printf("%s", msg); printf("\n%s: stopped in %s\n", progname, curdir); /* we generally want to keep quiet if a sub-make died */ if (shouldDieQuietly(gn, -1)) return; if (gn != NULL) SetErrorVars(gn); { char *errorVarsValues; (void)Var_Subst("${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", SCOPE_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) +Main_ExportMAKEFLAGS(bool first) { - static Boolean once = TRUE; + static bool once = true; const char *expr; char *s; if (once != first) return; - once = FALSE; + once = false; expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}"; (void)Var_Subst(expr, SCOPE_CMDLINE, VARE_WANTRES, &s); /* TODO: handle errors */ if (s[0] != '\0') { #ifdef POSIX setenv("MAKEFLAGS", s, 1); #else setenv("MAKE", s, 1); #endif } } char * getTmpdir(void) { static char *tmpdir = NULL; struct stat st; 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 "}/", + (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP ":S,/$,,W}/", SCOPE_GLOBAL, VARE_WANTRES, &tmpdir); /* TODO: handle errors */ if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { free(tmpdir); tmpdir = bmake_strdup(_PATH_TMP); } return tmpdir; } /* * Create and open a temp file using "pattern". * If 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 *tfile, size_t tfile_sz) { static char *tmpdir = NULL; char tbuf[MAXPATHLEN]; int fd; if (pattern == NULL) pattern = TMPPAT; if (tmpdir == NULL) tmpdir = getTmpdir(); if (tfile == NULL) { tfile = tbuf; tfile_sz = sizeof tbuf; } if (pattern[0] == '/') { snprintf(tfile, tfile_sz, "%s", pattern); } else { snprintf(tfile, tfile_sz, "%s%s", tmpdir, pattern); } if ((fd = mkstemp(tfile)) < 0) Punt("Could not create temporary file %s: %s", tfile, strerror(errno)); if (tfile == tbuf) { unlink(tfile); /* we just want the descriptor */ } return fd; } /* * 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. + * Anything that looks like "No", "False", "Off", "0" etc. is false, + * the empty string is the fallback, everything else is true. */ -Boolean -ParseBoolean(const char *s, Boolean fallback) +bool +ParseBoolean(const char *s, bool fallback) { char ch = ch_tolower(s[0]); if (ch == '\0') return fallback; if (ch == '0' || ch == 'f' || ch == 'n') - return FALSE; + return false; if (ch == 'o') return ch_tolower(s[1]) != 'f'; - return TRUE; + return true; } diff --git a/contrib/bmake/make.c b/contrib/bmake/make.c index 1cd7299a76c5..a85a497be32d 100644 --- a/contrib/bmake/make.c +++ b/contrib/bmake/make.c @@ -1,1439 +1,1440 @@ -/* $NetBSD: make.c,v 1.242 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: make.c,v 1.244 2021/04/04 10:05:08 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. */ /* * Examination of targets and their suitability for creation. * * Interface: - * Make_Run Initialize things for the module. Returns TRUE if + * Make_Run Initialize things for the module. Returns true if * work was (or would have been) done. * * 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 variable, etc. Place the parent on the * toBeMade queue if it should be. * * 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 + * GNode_SetLocalVars + * 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. * * 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.242 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: make.c,v 1.244 2021/04/04 10:05:08 rillig Exp $"); /* Sequence # to detect recursion. */ 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 = LST_INIT; 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 lineno) { debug_printf("make_abort from line %d\n", lineno); Targ_PrintNode(gn, 2); Targ_PrintNodes(&toBeMade, 2); Targ_PrintGraph(3); abort(); } 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_9(GNodeFlags, REMAKE, CHILDMADE, FORCE, DONE_WAIT, DONE_ORDER, FROM_DEPEND, DONE_ALLSRC, CYCLE, DONECYCLE); 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, "%s%s, type %s, flags %s%s", prefix, GNodeMade_Name(gn->made), GNodeType_ToString(type_buf, gn->type), GNodeFlags_ToString(flags_buf, gn->flags), suffix); } -Boolean +bool 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 GNode_UpdateYoungestChild(GNode *gn, GNode *cgn) { if (gn->youngestChild == NULL || cgn->mtime > gn->youngestChild->mtime) gn->youngestChild = cgn; } -static Boolean +static bool 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 true; } - return FALSE; + return false; } if (gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) { DEBUG0(MAKE, "nonexistent and no sources..."); - return TRUE; + return true; } if (gn->type & OP_DOUBLEDEP) { DEBUG0(MAKE, ":: operator and no sources..."); - return TRUE; + return true; } - return FALSE; + 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 +bool GNode_IsOODate(GNode *gn) { - Boolean oodate; + bool 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))) { - Dir_UpdateMTime(gn, TRUE); + Dir_UpdateMTime(gn, true); if (DEBUG(MAKE)) { if (gn->mtime != 0) debug_printf("modified %s...", Targ_FmtTime(gn->mtime)); else debug_printf("nonexistent..."); } } /* * 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; + oodate = false; } 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 nonexistent. */ 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) != 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; + oodate = true; } else if (IsOODateRegular(gn)) { - oodate = TRUE; + oodate = true; } else { /* * When a nonexistent 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 nonexistent * child after it was considered made. */ if (DEBUG(MAKE)) { if (gn->flags & FORCE) debug_printf("non existing child..."); } 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) GNode_UpdateYoungestChild(ln->datum, gn); } return oodate; } static void PretendAllChildrenAreMade(GNode *pgn) { GNodeListNode *ln; for (ln = pgn->children.first; ln != NULL; ln = ln->next) { GNode *cgn = ln->datum; /* This may also update cgn->path. */ - Dir_UpdateMTime(cgn, FALSE); + Dir_UpdateMTime(cgn, false); GNode_UpdateYoungestChild(pgn, cgn); pgn->unmade--; } } /* * 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))) { debug_printf("Make_HandleUse: called for plain node %s\n", cgn->name); /* XXX: debug mode should not affect control flow */ return; } #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 != NULL && 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; + bool unmarked; unmarked = !(cgn->type & OP_MARK); cgn->type |= OP_MARK; 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_UpdateMTime(gn, TRUE); + 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 "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_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 * 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, gn->mtime == 0 ? "nonexistent" : Targ_FmtTime(gn->mtime)); gn->mtime = now; } 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(pgn, IMPSRC, cname); if (cpref != NULL) Var_Set(pgn, PREFIX, cpref); } } } /* See if a .ORDER rule stops us from building this node. */ -static Boolean +static bool IsWaitingForOrder(GNode *gn) { GNodeListNode *ln; for (ln = gn->order_pred.first; ln != NULL; ln = ln->next) { GNode *ogn = ln->datum; if (GNode_IsDone(ogn) || !(ogn->flags & REMAKE)) continue; DEBUG2(MAKE, "IsWaitingForOrder: Waiting for .ORDER node \"%s%s\"\n", ogn->name, ogn->cohort_num); - return TRUE; + return true; } - return FALSE; + return false; } static void MakeBuildParent(GNode *, GNodeListNode *); static void ScheduleOrderSuccessors(GNode *gn) { GNodeListNode *toBeMadeNext = toBeMade.first; GNodeListNode *ln; for (ln = gn->order_succ.first; ln != NULL; ln = ln->next) MakeBuildParent(ln->datum, toBeMadeNext); } /* * 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_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 = ¢urion->parents; /* If this was a .ORDER node, schedule the RHS */ ScheduleOrderSuccessors(centurion); /* 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: ", 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->made == MADE) pgn->flags |= CHILDMADE; GNode_UpdateYoungestChild(pgn, cgn); } /* * A parent must wait for the completion of all instances * of a `::' dependency. */ if (centurion->unmade_cohorts != 0 || !GNode_IsDone(centurion)) { 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; } 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) { const char *child, *allsrc; if (cgn->type & OP_MARK) return; cgn->type |= OP_MARK; if (cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE | OP_INVISIBLE)) return; 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(pgn, ALLSRC, allsrc); if (pgn->type & OP_JOIN) { if (cgn->made == MADE) Var_Append(pgn, OODATE, child); } 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 child, * the parent is likely to have a modification time * later than that of 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(pgn, OODATE, child); } } /* * 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) +GNode_SetLocalVars(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(gn, OODATE)) Var_Set(gn, OODATE, ""); if (!Var_Exists(gn, ALLSRC)) Var_Set(gn, ALLSRC, ""); if (gn->type & OP_JOIN) Var_Set(gn, TARGET, GNode_VarAllsrc(gn)); gn->flags |= DONE_ALLSRC; } -static Boolean +static bool MakeBuildChild(GNode *cn, GNodeListNode *toBeMadeNext) { if (DEBUG(MAKE)) { debug_printf("MakeBuildChild: inspect %s%s, ", cn->name, cn->cohort_num); GNode_FprintDetails(opts.debug_file, "", cn, "\n"); } if (GNode_IsReady(cn)) - return FALSE; + return false; /* If this node is on the RHS of a .ORDER, check LHSs. */ if (IsWaitingForOrder(cn)) { /* Can't build this (or anything else in this child list) yet */ cn->made = DEFERRED; - return FALSE; /* but keep looking */ + return false; /* but keep looking */ } DEBUG2(MAKE, "MakeBuildChild: schedule %s%s\n", cn->name, cn->cohort_num); cn->made = REQUESTED; if (toBeMadeNext == NULL) Lst_Append(&toBeMade, cn); else Lst_InsertBefore(&toBeMade, toBeMadeNext, cn); if (cn->unmade_cohorts != 0) { ListNode *ln; for (ln = cn->cohorts.first; ln != NULL; ln = ln->next) if (MakeBuildChild(ln->datum, toBeMadeNext)) break; } /* * 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. */ static void MakeBuildParent(GNode *pn, GNodeListNode *toBeMadeNext) { if (pn->made != DEFERRED) return; if (!MakeBuildChild(pn, toBeMadeNext)) { /* When this node is built, reschedule its parents. */ pn->flags |= DONE_ORDER; } } static void MakeChildren(GNode *gn) { GNodeListNode *toBeMadeNext = toBeMade.first; GNodeListNode *ln; for (ln = gn->children.first; ln != NULL; ln = ln->next) if (MakeBuildChild(ln->datum, toBeMadeNext)) break; } /* * Start as many jobs as possible, taking them from the toBeMade queue. * * 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. In all other cases, this function returns FALSE. + * returns true. In all other cases, this function returns false. */ -static Boolean +static bool MakeStartJobs(void) { GNode *gn; - Boolean have_token = FALSE; + bool 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 = TRUE; + have_token = true; gn = Lst_Dequeue(&toBeMade); DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num); if (gn->made != REQUESTED) { /* * XXX: Replace %d with string representation; * see made_name. */ DEBUG1(MAKE, "state %d\n", gn->made); make_abort(gn, __LINE__); } 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_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; MakeChildren(gn); /* and drop this node on the floor */ DEBUG2(MAKE, "dropped %s%s\n", gn->name, gn->cohort_num); continue; } gn->made = BEINGMADE; if (GNode_IsOODate(gn)) { DEBUG0(MAKE, "out-of-date\n"); if (opts.queryFlag) - return TRUE; - Make_DoAllVar(gn); + return true; + GNode_SetLocalVars(gn); Job_Make(gn); - have_token = FALSE; + 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 local variables so * references to it get the correct value * for .TARGET when building up the local * variables of its parent(s)... */ - Make_DoAllVar(gn); + GNode_SetLocalVars(gn); } Make_Update(gn); } } if (have_token) Job_TokenReturn(); - return FALSE; + return false; } /* Print the status of a .ORDER node. */ static void MakePrintStatusOrderNode(GNode *ogn, GNode *gn) { if (!GNode_IsWaitingFor(ogn)) 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. */ -static Boolean +static bool MakePrintStatus(GNode *gn, int *errors) { if (gn->flags & DONECYCLE) { /* * We've completely processed this node before, don't do * it again. */ - return FALSE; + 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; + 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)) { /* 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; + 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; + return true; /* Reporting for our children will give the rest of the loop */ MakePrintStatusList(&gn->children, errors); - return FALSE; + 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 = LST_INIT; /* Queue of targets to examine */ Lst_AppendAll(&examine, targs); /* * 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 = strchr(gn->name, '('); char *eon = strchr(gn->name, ')'); if (eoa == NULL || eon == NULL) continue; *eoa = '\0'; *eon = '\0'; Var_Set(gn, MEMBER, eoa + 1); Var_Set(gn, ARCHIVE, gn->name); *eoa = '('; *eon = ')'; } - Dir_UpdateMTime(gn, FALSE); + Dir_UpdateMTime(gn, false); Var_Set(gn, TARGET, GNode_Path(gn)); UnmarkChildren(gn); HandleUseNodes(gn); if (!(gn->type & OP_MADE)) Suff_FindDeps(gn); else { PretendAllChildrenAreMade(gn); if (gn->unmade != 0) { printf( "Warning: " "%s%s still has %d unmade children\n", gn->name, gn->cohort_num, gn->unmade); } } if (gn->unmade != 0) ExamineLater(&examine, &gn->children); } Lst_Done(&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 = 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); Lst_Init(&examine); 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_Done(&examine); } /* * 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. + * 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 +bool Make_Run(GNodeList *targs) { int errors; /* Number of errors the Job module reports */ /* Start trying to make the current targets... */ Lst_Init(&toBeMade); 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 > 0) Targ_PrintGraph(4); } } return errors > 0; } diff --git a/contrib/bmake/make.h b/contrib/bmake/make.h index da5f60534f31..a074923c643e 100644 --- a/contrib/bmake/make.h +++ b/contrib/bmake/make.h @@ -1,870 +1,847 @@ -/* $NetBSD: make.h,v 1.256 2021/02/05 19:19:17 sjg Exp $ */ +/* $NetBSD: make.h,v 1.263 2021/06/21 10:33:11 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 +#define MAKE_STATIC static 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). - */ -#if defined(lint) || defined(USE_C99_BOOLEAN) +#if __STDC_VERSION__ >= 199901L || defined(lint) || defined(USE_C99_BOOLEAN) #include -typedef bool Boolean; -#define FALSE false -#define TRUE true -#elif defined(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 +#ifndef bool +typedef unsigned int Boolean; +#define bool Boolean +#endif +#ifndef true +#define true 1 #endif -#ifndef FALSE -#define FALSE 0 +#ifndef false +#define false 0 #endif #endif #include "lst.h" #include "enum.h" +#include "make_malloc.h" +#include "str.h" #include "hash.h" #include "make-conf.h" #include "buf.h" -#include "make_malloc.h" /* * some vendors don't have this --sjg */ #if defined(S_IFDIR) && !defined(S_ISDIR) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #if defined(sun) && (defined(__svr4__) || defined(__SVR4)) # define POSIX_SIGNALS #endif /* * The typical flow of states is: * * The direct successful path: * UNMADE -> BEINGMADE -> MADE. * * The direct error path: * UNMADE -> BEINGMADE -> ERROR. * * The successful path when dependencies need to be made first: * UNMADE -> DEFERRED -> REQUESTED -> BEINGMADE -> MADE. * * A node that has dependencies, and one of the dependencies cannot be made: * UNMADE -> DEFERRED -> ABORTED. * * A node that turns out to be up-to-date: * UNMADE -> BEINGMADE -> UPTODATE. */ typedef enum GNodeMade { /* Not examined yet. */ UNMADE, /* The node has been examined but is not yet ready since its * dependencies have to be made first. */ DEFERRED, /* The node is on the toBeMade list. */ REQUESTED, /* The node is already being made. Trying to build a node in this * state indicates a cycle in the graph. */ BEINGMADE, /* Was out-of-date and has been made. */ MADE, /* Was already up-to-date, does not need to be made. */ UPTODATE, /* An error occurred while it was being made. * Used only in compat mode. */ ERROR, /* The target was aborted due to an error making a dependency. * Used only in compat mode. */ ABORTED } 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. + * + * See the tests depsrc-*.mk and deptgt-*.mk. */ 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. * 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 or .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 (.IMPSRC, .ALLSRC). */ OP_INVISIBLE = 1 << 14, /* The node does not become the main target, even if it is the first * target in the first makefile. */ OP_NOTMAIN = 1 << 15, /* Not a file target; run always. */ OP_PHONY = 1 << 16, /* Don't search for the file in the path. */ OP_NOPATH = 1 << 17, /* 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, such as ".c.o". */ OP_TRANSFORM = 1 << 30, /* Target is a member of an archive */ /* XXX: How does this differ from OP_ARCHV? */ OP_MEMBER = 1 << 29, /* The node is a library, * its name has the form "-l" */ OP_LIB = 1 << 28, /* The node is an archive member, * its name has the form "archive(member)" */ /* XXX: How does this differ from OP_MEMBER? */ OP_ARCHV = 1 << 27, /* 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 << 26, /* 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 << 25, /* Already processed by Suff_FindDeps, to find dependencies from * suffix transformation rules. */ OP_DEPS_FOUND = 1 << 24, /* Node found while expanding .ALLSRC */ OP_MARK = 1 << 23, OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM } GNodeType; typedef enum GNodeFlags { GNF_NONE = 0, /* this target needs to be (re)made */ REMAKE = 1 << 0, /* children of this target were made */ CHILDMADE = 1 << 1, /* children don't exist, and we pretend made */ FORCE = 1 << 2, /* Set by Make_ProcessWait() */ DONE_WAIT = 1 << 3, /* Build requested by .ORDER processing */ DONE_ORDER = 1 << 4, /* Node created from .depend */ FROM_DEPEND = 1 << 5, /* We do it once only */ DONE_ALLSRC = 1 << 6, /* Used by MakePrintStatus */ CYCLE = 1 << 12, /* Used by MakePrintStatus */ DONECYCLE = 1 << 13 } GNodeFlags; typedef struct List StringList; typedef struct ListNode StringListNode; typedef struct List GNodeList; typedef struct ListNode GNodeListNode; typedef struct SearchPath { List /* of CachedDir */ dirs; } 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; /* The number of unmade children */ int unmade; /* The modification time; 0 means the node does not have a * corresponding 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; /* 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 targets that were defined using * the '::' dependency operator (OP_DOUBLEDEP). */ 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 SCOPE_GLOBAL, * SCOPE_CMDLINE, SCOPE_INTERNAL, which contain variables with * arbitrary names. */ HashTable /* of Var pointer */ vars; /* 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 Suffix *suffix; /* Filename where the GNode got defined */ /* XXX: What is the lifetime of this string? */ const char *fname; /* Line number where the GNode got defined */ int lineno; } GNode; /* 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; /* 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 */ /* * Global Variables */ /* True if every target is precious */ -extern Boolean allPrecious; +extern bool allPrecious; /* True if failed targets should be deleted */ -extern Boolean deleteOnError; -/* TRUE while processing .depend */ -extern Boolean doing_depend; +extern bool deleteOnError; +/* true while processing .depend */ +extern bool doing_depend; /* .DEFAULT rule */ extern GNode *defaultNode; /* * Variables defined internally by make which should not override those set * by makefiles. */ extern GNode *SCOPE_INTERNAL; /* Variables defined in a global scope, e.g in the makefile itself. */ extern GNode *SCOPE_GLOBAL; /* Variables defined on the command line. */ extern GNode *SCOPE_CMDLINE; /* * Value returned by Var_Parse when an error is encountered. It actually * points to an empty string, so naive callers needn't worry about it. */ extern char var_Error[]; /* The time at the start of this whole process */ extern time_t now; /* * The list of directories to search when looking for targets (set by the * special target .PATH). */ extern SearchPath dirSearchPath; /* Used for .include "...". */ extern SearchPath *parseIncPath; /* * Used for .include <...>, for the built-in sys.mk and for makefiles from * the command line arguments. */ extern SearchPath *sysIncPath; /* The default for sysIncPath. */ extern SearchPath *defSysIncPath; /* Startup directory */ extern char curdir[]; /* The basename of the program name, suffixed with [n] for sub-makes. */ extern const char *progname; extern int makelevel; /* Name of the .depend makefile */ extern char *makeDependfile; /* If we replaced environ, this will be non-NULL. */ extern char **savedEnv; extern pid_t myPid; #define MAKEFLAGS ".MAKEFLAGS" #define MAKEOVERRIDES ".MAKEOVERRIDES" /* prefix when printing the target of a job */ #define MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX" #define MAKE_EXPORTED ".MAKE.EXPORTED" /* exported variables */ #define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all loaded makefiles */ #define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */ #define MAKE_MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE" #define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */ #define MAKE_MODE ".MAKE.MODE" #ifndef MAKE_LEVEL_ENV # define MAKE_LEVEL_ENV "MAKELEVEL" #endif typedef enum DebugFlags { DEBUG_NONE = 0, DEBUG_ARCH = 1 << 0, DEBUG_COND = 1 << 1, DEBUG_CWD = 1 << 2, DEBUG_DIR = 1 << 3, DEBUG_ERROR = 1 << 4, DEBUG_FOR = 1 << 5, DEBUG_GRAPH1 = 1 << 6, DEBUG_GRAPH2 = 1 << 7, DEBUG_GRAPH3 = 1 << 8, DEBUG_HASH = 1 << 9, DEBUG_JOB = 1 << 10, DEBUG_LOUD = 1 << 11, DEBUG_MAKE = 1 << 12, DEBUG_META = 1 << 13, DEBUG_PARSE = 1 << 14, DEBUG_SCRIPT = 1 << 15, DEBUG_SHELL = 1 << 16, DEBUG_SUFF = 1 << 17, DEBUG_TARG = 1 << 18, DEBUG_VAR = 1 << 19, DEBUG_ALL = (1 << 20) - 1 } DebugFlags; #define CONCAT(a, b) a##b #define DEBUG(module) ((opts.debug & CONCAT(DEBUG_, module)) != 0) void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); #define DEBUG_IMPL(module, args) \ do { \ if (DEBUG(module)) \ debug_printf args; \ - } while (/*CONSTCOND*/FALSE) + } while (/*CONSTCOND*/false) #define DEBUG0(module, text) \ DEBUG_IMPL(module, ("%s", text)) #define DEBUG1(module, fmt, arg1) \ DEBUG_IMPL(module, (fmt, arg1)) #define DEBUG2(module, fmt, arg1, arg2) \ DEBUG_IMPL(module, (fmt, arg1, arg2)) #define DEBUG3(module, fmt, arg1, arg2, arg3) \ DEBUG_IMPL(module, (fmt, arg1, arg2, arg3)) #define DEBUG4(module, fmt, arg1, arg2, arg3, arg4) \ DEBUG_IMPL(module, (fmt, arg1, arg2, arg3, arg4)) #define DEBUG5(module, fmt, arg1, arg2, arg3, arg4, arg5) \ DEBUG_IMPL(module, (fmt, arg1, arg2, arg3, arg4, arg5)) typedef enum PrintVarsMode { PVM_NONE, PVM_UNEXPANDED, PVM_EXPANDED } PrintVarsMode; /* Command line options */ typedef struct CmdOpts { /* -B: whether we are make compatible */ - Boolean compatMake; + bool compatMake; /* -d: debug control: There is one bit per module. It is up to the * module what debug information to print. */ DebugFlags debug; /* -df: debug output is written here - default stderr */ FILE *debug_file; /* -dL: lint mode * * Runs make in strict mode, with additional checks and better error * handling. */ - Boolean strict; + bool strict; /* -dV: for the -V option, print unexpanded variable values */ - Boolean debugVflag; + bool debugVflag; /* -e: check environment variables before global variables */ - Boolean checkEnvFirst; + bool checkEnvFirst; /* -f: the makefiles to read */ StringList makefiles; /* -i: if true, ignore all errors from shell commands */ - Boolean ignoreErrors; + bool ignoreErrors; /* -j: the maximum number of jobs that can run in parallel; * this is coordinated with the submakes */ int maxJobs; /* -k: if true and an error occurs while making a node, continue * making nodes that do not depend on the erroneous node */ - Boolean keepgoing; + bool keepgoing; /* -N: execute no commands from the targets */ - Boolean noRecursiveExecute; + bool noRecursiveExecute; /* -n: execute almost no commands from the targets */ - Boolean noExecute; + bool noExecute; /* * -q: if true, do not really make anything, just see if the targets * are out-of-date */ - Boolean queryFlag; + bool queryFlag; /* -r: raw mode, do not load the builtin rules. */ - Boolean noBuiltins; + bool noBuiltins; /* -s: don't echo the shell commands before executing them */ - Boolean beSilent; + bool beSilent; /* -t: touch the targets if they are out-of-date, but don't actually * make them */ - Boolean touchFlag; + bool touchFlag; /* -[Vv]: print expanded or unexpanded selected variables */ PrintVarsMode printVars; /* -[Vv]: the variables to print */ StringList variables; /* -W: if true, makefile parsing warnings are treated as errors */ - Boolean parseWarnFatal; + bool parseWarnFatal; /* -w: print 'Entering' and 'Leaving' for submakes */ - Boolean enterFlag; + bool enterFlag; /* -X: if true, do not export variables set on the command line to the * environment. */ - Boolean varNoExportEnv; + bool varNoExportEnv; /* The target names specified on the command line. * Used to resolve .if make(...) statements. */ StringList create; } CmdOpts; extern CmdOpts opts; #include "nonints.h" void GNode_UpdateYoungestChild(GNode *, GNode *); -Boolean GNode_IsOODate(GNode *); +bool GNode_IsOODate(GNode *); void Make_ExpandUse(GNodeList *); time_t Make_Recheck(GNode *); void Make_HandleUse(GNode *, GNode *); void Make_Update(GNode *); -void Make_DoAllVar(GNode *); -Boolean Make_Run(GNodeList *); -Boolean shouldDieQuietly(GNode *, int); +void GNode_SetLocalVars(GNode *); +bool Make_Run(GNodeList *); +bool shouldDieQuietly(GNode *, int); void PrintOnError(GNode *, const char *); -void Main_ExportMAKEFLAGS(Boolean); -Boolean Main_SetObjdir(Boolean, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); +void Main_ExportMAKEFLAGS(bool); +bool Main_SetObjdir(bool, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); int mkTempFile(const char *, char *, size_t); int str2Lst_Append(StringList *, char *); void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); -Boolean GNode_ShouldExecute(GNode *gn); +bool GNode_ShouldExecute(GNode *gn); /* See if the node was seen on the left-hand side of a dependency operator. */ -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsTarget(const GNode *gn) { return (gn->type & OP_OPMASK) != 0; } MAKE_INLINE const char * GNode_Path(const GNode *gn) { return gn->path != NULL ? gn->path : gn->name; } -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsWaitingFor(const GNode *gn) { return (gn->flags & REMAKE) && gn->made <= REQUESTED; } -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsReady(const GNode *gn) { return gn->made > DEFERRED; } -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsDone(const GNode *gn) { return gn->made >= MADE; } -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsError(const GNode *gn) { return gn->made == ERROR || gn->made == ABORTED; } MAKE_INLINE const char * GNode_VarTarget(GNode *gn) { return GNode_ValueDirect(gn, TARGET); } MAKE_INLINE const char * GNode_VarOodate(GNode *gn) { return GNode_ValueDirect(gn, OODATE); } MAKE_INLINE const char * GNode_VarAllsrc(GNode *gn) { return GNode_ValueDirect(gn, ALLSRC); } MAKE_INLINE const char * GNode_VarImpsrc(GNode *gn) { return GNode_ValueDirect(gn, IMPSRC); } MAKE_INLINE const char * GNode_VarPrefix(GNode *gn) { return GNode_ValueDirect(gn, PREFIX); } MAKE_INLINE const char * GNode_VarArchive(GNode *gn) { return GNode_ValueDirect(gn, ARCHIVE); } MAKE_INLINE const char * GNode_VarMember(GNode *gn) { return GNode_ValueDirect(gn, MEMBER); } -#ifdef __GNUC__ +#if defined(__GNUC__) && __STDC_VERSION__ >= 199901L #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 -MAKE_INLINE Boolean +MAKE_INLINE bool ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; } -MAKE_INLINE Boolean +MAKE_INLINE bool ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; } -MAKE_INLINE Boolean +MAKE_INLINE bool ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; } -MAKE_INLINE Boolean +MAKE_INLINE bool ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; } -MAKE_INLINE Boolean +MAKE_INLINE bool 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); } MAKE_INLINE void cpp_skip_whitespace(const char **pp) { while (ch_isspace(**pp)) (*pp)++; } 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)++; } #if defined(lint) # define MAKE_RCSID(id) extern void do_not_define_rcsid(void) #elif defined(MAKE_NATIVE) # include # define MAKE_RCSID(id) __RCSID(id) #elif defined(MAKE_ALL_IN_ONE) && defined(__COUNTER__) # define MAKE_RCSID_CONCAT(x, y) CONCAT(x, y) # define MAKE_RCSID(id) static volatile char \ MAKE_RCSID_CONCAT(rcsid_, __COUNTER__)[] = id #elif defined(MAKE_ALL_IN_ONE) # define MAKE_RCSID(id) extern void do_not_define_rcsid(void) #else # define MAKE_RCSID(id) static volatile char rcsid[] = id #endif #endif /* MAKE_MAKE_H */ diff --git a/contrib/bmake/meta.c b/contrib/bmake/meta.c index b7f1831b3cdf..c1f78136fb7c 100644 --- a/contrib/bmake/meta.c +++ b/contrib/bmake/meta.c @@ -1,1719 +1,1721 @@ -/* $NetBSD: meta.c,v 1.177 2021/02/05 19:19:17 sjg Exp $ */ +/* $NetBSD: meta.c,v 1.181 2021/04/04 10:05:08 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 = LST_INIT; /* our scope of control */ static char *metaBailiwickStr; /* string storage for the list */ static StringList metaIgnorePaths = LST_INIT; /* 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; +bool useMeta = false; +static bool useFilemon = false; +static bool writeMeta = false; +static bool metaMissing = false; /* oodate if missing */ +static bool filemonMissing = false; /* oodate if missing */ +static bool metaEnv = false; /* don't save env unless asked */ +static bool metaVerbose = false; +static bool metaIgnoreCMDs = false; /* ignore CMDs in .meta files */ +static bool metaIgnorePatterns = false; /* do we need to do pattern matches */ +static bool metaIgnoreFilter = false; /* do we have more complex filtering? */ +static bool metaCurdirOk = false; /* write .meta in .CURDIR Ok? */ +static bool metaSilent = false; /* if we have a .meta be SILENT */ + +extern bool forceJobs; extern char **environ; #define MAKE_META_PREFIX ".MAKE.META.PREFIX" #ifndef N2U # define N2U(n, u) (((n) + ((u) - 1)) / (u)) #endif #ifndef ROUNDUP # define ROUNDUP(n, u) (N2U((n), (u)) * (u)) #endif #if !defined(HAVE_STRSEP) # define strsep(s, d) stresep((s), (d), '\0') #endif /* * Filemon is a kernel module which snoops certain syscalls. * * C chdir * E exec * F [v]fork * L [sym]link * M rename * R read * W write * S stat * * See meta_oodate below - we mainly care about 'E' and 'R'. * * We can still use meta mode without filemon, but * the benefits are more limited. */ #ifdef USE_FILEMON /* * Open the filemon device. */ static void meta_open_filemon(BuildMon *pbm) { int dupfd; pbm->mon_fd = -1; pbm->filemon = NULL; if (!useFilemon || pbm->mfp == NULL) return; pbm->filemon = filemon_open(); if (pbm->filemon == NULL) { - useFilemon = FALSE; + 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. */ if (!opts.compatMake) pbm->mon_fd = Job_TempFile("filemon.XXXXXX", NULL, 0); else pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL, 0); 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) { 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 != NULL) { 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 != NULL); } 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, '/')) != NULL) { if (cached_realpath(tname, buf) != NULL) { if ((rp = strrchr(buf, '/')) != NULL) { 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); } else { snprintf(buf, sizeof buf, "%s/%s", cwd, tname); } eat_dots(buf, sizeof buf, 1); /* ./ */ eat_dots(buf, sizeof buf, 2); /* ../ */ tname = buf; } } /* on some systems dirname may modify its arg */ tp = bmake_strdup(tname); 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 Boolean +static bool is_submake(const char *cmd, GNode *gn) { static const char *p_make = NULL; static size_t p_len; char *mp = NULL; char *cp; char *cp2; - Boolean rc = FALSE; + bool rc = false; if (p_make == NULL) { p_make = Var_Value(gn, ".MAKE").str; p_len = strlen(p_make); } cp = strchr(cmd, '$'); if (cp != NULL) { (void)Var_Subst(cmd, gn, VARE_WANTRES, &mp); /* TODO: handle errors */ cmd = mp; } cp2 = strstr(cmd, p_make); if (cp2 != NULL) { switch (cp2[p_len]) { case '\0': case ' ': case '\t': case '\n': - rc = TRUE; + rc = true; break; } if (cp2 > cmd && rc) { switch (cp2[-1]) { case ' ': case '\t': case '\n': break; default: - rc = FALSE; /* no match */ + rc = false; /* no match */ break; } } } free(mp); return rc; } -static Boolean +static bool any_is_submake(GNode *gn) { StringListNode *ln; for (ln = gn->commands.first; ln != NULL; ln = ln->next) if (is_submake(ln->datum, gn)) - return TRUE; - return FALSE; + return true; + return false; } static void -printCMD(const char *cmd, FILE *fp, GNode *gn) +printCMD(const char *ucmd, FILE *fp, GNode *gn) { - char *cmd_freeIt = NULL; + FStr xcmd = FStr_InitRefer(ucmd); - if (strchr(cmd, '$') != NULL) { - (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmd_freeIt); + if (strchr(ucmd, '$') != NULL) { + char *expanded; + (void)Var_Subst(ucmd, gn, VARE_WANTRES, &expanded); /* TODO: handle errors */ - cmd = cmd_freeIt; + xcmd = FStr_InitOwn(expanded); } - fprintf(fp, "CMD %s\n", cmd); - free(cmd_freeIt); + + fprintf(fp, "CMD %s\n", xcmd.str); + FStr_Done(&xcmd); } static void printCMDs(GNode *gn, FILE *fp) { StringListNode *ln; for (ln = gn->commands.first; ln != NULL; ln = ln->next) printCMD(ln->datum, fp, gn); } /* * 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; \ + return false; \ } \ -} while (/*CONSTCOND*/FALSE) +} while (/*CONSTCOND*/false) /* * Do we need/want a .meta file ? */ -static Boolean +static bool meta_needed(GNode *gn, const char *dname, - char *objdir_realpath, Boolean verbose) + char *objdir_realpath, bool verbose) { 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; + return false; } if ((gn->type & (OP_META|OP_SUBMAKE)) == OP_SUBMAKE) { /* OP_SUBMAKE is a bit too aggressive */ if (any_is_submake(gn)) { DEBUG1(META, "Skipping meta for %s: .SUBMAKE\n", gn->name); - return FALSE; + return false; } } /* The object directory may not exist. Check it.. */ if (cached_stat(dname, &cst) != 0) { if (verbose) debug_printf("Skipping meta for %s: no .OBJDIR\n", gn->name); - return FALSE; + return false; } /* make sure these are canonical */ if (cached_realpath(dname, objdir_realpath) != NULL) dname = objdir_realpath; /* 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 false; } - return TRUE; + return true; } static FILE * meta_create(BuildMon *pbm, GNode *gn) { FILE *fp; char buf[MAXPATHLEN]; char objdir_realpath[MAXPATHLEN]; char **ptr; FStr dname; const char *tname; char *fname; const char *cp; fp = NULL; dname = Var_Value(gn, ".OBJDIR"); tname = GNode_VarTarget(gn); /* if this succeeds objdir_realpath is realpath of dname */ - if (!meta_needed(gn, dname.str, objdir_realpath, TRUE)) + if (!meta_needed(gn, dname.str, objdir_realpath, true)) goto out; dname.str = objdir_realpath; 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[0] != '\0') fprintf(stdout, "%s\n", mp); free(mp); } /* Get the basename of the target */ cp = str_basename(tname); fflush(stdout); if (!writeMeta) /* Don't create meta data. */ goto out; fname = meta_name(pbm->meta_fname, sizeof pbm->meta_fname, dname.str, tname, objdir_realpath); #ifdef DEBUG_META_MODE DEBUG1(META, "meta_create: %s\n", fname); #endif if ((fp = fopen(fname, "w")) == NULL) err(1, "Could not open meta file '%s'", fname); fprintf(fp, "# Meta data file %s\n", fname); printCMDs(gn, fp); fprintf(fp, "CWD %s\n", getcwd(buf, sizeof buf)); fprintf(fp, "TARGET %s\n", tname); cp = GNode_VarOodate(gn); if (cp != NULL && *cp != '\0') { fprintf(fp, "OODATE %s\n", cp); } if (metaEnv) { for (ptr = environ; *ptr != NULL; ptr++) fprintf(fp, "ENV %s\n", *ptr); } fprintf(fp, "-- command output --\n"); fflush(fp); Global_Append(".MAKE.META.FILES", fname); Global_Append(".MAKE.META.CREATED", fname); gn->type |= OP_META; /* in case anyone wants to know */ if (metaSilent) { gn->type |= OP_SILENT; } out: FStr_Done(&dname); return fp; } -static Boolean +static bool boolValue(char *s) { switch(*s) { case '0': case 'N': case 'n': case 'F': case 'f': - return FALSE; + return false; } - return TRUE; + 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 */ Global_Set(".MAKE.PATH_FILEMON", filemon_path()); #endif } #define get_mode_bf(bf, token) \ if ((cp = strstr(make_mode, token)) != NULL) \ bf = boolValue(cp + sizeof (token) - 1) /* * Initialization we need after reading makefiles. */ void meta_mode_init(const char *make_mode) { - static Boolean once = FALSE; + static bool once = false; char *cp; FStr value; - useMeta = TRUE; - useFilemon = TRUE; - writeMeta = TRUE; + useMeta = true; + useFilemon = true; + writeMeta = true; if (make_mode != NULL) { if (strstr(make_mode, "env") != NULL) - metaEnv = TRUE; + metaEnv = true; if (strstr(make_mode, "verb") != NULL) - metaVerbose = TRUE; + metaVerbose = true; if (strstr(make_mode, "read") != NULL) - writeMeta = FALSE; + writeMeta = false; if (strstr(make_mode, "nofilemon") != NULL) - useFilemon = FALSE; + useFilemon = false; if (strstr(make_mode, "ignore-cmd") != NULL) - metaIgnoreCMDs = TRUE; + 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(SCOPE_GLOBAL, MAKE_META_PREFIX)) { /* * 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. */ Global_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}"); } if (once) return; - once = TRUE; + once = true; memset(&Mybm, 0, sizeof Mybm); /* * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} */ (void)Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", SCOPE_GLOBAL, VARE_WANTRES, &metaBailiwickStr); /* TODO: handle errors */ str2Lst_Append(&metaBailiwick, metaBailiwickStr); /* * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} */ Global_Append(MAKE_META_IGNORE_PATHS, "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}"); (void)Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", SCOPE_GLOBAL, VARE_WANTRES, &metaIgnorePathsStr); /* TODO: handle errors */ str2Lst_Append(&metaIgnorePaths, metaIgnorePathsStr); /* * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS} */ value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_PATTERNS); if (value.str != NULL) { - metaIgnorePatterns = TRUE; + metaIgnorePatterns = true; FStr_Done(&value); } value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_FILTER); if (value.str != NULL) { - metaIgnoreFilter = TRUE; + metaIgnoreFilter = true; FStr_Done(&value); } } /* * 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 != NULL) { 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 != NULL) { 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 != NULL) { 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 != NULL) { return filemon_process(pbm->filemon); } #endif return 0; } void -meta_job_error(Job *job, GNode *gn, Boolean ignerr, int status) +meta_job_error(Job *job, GNode *gn, bool ignerr, int status) { char cwd[MAXPATHLEN]; BuildMon *pbm; if (job != NULL) { pbm = &job->bm; if (gn == NULL) gn = job->node; } else { pbm = &Mybm; } if (pbm->mfp != NULL) { fprintf(pbm->mfp, "\n*** Error code %d%s\n", status, ignerr ? "(ignored)" : ""); } if (gn != NULL) Global_Set(".ERROR_TARGET", GNode_Path(gn)); getcwd(cwd, sizeof cwd); Global_Set(".ERROR_CWD", cwd); if (pbm->meta_fname[0] != '\0') { Global_Set(".ERROR_META_FILE", pbm->meta_fname); } 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 == NULL) { char *cp2; (void)Var_Subst("${" MAKE_META_PREFIX "}", SCOPE_GLOBAL, VARE_WANTRES, &meta_prefix); /* TODO: handle errors */ if ((cp2 = strchr(meta_prefix, '$')) != NULL) 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 == NULL) return; cp++; } } 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 == NULL) pbm = &Mybm; #ifdef USE_FILEMON if (pbm->filemon != NULL) { 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; return error; } #endif 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) { Lst_Done(&metaBailiwick); free(metaBailiwickStr); Lst_Done(&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 %u -> %u\n", (unsigned)bufsz, (unsigned)newsz); p = bmake_realloc(buf, newsz); *bufp = buf = p; *szp = bufsz = newsz; /* fetch the rest */ if (fgets(&buf[x], (int)bufsz - x, fp) == NULL) return x; /* truncated! */ goto check_newline; } } return 0; } -static Boolean +static bool prefix_match(const char *prefix, const char *path) { size_t n = strlen(prefix); return strncmp(path, prefix, n) == 0; } -static Boolean +static bool has_any_prefix(const char *path, StringList *prefixes) { StringListNode *ln; for (ln = prefixes->first; ln != NULL; ln = ln->next) if (prefix_match(ln->datum, path)) - return TRUE; - return FALSE; + return true; + return false; } /* See if the path equals prefix or starts with "prefix/". */ -static Boolean +static bool path_starts_with(const char *path, const char *prefix) { size_t n = strlen(prefix); if (strncmp(path, prefix, n) != 0) - return FALSE; + return false; return path[n] == '\0' || path[n] == '/'; } -static Boolean +static bool meta_ignore(GNode *gn, const char *p) { char fname[MAXPATHLEN]; if (p == NULL) - return TRUE; + return true; if (*p == '/') { cached_realpath(p, fname); /* clean it up */ if (has_any_prefix(fname, &metaIgnorePaths)) { #ifdef DEBUG_META_MODE DEBUG1(META, "meta_oodate: ignoring path: %s\n", p); #endif - return TRUE; + return true; } } if (metaIgnorePatterns) { const char *expr; char *pm; /* * XXX: This variable is set on a target GNode but is not one of * the usual local variables. It should be deleted afterwards. * Ideally it would not be created in the first place, just like * in a .for loop. */ Var_Set(gn, ".p.", p); expr = "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}"; (void)Var_Subst(expr, gn, VARE_WANTRES, &pm); /* TODO: handle errors */ if (pm[0] != '\0') { #ifdef DEBUG_META_MODE DEBUG1(META, "meta_oodate: ignoring pattern: %s\n", p); #endif free(pm); - return TRUE; + return true; } free(pm); } if (metaIgnoreFilter) { char *fm; /* skip if filter result is empty */ 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; + return true; } free(fm); } - return FALSE; + 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. + * Setting oodate true will have that effect. */ #define CHECK_VALID_META(p) if (!(p != NULL && *p != '\0')) { \ warnx("%s: %d: malformed", fname, lineno); \ - oodate = TRUE; \ + oodate = true; \ continue; \ } #define DEQUOTE(p) if (*p == '\'') { \ char *ep; \ p++; \ if ((ep = strchr(p, '\'')) != NULL) \ *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) +bool +meta_oodate(GNode *gn, bool 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]; FStr dname; const char *tname; char *p; char *link_src; char *move_target; static size_t cwdlen = 0; static size_t tmplen = 0; FILE *fp; - Boolean needOODATE = FALSE; + bool needOODATE = false; StringList missingFiles; - Boolean have_filemon = FALSE; + bool have_filemon = false; if (oodate) return oodate; /* we're done */ dname = Var_Value(gn, ".OBJDIR"); tname = GNode_VarTarget(gn); /* if this succeeds fname3 is realpath of dname */ - if (!meta_needed(gn, dname.str, fname3, FALSE)) + if (!meta_needed(gn, dname.str, fname3, false)) goto oodate_out; dname.str = fname3; Lst_Init(&missingFiles); /* * 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); + GNode_SetLocalVars(gn); meta_name(fname, sizeof fname, dname.str, tname, dname.str); #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 cached_stat cst; if (buf == NULL) { bufsz = 8 * BUFSIZ; buf = bmake_malloc(bufsz); } 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); if (tmpdir == NULL) { tmpdir = getTmpdir(); tmplen = strlen(tmpdir); } /* we want to track all the .meta we read */ Global_Append(".MAKE.META.FILES", fname); 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; + 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; + have_filemon = true; continue; } if (strncmp(buf, "# buildmon", 10) == 0) { - have_filemon = TRUE; + 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) { FStr ldir; if (lastpid > 0) { /* We need to remember these. */ Global_SetExpand(lcwd_vname, lcwd); Global_SetExpand(ldir_vname, latestdir); } 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(SCOPE_GLOBAL, ldir_vname); if (ldir.str != NULL) { strlcpy(latestdir, ldir.str, sizeof latestdir); FStr_Done(&ldir); } ldir = Var_Value(SCOPE_GLOBAL, lcwd_vname); if (ldir.str != NULL) { strlcpy(lcwd, ldir.str, sizeof lcwd); FStr_Done(&ldir); } } /* 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_DeleteExpand(SCOPE_GLOBAL, lcwd_vname); Var_DeleteExpand(SCOPE_GLOBAL, ldir_vname); 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); Global_SetExpand(cldir, lcwd); snprintf(cldir, sizeof cldir, LDIR_VNAME_FMT, child); Global_SetExpand(cldir, latestdir); #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); Global_SetExpand(lcwd_vname, lcwd); Global_SetExpand(ldir_vname, lcwd); #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. */ { char *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 (!has_any_prefix(p, &metaBailiwick)) 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) != NULL) break; 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; - Boolean found = FALSE; + bool found = false; if (*p == '/') { sdirs[sdx++] = p; /* done */ } else { if (strcmp(".", p) == 0) continue; /* no point */ /* Check vs latestdir */ snprintf(fname1, sizeof fname1, "%s/%s", latestdir, p); sdirs[sdx++] = fname1; if (strcmp(latestdir, lcwd) != 0) { /* Check vs lcwd */ snprintf(fname2, sizeof fname2, "%s/%s", lcwd, p); sdirs[sdx++] = fname2; } if (strcmp(lcwd, cwd) != 0) { /* Check vs cwd */ snprintf(fname3, sizeof fname3, "%s/%s", cwd, p); sdirs[sdx++] = fname3; } } sdirs[sdx++] = NULL; for (sdp = sdirs; *sdp != NULL && !found; sdp++) { #ifdef DEBUG_META_MODE DEBUG3(META, "%s: %d: looking for: %s\n", fname, lineno, *sdp); #endif if (cached_stat(*sdp, &cst) == 0) { - found = TRUE; + found = true; p = *sdp; } } if (found) { #ifdef DEBUG_META_MODE DEBUG3(META, "%s: %d: found: %s\n", fname, lineno, p); #endif 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; + oodate = true; } 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); } 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; + oodate = true; } else { const char *cp; char *cmd = cmdNode->datum; - Boolean hasOODATE = FALSE; + bool hasOODATE = false; if (strstr(cmd, "$?") != NULL) - hasOODATE = TRUE; + hasOODATE = true; else if ((cp = strstr(cmd, ".OODATE")) != NULL) { /* check for $[{(].OODATE[:)}] */ if (cp > cmd + 2 && cp[-2] == '$') - hasOODATE = TRUE; + hasOODATE = true; } if (hasOODATE) { - needOODATE = TRUE; + needOODATE = true; DEBUG2(META, "%s: %d: cannot compare command using .OODATE\n", fname, lineno); } - (void)Var_Subst(cmd, gn, VARE_WANTRES|VARE_UNDEFERR, &cmd); + (void)Var_Subst(cmd, gn, VARE_UNDEFERR, &cmd); /* TODO: handle errors */ if ((cp = strchr(cmd, '\n')) != NULL) { 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 + 1, '\n'); } while (cp != NULL); if (buf[x - 1] == '\n') buf[x - 1] = '\0'; } if (p != NULL && !hasOODATE && !(gn->type & OP_NOMETA_CMP) && (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; + 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; + 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; + oodate = true; } } } fclose(fp); if (!Lst_IsEmpty(&missingFiles)) { DEBUG2(META, "%s: missing files: %s...\n", fname, (char *)missingFiles.first->datum); - oodate = TRUE; + oodate = true; } if (!oodate && !have_filemon && filemonMissing) { DEBUG1(META, "%s: missing filemon data\n", fname); - oodate = TRUE; + oodate = true; } } else { if (writeMeta && (metaMissing || (gn->type & OP_META))) { const char *cp = NULL; /* if target is in .CURDIR we do not need a meta file */ if (gn->path != NULL && (cp = strrchr(gn->path, '/')) != NULL && (cp > gn->path)) { if (strncmp(curdir, gn->path, (size_t)(cp - gn->path)) != 0) { cp = NULL; /* not in .CURDIR */ } } if (cp == NULL) { DEBUG1(META, "%s: required but missing\n", fname); - oodate = TRUE; - needOODATE = TRUE; /* assume the worst */ + oodate = true; + needOODATE = true; /* assume the worst */ } } } Lst_DoneCall(&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(gn, OODATE); Var_Set(gn, OODATE, GNode_VarAllsrc(gn)); } oodate_out: FStr_Done(&dname); 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 != NULL ? 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) != 0) do { /* XXX this is not line-buffered */ 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 (/*CONSTCOND*/FALSE); + } while (/*CONSTCOND*/false); if (metafd != -1 && FD_ISSET(metafd, &readfds) != 0) { if (meta_job_event(NULL) <= 0) metafd = -1; } } } #endif /* USE_META */ diff --git a/contrib/bmake/meta.h b/contrib/bmake/meta.h index 1fc8910d3b65..d4f02da4e78b 100644 --- a/contrib/bmake/meta.h +++ b/contrib/bmake/meta.h @@ -1,60 +1,60 @@ -/* $NetBSD: meta.h,v 1.9 2020/12/10 20:49:11 rillig Exp $ */ +/* $NetBSD: meta.h,v 1.10 2021/04/03 11:08:40 rillig Exp $ */ /* * Things needed for 'meta' mode. */ /* * Copyright (c) 2009-2010, Juniper Networks, 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. Neither the name of the copyright holders 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 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. */ typedef struct BuildMon { char meta_fname[MAXPATHLEN]; struct filemon *filemon; int mon_fd; FILE *mfp; } BuildMon; struct Job; void meta_init(void); void meta_finish(void); void meta_mode_init(const char *); void meta_job_start(struct Job *, GNode *); void meta_job_child(struct Job *); void meta_job_parent(struct Job *, pid_t); int meta_job_fd(struct Job *); int meta_job_event(struct Job *); -void meta_job_error(struct Job *, GNode *, Boolean, int); +void meta_job_error(struct Job *, GNode *, bool, int); void meta_job_output(struct Job *, char *, const char *); int meta_cmd_finish(void *); int meta_job_finish(struct Job *); -Boolean meta_oodate(GNode *, Boolean); +bool meta_oodate(GNode *, bool); void meta_compat_start(void); void meta_compat_child(void); void meta_compat_parent(pid_t); -extern Boolean useMeta; +extern bool useMeta; diff --git a/contrib/bmake/metachar.h b/contrib/bmake/metachar.h index 04f967229109..1fd1397cfe63 100644 --- a/contrib/bmake/metachar.h +++ b/contrib/bmake/metachar.h @@ -1,48 +1,48 @@ -/* $NetBSD: metachar.h,v 1.15 2021/01/19 20:51:46 rillig Exp $ */ +/* $NetBSD: metachar.h,v 1.16 2021/04/03 11:08:40 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] != 0) -MAKE_INLINE Boolean +MAKE_INLINE bool needshell(const char *cmd) { while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=') cmd++; return *cmd != '\0'; } #endif /* MAKE_METACHAR_H */ diff --git a/contrib/bmake/mk/ChangeLog b/contrib/bmake/mk/ChangeLog index fa6ea9b9d337..f73c4fb68c6b 100644 --- a/contrib/bmake/mk/ChangeLog +++ b/contrib/bmake/mk/ChangeLog @@ -1,1764 +1,1799 @@ +2021-06-16 Simon J Gerraty + + * install-mk (MK_VERSION): 20210616 + + * dirdeps.mk: when using .MAKE.DEPENDFILE_PREFERENCE to find + depend files to read, anchor MACHINE at , or end of string + to avoid prefix match. + +2021-05-04 Simon J Gerraty + + * install-mk (MK_VERSION): 20210504 + + * dirdeps.mk: re-implement ALL_MACHINES support to better + cater for local complexities, when ONLY_TARGET_SPEC_LIST + is not set. local.dirdeps.mk can set + DIRDEPS_ALL_MACHINES_FILTER and/or + DIRDEPS_ALL_MACHINES_FILTER_XTRAS to filter the results we get + from listing all existing Makefile.depend.* + +2021-04-20 Simon J Gerraty + + * install-mk (MK_VERSION): 20210420 + + * dirdeps.mk: revert previous - not always safe. + +2021-03-20 Simon J Gerraty + + * install-mk (MK_VERSION): 20210321 + + * dirdeps.mk: when generating dirdeps.cache + we only need to hook the initial DIRDEPS to the + dirdeps target. That and any _build_xtra_dirs (like tests which + should not be hooked directly to the dependency graph - to avoid + cycles) + 2021-01-30 Simon J Gerraty * install-mk (MK_VERSION): 20210130 * dirdeps.mk: expr 2 - 1 - 1 exits with a bad status we need to guard against this in DIRDEP_LOADAVG_REPORT. * dirdeps.mk: restore respect for TARGET_MACHINE 2021-01-06 Simon J Gerraty * install-mk (MK_VERSION): 20210101 * dirdeps.mk: first time we are read, just use TARGET_SPEC for _DEP_TARGET_SPEC 2020-12-22 Simon J Gerraty * sys.mk (MAKE_SHELL): use ${.SHELL:Ush} and use := when setting SHELL 2020-12-21 Simon J Gerraty * install-mk (MK_VERSION): 20201221 * dirdeps-options.mk: latest bmake allows only one arg to .undef 2020-12-11 Simon J Gerraty * dirdeps-targets.mk: allow for "." in DIRDEPS_TARGETS_DIRS so that any directory can be treated as a target. 2020-11-26 Simon J Gerraty * install-mk (MK_VERSION): 20201126 * own.mk: use .MAKE.{UID,GID} if available. * init.mk: suppress _SKIP_BUILD warning if doing -V 2020-11-20 Simon J Gerraty * install-mk (MK_VERSION): 20201120 * init.mk: rename LEVEL0_TARGETS to DIRDEPS_BUILD_LEVEL0_TARGETS * dirdeps-targets.mk: fix typo in comment 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 diff --git a/contrib/bmake/mk/dirdeps.mk b/contrib/bmake/mk/dirdeps.mk index 4eb20cedfec7..38ead3de37cd 100644 --- a/contrib/bmake/mk/dirdeps.mk +++ b/contrib/bmake/mk/dirdeps.mk @@ -1,858 +1,914 @@ -# $Id: dirdeps.mk,v 1.133 2021/01/31 04:39:22 sjg Exp $ +# $Id: dirdeps.mk,v 1.140 2021/06/20 23:42:38 sjg Exp $ # Copyright (c) 2010-2021, Simon J. Gerraty # Copyright (c) 2010-2018, Juniper Networks, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Much of the complexity here is for supporting cross-building. # If a tree does not support that, simply using plain Makefile.depend # should provide sufficient clue. # Otherwise the recommendation is to use Makefile.depend.${MACHINE} # as expected below. # Note: this file gets multiply included. # This is what we do with DIRDEPS # DIRDEPS: # This is a list of directories - relative to SRCTOP, it is # normally only of interest to .MAKE.LEVEL 0. # In some cases the entry may be qualified with a . # or . suffix (see TARGET_SPEC_VARS below), # for example to force building something for the pseudo # machines "host" or "common" regardless of current ${MACHINE}. # # All unqualified entries end up being qualified with .${TARGET_SPEC} # and partially qualified (if TARGET_SPEC_VARS has multiple # entries) are also expanded to a full .. # The _DIRDEP_USE target uses the suffix to set TARGET_SPEC # correctly when visiting each entry. # # The fully qualified directory entries are used to construct a # dependency graph that will drive the build later. # # Also, for each fully qualified directory target, we will search # using ${.MAKE.DEPENDFILE_PREFERENCE} to find additional # dependencies. We use Makefile.depend (default value for # .MAKE.DEPENDFILE_PREFIX) to refer to these makefiles to # distinguish them from others. # # Before each Makefile.depend file is read, we set # DEP_RELDIR to be the RELDIR (path relative to SRCTOP) for # its directory, and DEP_MACHINE etc according to the . # represented by the suffix of the corresponding target. # # Since each Makefile.depend file includes dirdeps.mk, this # processing is recursive and results in .MAKE.LEVEL 0 learning the # dependencies of the tree wrt the initial directory (_DEP_RELDIR). # # TARGET_SPEC_VARS # The default value is just MACHINE, and for most environments # this is sufficient. The _DIRDEP_USE target actually sets # both MACHINE and TARGET_SPEC to the suffix of the current # target so that in the general case TARGET_SPEC can be ignored. # # If more than MACHINE is needed then sys.mk needs to decompose # TARGET_SPEC and set the relevant variables accordingly. # It is important that MACHINE be included in and actually be # the first member of TARGET_SPEC_VARS. This allows other # variables to be considered optional, and some of the treatment # below relies on MACHINE being the first entry. # Note: TARGET_SPEC cannot contain any '.'s so the target # triple used by compiler folk won't work (directly anyway). # # For example: # # # Always list MACHINE first, # # other variables might be optional. # TARGET_SPEC_VARS = MACHINE TARGET_OS # .if ${TARGET_SPEC:Uno:M*,*} != "" # _tspec := ${TARGET_SPEC:S/,/ /g} # MACHINE := ${_tspec:[1]} # TARGET_OS := ${_tspec:[2]} # # etc. # # We need to stop that TARGET_SPEC affecting any submakes # # and deal with MACHINE=${TARGET_SPEC} in the environment. # TARGET_SPEC = # # export but do not track # .export-env TARGET_SPEC # .export ${TARGET_SPEC_VARS} # .for v in ${TARGET_SPEC_VARS:O:u} # .if empty($v) # .undef $v # .endif # .endfor # .endif # # make sure we know what TARGET_SPEC is # # as we may need it to find Makefile.depend* # TARGET_SPEC = ${TARGET_SPEC_VARS:@v@${$v:U}@:ts,} # # The following variables can influence the initial DIRDEPS # computation with regard to the TARGET_SPECs that will be # built. # Most should also be considered by init.mk # # ONLY_TARGET_SPEC_LIST # Defines a list of TARGET_SPECs for which the current # directory can be built. # If ALL_MACHINES is defined, we build for all the # TARGET_SPECs listed. # # ONLY_MACHINE_LIST # As for ONLY_TARGET_SPEC_LIST but only specifies # MACHINEs. # # NOT_TARGET_SPEC_LIST # A list of TARGET_SPECs for which the current # directory should not be built. # # NOT_MACHINE_LIST # A list of MACHINEs the current directory should not be # built for. # # _build_xtra_dirs # local.dirdeps.mk can add targets to this variable. # They will be hooked into the build, but independent of # any other DIRDEP. # # This allows for adding TESTS to the build, such that the build # if any test fails, but without the risk of introducing # circular dependencies. now_utc ?= ${%s:L:gmtime} .if !defined(start_utc) start_utc := ${now_utc} .endif .if !target(bootstrap) && (make(bootstrap) || \ make(bootstrap-this) || \ make(bootstrap-recurse) || \ make(bootstrap-empty)) # disable most of below .MAKE.LEVEL = 1 .endif # touch this at your peril _DIRDEP_USE_LEVEL?= 0 .if ${.MAKE.LEVEL} == ${_DIRDEP_USE_LEVEL} # only the first instance is interested in all this .if !target(_DIRDEP_USE) # do some setup we only need once _CURDIR ?= ${.CURDIR} _OBJDIR ?= ${.OBJDIR} .if ${MAKEFILE:T} == ${.PARSEFILE} && empty(DIRDEPS) && ${.TARGETS:Uall:M*/*} != "" # This little trick let's us do # # mk -f dirdeps.mk some/dir.${TARGET_SPEC} # all: ${.TARGETS:Nall}: all DIRDEPS := ${.TARGETS:M*[/.]*} # so that -DNO_DIRDEPS works DEP_RELDIR := ${DIRDEPS:[1]:R} # this will become DEP_MACHINE below TARGET_MACHINE := ${DIRDEPS:[1]:E:C/,.*//} .if ${TARGET_MACHINE:N*/*} == "" TARGET_MACHINE := ${MACHINE} .endif # disable DIRDEPS_CACHE as it does not like this trick MK_DIRDEPS_CACHE = no .endif # make sure we get the behavior we expect .MAKE.SAVE_DOLLARS = no # make sure these are empty to start with _DEP_TARGET_SPEC = # If TARGET_SPEC_VARS is other than just MACHINE # it should be set by sys.mk or similar by now. # TARGET_SPEC must not contain any '.'s. TARGET_SPEC_VARS ?= MACHINE # this is what we started with TARGET_SPEC = ${TARGET_SPEC_VARS:@v@${$v:U}@:ts,} # this is what we mostly use below DEP_TARGET_SPEC = ${TARGET_SPEC_VARS:S,^,DEP_,:@v@${$v:U}@:ts,} # make sure we have defaults .for v in ${TARGET_SPEC_VARS} DEP_$v ?= ${$v} .endfor .if ${TARGET_SPEC_VARS:[#]} > 1 # Ok, this gets more complex (putting it mildly). # In order to stay sane, we need to ensure that all the build_dirs # we compute below are fully qualified wrt DEP_TARGET_SPEC. # The makefiles may only partially specify (eg. MACHINE only), # so we need to construct a set of modifiers to fill in the gaps. .if ${MAKE_VERSION} >= 20170130 _tspec_x := ${TARGET_SPEC_VARS:range} .else # do it the hard way _tspec_x := ${TARGET_SPEC_VARS:[#]:@x@i=1;while [ $$i -le $x ]; do echo $$i; i=$$((i + 1)); done;@:sh} .endif # this handles unqualified entries M_dep_qual_fixes = C;(/[^/.,]+)$$;\1.$${DEP_TARGET_SPEC}; # there needs to be at least one item missing for these to make sense .for i in ${_tspec_x:[2..-1]} _tspec_m$i := ${TARGET_SPEC_VARS:[2..$i]:@w@[^,]+@:ts,} _tspec_a$i := ,${TARGET_SPEC_VARS:[$i..-1]:@v@$$$${DEP_$v}@:ts,} M_dep_qual_fixes += C;(\.${_tspec_m$i})$$;\1${_tspec_a$i}; .endfor TARGET_SPEC_VARSr := ${TARGET_SPEC_VARS:[-1..1]} .else # A harmless? default. M_dep_qual_fixes = U .endif .if !defined(.MAKE.DEPENDFILE_PREFERENCE) # .MAKE.DEPENDFILE_PREFERENCE makes the logic below neater? # you really want this set by sys.mk or similar .MAKE.DEPENDFILE_PREFERENCE = ${_CURDIR}/${.MAKE.DEPENDFILE:T} .if ${.MAKE.DEPENDFILE:E} == "${TARGET_SPEC}" .if ${TARGET_SPEC} != ${MACHINE} .MAKE.DEPENDFILE_PREFERENCE += ${_CURDIR}/${.MAKE.DEPENDFILE:T:R}.$${MACHINE} .endif .MAKE.DEPENDFILE_PREFERENCE += ${_CURDIR}/${.MAKE.DEPENDFILE:T:R} .endif .endif _default_dependfile := ${.MAKE.DEPENDFILE_PREFERENCE:[1]:T} _machine_dependfiles := ${.MAKE.DEPENDFILE_PREFERENCE:T:M*${MACHINE}*} # for machine specific dependfiles we require ${MACHINE} to be at the end # also for the sake of sanity we require a common prefix .if !defined(.MAKE.DEPENDFILE_PREFIX) # knowing .MAKE.DEPENDFILE_PREFIX helps .if !empty(_machine_dependfiles) .MAKE.DEPENDFILE_PREFIX := ${_machine_dependfiles:[1]:T:R} .else .MAKE.DEPENDFILE_PREFIX := ${_default_dependfile:T} .endif .endif # this is how we identify non-machine specific dependfiles N_notmachine := ${.MAKE.DEPENDFILE_PREFERENCE:E:N*${MACHINE}*:${M_ListToSkip}} .endif # !target(_DIRDEP_USE) # First off, we want to know what ${MACHINE} to build for. # This can be complicated if we are using a mixture of ${MACHINE} specific # and non-specific Makefile.depend* # if we were included recursively _DEP_TARGET_SPEC should be valid. .if empty(_DEP_TARGET_SPEC) DEP_MACHINE = ${TARGET_MACHINE:U${MACHINE}} _DEP_TARGET_SPEC := ${DEP_TARGET_SPEC} .if ${.INCLUDEDFROMFILE:U:M${.MAKE.DEPENDFILE_PREFIX}*} != "" # record that we've read dependfile for this _dirdeps_checked.${_CURDIR}.${TARGET_SPEC}: .endif .endif # by now _DEP_TARGET_SPEC should be set, parse it. .if ${TARGET_SPEC_VARS:[#]} > 1 # we need to parse DEP_MACHINE may or may not contain more info _tspec := ${_DEP_TARGET_SPEC:S/,/ /g} .for i in ${_tspec_x} DEP_${TARGET_SPEC_VARS:[$i]} := ${_tspec:[$i]} .endfor .for v in ${TARGET_SPEC_VARS:O:u} .if empty(DEP_$v) .undef DEP_$v .endif .endfor .else DEP_MACHINE := ${_DEP_TARGET_SPEC} .endif # reset each time through _build_all_dirs = _build_xtra_dirs = # the first time we are included the _DIRDEP_USE target will not be defined # we can use this as a clue to do initialization and other one time things. .if !target(_DIRDEP_USE) # make sure this target exists dirdeps: beforedirdeps .WAIT beforedirdeps: # We normally expect to be included by Makefile.depend.* # which sets the DEP_* macros below. DEP_RELDIR ?= ${RELDIR} # this can cause lots of output! # set to a set of glob expressions that might match RELDIR DEBUG_DIRDEPS ?= no # remember the initial value of DEP_RELDIR - we test for it below. _DEP_RELDIR := ${DEP_RELDIR} .endif # DIRDEPS_CACHE can be very handy for debugging. # Also if repeatedly building the same target, # we can avoid the overhead of re-computing the tree dependencies. MK_DIRDEPS_CACHE ?= no BUILD_DIRDEPS_CACHE ?= no BUILD_DIRDEPS ?= yes .if ${MK_DIRDEPS_CACHE} == "yes" # this is where we will cache all our work DIRDEPS_CACHE ?= ${_OBJDIR:tA}/dirdeps.cache${_TARGETS:U${.TARGETS}:Nall:O:u:ts-:S,/,_,g:S,^,.,:N.} .endif .if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != "" _debug_reldir = 1 .else _debug_reldir = 0 .endif .if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend:L:M$x}@} != "" _debug_search = 1 .else _debug_search = 0 .endif # pickup customizations # as below you can use !target(_DIRDEP_USE) to protect things # which should only be done once. .-include .if !target(_DIRDEP_USE) # things we skip for host tools SKIP_HOSTDIR ?= NSkipHostDir = ${SKIP_HOSTDIR:N*.host*:S,$,.host*,:N.host*:S,^,${SRCTOP}/,:${M_ListToSkip}} # things we always skip # SKIP_DIRDEPS allows for adding entries on command line. SKIP_DIR += .host *.WAIT ${SKIP_DIRDEPS} SKIP_DIR.host += ${SKIP_HOSTDIR} DEP_SKIP_DIR = ${SKIP_DIR} \ ${SKIP_DIR.${DEP_TARGET_SPEC}:U} \ ${TARGET_SPEC_VARS:@v@${SKIP_DIR.${DEP_$v}:U}@} \ ${SKIP_DIRDEPS.${DEP_TARGET_SPEC}:U} \ ${TARGET_SPEC_VARS:@v@${SKIP_DIRDEPS.${DEP_$v}:U}@} NSkipDir = ${DEP_SKIP_DIR:${M_ListToSkip}} .if defined(NODIRDEPS) || defined(WITHOUT_DIRDEPS) NO_DIRDEPS = .elif defined(WITHOUT_DIRDEPS_BELOW) NO_DIRDEPS_BELOW = .endif .if defined(NO_DIRDEPS) # confine ourselves to the original dir and below. DIRDEPS_FILTER += M${_DEP_RELDIR}* .elif defined(NO_DIRDEPS_BELOW) DIRDEPS_FILTER += M${_DEP_RELDIR} .endif # this is what we run below DIRDEP_MAKE ?= ${.MAKE} DIRDEP_DIR ?= ${.TARGET:R} # if you want us to report load averages during build # DIRDEP_USE_PRELUDE += ${DIRDEP_LOADAVG_REPORT}; DIRDEP_LOADAVG_CMD ?= ${UPTIME:Uuptime} | sed 's,.*\(load\),\1,' DIRDEP_LOADAVG_LAST = 0 # yes the expression here is a bit complicated, # the trick is to only eval ${DIRDEP_LOADAVG_LAST::=${now_utc}} # when we want to report. # Note: expr(1) will exit 1 if the expression evaluates to 0 # hence the || true DIRDEP_LOADAVG_REPORT = \ test -z "${"${expr ${now_utc} - ${DIRDEP_LOADAVG_INTEVAL:U60} - ${DIRDEP_LOADAVG_LAST} || true:L:sh:N-*}":?yes${DIRDEP_LOADAVG_LAST::=${now_utc}}:}" || \ echo "${TRACER}`${DIRDEP_LOADAVG_CMD}`" # we suppress SUBDIR when visiting the leaves # we assume sys.mk will set MACHINE_ARCH # you can add extras to DIRDEP_USE_ENV # if there is no makefile in the target directory, we skip it. _DIRDEP_USE: .USE .MAKE @for m in ${.MAKE.MAKEFILE_PREFERENCE}; do \ test -s ${.TARGET:R}/$$m || continue; \ echo "${TRACER}Checking ${.TARGET:S,${SRCTOP}/,,} for ${.TARGET:E} ..."; \ ${DIRDEP_USE_PRELUDE} \ MACHINE_ARCH= NO_SUBDIR=1 ${DIRDEP_USE_ENV} \ TARGET_SPEC=${.TARGET:E} \ MACHINE=${.TARGET:E} \ ${DIRDEP_MAKE} -C ${DIRDEP_DIR} || exit 1; \ break; \ done .ifdef ALL_MACHINES -# this is how you limit it to only the machines we have been built for -# previously. .if empty(ONLY_TARGET_SPEC_LIST) && empty(ONLY_MACHINE_LIST) -.if !empty(ALL_MACHINE_LIST) -# ALL_MACHINE_LIST is the list of all legal machines - ignore anything else -_machine_list != cd ${_CURDIR} && 'ls' -1 ${ALL_MACHINE_LIST:O:u:@m@${.MAKE.DEPENDFILE:T:R}.$m@} 2> /dev/null; echo +# we start with everything +_machine_list != echo; 'ls' -1 ${_CURDIR}/${.MAKE.DEPENDFILE_PREFIX}* 2> /dev/null + +# some things we know we want to ignore +DIRDEPS_TARGETS_SKIP_LIST += \ + *~ \ + *.bak \ + *.inc \ + *.old \ + *.options \ + *.orig \ + *.rej \ + +# first trim things we know we want to skip +# and provide canonical form +_machine_list := ${_machine_list:${DIRDEPS_TARGETS_SKIP_LIST:${M_ListToSkip}}:T:E} + +# cater for local complexities +# local.dirdeps.mk can set +# DIRDEPS_ALL_MACHINES_FILTER and +# DIRDEPS_ALL_MACHINES_FILTER_XTRAS for final tweaks + +.if !empty(ALL_TARGET_SPEC_LIST) +.if ${_debug_reldir} +.info ALL_TARGET_SPEC_LIST=${ALL_TARGET_SPEC_LIST} +.endif +DIRDEPS_ALL_MACHINES_FILTER += \ + @x@$${ALL_TARGET_SPEC_LIST:@s@$${x:M$$s}@}@ +.elif !empty(ALL_MACHINE_LIST) +.if ${_debug_reldir} +.info ALL_MACHINE_LIST=${ALL_MACHINE_LIST} +.endif +.if ${TARGET_SPEC_VARS:[#]} > 1 +# the space below can result in extraneous ':' +DIRDEPS_ALL_MACHINES_FILTER += \ + @x@$${ALL_MACHINE_LIST:@m@$${x:M$$m,*} $${x:M$$m}@}@ .else -_machine_list != 'ls' -1 ${_CURDIR}/${.MAKE.DEPENDFILE_PREFIX}.* 2> /dev/null; echo +DIRDEPS_ALL_MACHINES_FILTER += \ + @x@$${ALL_MACHINE_LIST:@m@$${x:M$$m}@}@ .endif -_only_machines := ${_machine_list:${NIgnoreFiles:UN*.bak}:E:O:u} +.endif +# add local XTRAS - default to something benign +DIRDEPS_ALL_MACHINES_FILTER += \ + ${DIRDEPS_ALL_MACHINES_FILTER_XTRAS:UNbak} + +.if ${_debug_reldir} +.info _machine_list=${_machine_list} +.info DIRDEPS_ALL_MACHINES_FILTER=${DIRDEPS_ALL_MACHINES_FILTER} +.endif + +_only_machines := ${_machine_list:${DIRDEPS_ALL_MACHINES_FILTER:ts:}:S,:, ,g} .else _only_machines := ${ONLY_TARGET_SPEC_LIST:U} ${ONLY_MACHINE_LIST:U} .endif .if empty(_only_machines) # we must be boot-strapping -_only_machines := ${TARGET_MACHINE:U${ALL_MACHINE_LIST:U${DEP_MACHINE}}} +_only_machines := ${TARGET_MACHINE:U${ALL_TARGET_SPEC_LIST:U${ALL_MACHINE_LIST:U${DEP_MACHINE}}}} +.endif + +# cleanup the result +_only_machines := ${_only_machines:O:u} + +.if ${_debug_reldir} +.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: ALL_MACHINES _only_machines=${_only_machines} .endif .else # ! ALL_MACHINES # if ONLY_TARGET_SPEC_LIST or ONLY_MACHINE_LIST is set, we are limited to that. # Note that ONLY_TARGET_SPEC_LIST should be fully qualified. # if TARGET_MACHINE is set - it is really the same as ONLY_MACHINE_LIST # otherwise DEP_MACHINE is it - so DEP_MACHINE will match. _only_machines := ${ONLY_TARGET_SPEC_LIST:U:M${DEP_MACHINE},*} .if empty(_only_machines) _only_machines := ${ONLY_MACHINE_LIST:U${TARGET_MACHINE:U${DEP_MACHINE}}:M${DEP_MACHINE}} .endif .endif .if !empty(NOT_MACHINE_LIST) _only_machines := ${_only_machines:${NOT_MACHINE_LIST:${M_ListToSkip}}} .endif .if !empty(NOT_TARGET_SPEC_LIST) # we must first qualify _dm := ${DEP_MACHINE} _only_machines := ${_only_machines:M*,*} ${_only_machines:N*,*:@DEP_MACHINE@${DEP_TARGET_SPEC}@:S,^,.,:${M_dep_qual_fixes:ts:}:O:u:S,^.,,} DEP_MACHINE := ${_dm} _only_machines := ${_only_machines:${NOT_TARGET_SPEC_LIST:${M_ListToSkip}}} .endif # clean up _only_machines := ${_only_machines:O:u} +.if ${_debug_reldir} +.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _only_machines=${_only_machines} +.endif + # make sure we have a starting place? DIRDEPS ?= ${RELDIR} .endif # target .if !defined(NO_DIRDEPS) && !defined(NO_DIRDEPS_BELOW) .if ${MK_DIRDEPS_CACHE} == "yes" # just ensure this exists build-dirdeps: M_oneperline = @x@\\${.newline} $$x@ .if ${BUILD_DIRDEPS_CACHE} == "no" .if !target(dirdeps-cached) # we do this via sub-make BUILD_DIRDEPS = no # ignore anything but these .MAKE.META.IGNORE_FILTER = M*/${.MAKE.DEPENDFILE_PREFIX}* dirdeps: dirdeps-cached dirdeps-cached: ${DIRDEPS_CACHE} .MAKE @echo "${TRACER}Using ${DIRDEPS_CACHE}" @MAKELEVEL=${.MAKE.LEVEL} ${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \ dirdeps MK_DIRDEPS_CACHE=no BUILD_DIRDEPS=no # leaf makefiles rarely work for building DIRDEPS_CACHE .if ${RELDIR} != "." BUILD_DIRDEPS_MAKEFILE ?= -f dirdeps.mk .endif # these should generally do BUILD_DIRDEPS_MAKEFILE ?= BUILD_DIRDEPS_TARGETS ?= ${.TARGETS} .if ${DIRDEPS_CACHE} != ${STATIC_DIRDEPS_CACHE:Uno} && ${DIRDEPS_CACHE:M${SRCTOP}/*} == "" # export this for dirdeps-cache-update.mk DYNAMIC_DIRDEPS_CACHE := ${DIRDEPS_CACHE} .export DYNAMIC_DIRDEPS_CACHE # we need the .meta file to ensure we update if # any of the Makefile.depend* changed. # We do not want to compare the command line though. ${DIRDEPS_CACHE}: .META .NOMETA_CMP +@{ echo '# Autogenerated - do NOT edit!'; echo; \ echo 'BUILD_DIRDEPS=no'; echo; \ echo '.include '; echo; \ } > ${.TARGET}.new +@MAKELEVEL=${.MAKE.LEVEL} DIRDEPS_CACHE=${DIRDEPS_CACHE} \ DIRDEPS="${DIRDEPS}" \ TARGET_SPEC=${TARGET_SPEC} \ MAKEFLAGS= ${DIRDEP_CACHE_MAKE:U${.MAKE}} -C ${_CURDIR} \ ${BUILD_DIRDEPS_MAKEFILE} \ ${BUILD_DIRDEPS_TARGETS} BUILD_DIRDEPS_CACHE=yes \ .MAKE.DEPENDFILE=.none \ ${"${DEBUG_DIRDEPS:Nno}":?DEBUG_DIRDEPS='${DEBUG_DIRDEPS}':} \ ${.MAKEFLAGS:tW:S,-D ,-D,g:tw:M*WITH*} \ ${.MAKEFLAGS:tW:S,-d ,-d,g:tw:M-d*} \ 3>&1 1>&2 | sed 's,${SRCTOP},$${SRCTOP},g;s,_{,$${,g' >> ${.TARGET}.new && \ mv ${.TARGET}.new ${.TARGET} .endif .endif .elif !target(_count_dirdeps) # we want to capture the dirdeps count in the cache .END: _count_dirdeps _count_dirdeps: .NOMETA @{ echo; echo '.info $${.newline}$${TRACER}Makefiles read: total=${.MAKE.MAKEFILES:[#]} depend=${.MAKE.MAKEFILES:M*depend*:[#]} dirdeps=${.ALLTARGETS:M${SRCTOP}*:O:u:[#]} ${DIRDEP_INFO_XTRAS}'; } >&3 .endif .elif !make(dirdeps) && !target(_count_dirdeps) beforedirdeps: _count_dirdeps _count_dirdeps: .NOMETA @echo "${TRACER}Makefiles read: total=${.MAKE.MAKEFILES:[#]} depend=${.MAKE.MAKEFILES:M*depend*:[#]} dirdeps=${.ALLTARGETS:M${SRCTOP}*:O:u:[#]} ${DIRDEP_INFO_XTRAS} seconds=`expr ${now_utc} - ${start_utc}`" .endif .endif .if ${BUILD_DIRDEPS} == "yes" # the rest is done repeatedly for every Makefile.depend we read. # if we are anything but the original dir we care only about the # machine type we were included for.. .if ${DEP_RELDIR} == "." _this_dir := ${SRCTOP} .else _this_dir := ${SRCTOP}/${DEP_RELDIR} .endif # on rare occasions, there can be a need for extra help _dep_hack := ${_this_dir}/${.MAKE.DEPENDFILE_PREFIX}.inc .-include <${_dep_hack}> .-include <${_dep_hack:R}.options> .if ${DEP_RELDIR} != ${_DEP_RELDIR} || ${DEP_TARGET_SPEC} != ${TARGET_SPEC} # this should be all _machines := ${DEP_MACHINE} .else # this is the machine list we actually use below _machines := ${_only_machines} .if defined(HOSTPROG) || ${DEP_MACHINE:Nhost*} == "" # we need to build this guy's dependencies for host as well. .if ${DEP_MACHINE:Nhost*} == "" _machines += ${DEP_MACHINE} .else _machines += host .endif .endif _machines := ${_machines:O:u} .endif .if ${TARGET_SPEC_VARS:[#]} > 1 # we need to tweak _machines _dm := ${DEP_MACHINE} # apply the same filtering that we do when qualifying DIRDEPS. # M_dep_qual_fixes expects .${MACHINE}* so add (and remove) '.' # Again we expect that any already qualified machines are fully qualified. _machines := ${_machines:M*,*} ${_machines:N*,*:@DEP_MACHINE@${DEP_TARGET_SPEC}@:S,^,.,:${M_dep_qual_fixes:ts:}:O:u:S,^.,,} DEP_MACHINE := ${_dm} _machines := ${_machines:O:u} .endif # reset each time through _build_dirs = .if ${DEP_RELDIR} == ${_DEP_RELDIR} # pickup other machines for this dir if necessary _build_dirs += ${_machines:@m@${_CURDIR}.$m@} .endif .if ${_debug_reldir} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: DIRDEPS='${DIRDEPS}' .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _machines='${_machines}' .endif .if !empty(DIRDEPS) # these we reset each time through as they can depend on DEP_MACHINE DEP_DIRDEPS_FILTER = \ ${DIRDEPS_FILTER.${DEP_TARGET_SPEC}:U} \ ${TARGET_SPEC_VARS:@v@${DIRDEPS_FILTER.${DEP_$v}:U}@} \ ${DIRDEPS_FILTER:U} .if empty(DEP_DIRDEPS_FILTER) # something harmless DEP_DIRDEPS_FILTER = U .endif # this is what we start with __depdirs := ${DIRDEPS:${NSkipDir}:${DEP_DIRDEPS_FILTER:ts:}:C,//+,/,g:O:u:@d@${SRCTOP}/$d@} # some entries may be qualified with . # the :M*/*/*.* just tries to limit the dirs we check to likely ones. # the ${d:E:M*/*} ensures we don't consider junos/usr.sbin/mgd __qual_depdirs := ${__depdirs:M*/*/*.*:@d@${exists($d):?:${"${d:E:M*/*}":?:${exists(${d:R}):?$d:}}}@} __unqual_depdirs := ${__depdirs:${__qual_depdirs:Uno:${M_ListToSkip}}} .if ${DEP_RELDIR} == ${_DEP_RELDIR} # if it was called out - we likely need it. __hostdpadd := ${DPADD:U.:M${HOST_OBJTOP}/*:S,${HOST_OBJTOP}/,,:H:${NSkipDir}:${DIRDEPS_FILTER:ts:}:S,$,.host,:N.*:@d@${SRCTOP}/$d@} \ ${DPADD:U.:M${HOST_OBJTOP32:Uno}/*:S,${HOST_OBJTOP32:Uno}/,,:H:${NSkipDir}:${DIRDEPS_FILTER:ts:}:S,$,.host32,:N.*:@d@${SRCTOP}/$d@} __qual_depdirs += ${__hostdpadd} .endif .if ${_debug_reldir} .info DEP_DIRDEPS_FILTER=${DEP_DIRDEPS_FILTER:ts:} .info depdirs=${__depdirs} .info qualified=${__qual_depdirs} .info unqualified=${__unqual_depdirs} .endif # _build_dirs is what we will feed to _DIRDEP_USE _build_dirs += \ ${__qual_depdirs:M*.host:${NSkipHostDir}:N.host} \ ${__qual_depdirs:N*.host} \ ${_machines:Mhost*:@m@${__unqual_depdirs:@d@$d.$m@}@:${NSkipHostDir}:N.host} \ ${_machines:Nhost*:@m@${__unqual_depdirs:@d@$d.$m@}@} # qualify everything now _build_dirs := ${_build_dirs:${M_dep_qual_fixes:ts:}:O:u} .endif # empty DIRDEPS _build_all_dirs += ${_build_dirs} ${_build_xtra_dirs} _build_all_dirs := ${_build_all_dirs:O:u} # Normally if doing make -V something, # we do not want to waste time chasing DIRDEPS # but if we want to count the number of Makefile.depend* read, we do. .if ${.MAKEFLAGS:M-V${_V_READ_DIRDEPS}} == "" .if !empty(_build_all_dirs) .if ${BUILD_DIRDEPS_CACHE} == "yes" x!= echo; { echo; echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}'; } >&3 # guard against _new_dirdeps being too big for a single command line _new_dirdeps := ${_build_all_dirs:@x@${target($x):?:$x}@} .export _build_xtra_dirs _new_dirdeps .if !empty(DEP_EXPORT_VARS) # Discouraged, but there are always exceptions. # Handle it here rather than explain how. x!= echo; { echo; ${DEP_EXPORT_VARS:@v@echo '$v=${$v}';@} echo '.export ${DEP_EXPORT_VARS}'; echo; } >&3 .endif .else # this makes it all happen dirdeps: ${_build_all_dirs} .endif ${_build_all_dirs}: _DIRDEP_USE .if ${_debug_reldir} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs} .endif .if !empty(DEP_EXPORT_VARS) .export ${DEP_EXPORT_VARS} DEP_EXPORT_VARS= .endif # this builds the dependency graph .for m in ${_machines} .if ${BUILD_DIRDEPS_CACHE} == "yes" && !empty(_build_dirs) x!= echo; { echo; echo 'DIRDEPS.${_this_dir}.$m = \'; } >&3 _cache_deps = .endif # it would be nice to do :N${.TARGET} .if !empty(__qual_depdirs) .for q in ${__qual_depdirs:${M_dep_qual_fixes:ts:}:E:O:u:N$m} .if ${_debug_reldir} || ${DEBUG_DIRDEPS:@x@${${DEP_RELDIR}.$m:L:M$x}${${DEP_RELDIR}.$q:L:M$x}@} != "" .info ${DEP_RELDIR}.$m: graph: ${_build_dirs:M*.$q} .endif .if ${BUILD_DIRDEPS_CACHE} == "yes" _cache_deps += ${_build_dirs:M*.$q} .else ${_this_dir}.$m: ${_build_dirs:M*.$q} .endif .endfor .endif .if ${_debug_reldir} .info ${DEP_RELDIR}.$m: graph: ${_build_dirs:M*.$m:N${_this_dir}.$m} .endif .if ${BUILD_DIRDEPS_CACHE} == "yes" .if !empty(_build_dirs) _cache_deps += ${_build_dirs:M*.$m:N${_this_dir}.$m} .if !empty(_cache_deps) .export _cache_deps x!= echo; for x in $$_cache_deps; do echo " $$x \\"; done >&3 .endif +# anything in _build_xtra_dirs is hooked to dirdeps: only x!= echo; { echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \ echo; echo 'dirdeps: ${_this_dir}.$m \'; \ for x in $$_build_xtra_dirs; do echo " $$x \\"; done; \ echo; for x in $$_new_dirdeps; do echo "$$x: _DIRDEP_USE"; done; } >&3 .endif .else ${_this_dir}.$m: ${_build_dirs:M*.$m:N${_this_dir}.$m} .endif .endfor .endif # Now find more dependencies - and recurse. .for d in ${_build_all_dirs} .if !target(_dirdeps_checked.$d) # once only _dirdeps_checked.$d: .if ${_debug_search} .info checking $d .endif # Note: _build_all_dirs is fully qualifed so d:R is always the directory .if exists(${d:R}) # we pass _DEP_TARGET_SPEC to tell the next step what we want _DEP_TARGET_SPEC := ${d:E} # some makefiles may still look at this _DEP_MACHINE := ${d:E:C/,.*//} # set these too in case Makefile.depend* uses them .if ${TARGET_SPEC_VARS:[#]} > 1 _dtspec := ${_DEP_TARGET_SPEC:S/,/ /g} .for i in ${_tspec_x} DEP_${TARGET_SPEC_VARS:[$i]} := ${_dtspec:[$i]} .endfor .else DEP_MACHINE := ${_DEP_MACHINE} .endif # Warning: there is an assumption here that MACHINE is always # the first entry in TARGET_SPEC_VARS. # If TARGET_SPEC and MACHINE are insufficient, you have a problem. -_m := ${.MAKE.DEPENDFILE_PREFERENCE:T:S;${TARGET_SPEC}$;${d:E};:S;${MACHINE};${d:E:C/,.*//};:@m@${exists(${d:R}/$m):?${d:R}/$m:}@:[1]} +_m := ${.MAKE.DEPENDFILE_PREFERENCE:T:S;${TARGET_SPEC}$;${d:E};:C;${MACHINE}((,.+)?)$;${d:E:C/,.*//}\1;:@m@${exists(${d:R}/$m):?${d:R}/$m:}@:[1]} .if !empty(_m) # M_dep_qual_fixes isn't geared to Makefile.depend _qm := ${_m:C;(\.depend)$;\1.${d:E};:${M_dep_qual_fixes:ts:}} .if ${_debug_search} .info Looking for ${_qm} .endif # set this "just in case" # we can skip :tA since we computed the path above DEP_RELDIR := ${_m:H:S,${SRCTOP}/,,} # and reset this DIRDEPS = .if ${_debug_reldir} && ${_qm} != ${_m} .info loading ${_m} for ${d:E} .endif .include <${_m}> .else .-include .endif .endif .endif .endfor .endif # -V .endif # BUILD_DIRDEPS .elif ${.MAKE.LEVEL} > 42 .error You should have stopped recursing by now. .else # we are building something DEP_RELDIR := ${RELDIR} _DEP_RELDIR := ${RELDIR} # Since we are/should be included by .MAKE.DEPENDFILE # This is a final opportunity to add/hook global rules. .-include # skip _reldir_{finish,failed} if not included from Makefile.depend* # or not in meta mode .if !defined(WITHOUT_META_STATS) && ${.INCLUDEDFROMFILE:U:M${.MAKE.DEPENDFILE_PREFIX}*} != "" && ${.MAKE.MODE:Mmeta} != "" meta_stats= meta=${empty(.MAKE.META.FILES):?0:${.MAKE.META.FILES:[#]}} \ created=${empty(.MAKE.META.CREATED):?0:${.MAKE.META.CREATED:[#]}} .if !target(_reldir_finish) .END: _reldir_finish _reldir_finish: .NOMETA @echo "${TRACER}Finished ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}" .endif .if !target(_reldir_failed) .ERROR: _reldir_failed _reldir_failed: .NOMETA @echo "${TRACER}Failed ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}" .endif .endif # pickup local dependencies .if ${MAKE_VERSION} < 20160220 .-include <.depend> .else .dinclude <.depend> .endif .endif # bootstrapping new dependencies made easy? +.if !target(bootstrap-empty) .if !target(bootstrap) && (make(bootstrap) || \ make(bootstrap-this) || \ make(bootstrap-recurse) || \ make(bootstrap-empty)) # if we are bootstrapping create the default _want = ${.CURDIR}/${.MAKE.DEPENDFILE_DEFAULT:T} .if exists(${_want}) # stop here ${.TARGETS:Mboot*}: .elif !make(bootstrap-empty) # find a Makefile.depend to use as _src _src != cd ${.CURDIR} && for m in ${.MAKE.DEPENDFILE_PREFERENCE:T:S,${MACHINE},*,}; do test -s $$m || continue; echo $$m; break; done; echo .if empty(_src) .error cannot find any of ${.MAKE.DEPENDFILE_PREFERENCE:T}${.newline}Use: bootstrap-empty .endif _src?= ${.MAKE.DEPENDFILE} .MAKE.DEPENDFILE_BOOTSTRAP_SED+= -e 's/${_src:E:C/,.*//}/${MACHINE}/g' # just create Makefile.depend* for this dir bootstrap-this: .NOTMAIN @echo Bootstrapping ${RELDIR}/${_want:T} from ${_src:T}; \ echo You need to build ${RELDIR} to correctly populate it. .if ${_src:T} != ${.MAKE.DEPENDFILE_PREFIX:T} (cd ${.CURDIR} && sed ${.MAKE.DEPENDFILE_BOOTSTRAP_SED} ${_src} > ${_want:T}) .else cp ${.CURDIR}/${_src:T} ${_want} .endif # create Makefile.depend* for this dir and its dependencies bootstrap: bootstrap-recurse bootstrap-recurse: bootstrap-this _mf := ${.PARSEFILE} bootstrap-recurse: .NOTMAIN .MAKE @cd ${SRCTOP} && \ for d in `cd ${RELDIR} && ${.MAKE} -B -f ${"${.MAKEFLAGS:M-n}":?${_src}:${.MAKE.DEPENDFILE:T}} -V DIRDEPS`; do \ test -d $$d || d=$${d%.*}; \ test -d $$d || continue; \ echo "Checking $$d for bootstrap ..."; \ (cd $$d && ${.MAKE} -f ${_mf} bootstrap-recurse); \ done .endif # create an empty Makefile.depend* to get the ball rolling. bootstrap-empty: .NOTMAIN .NOMETA @echo Creating empty ${RELDIR}/${_want:T}; \ echo You need to build ${RELDIR} to correctly populate it. @{ echo DIRDEPS=; echo ".include "; } > ${_want} .endif +.endif diff --git a/contrib/bmake/mk/dpadd.mk b/contrib/bmake/mk/dpadd.mk index 45585ce76d9b..aef12528f163 100644 --- a/contrib/bmake/mk/dpadd.mk +++ b/contrib/bmake/mk/dpadd.mk @@ -1,339 +1,340 @@ -# $Id: dpadd.mk,v 1.28 2020/08/19 17:51:53 sjg Exp $ +# $Id: dpadd.mk,v 1.29 2021/04/20 02:30:44 sjg Exp $ # # @(#) Copyright (c) 2004, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # ## # DESCRIPTION: # This makefile manages a number of variables that simplify # dealing with libs in a build. # # Primary inputs are DPLIBS, DPADD and SRC_LIBS: # # DPLIBS # List of LIB* that we will actually link with # should be in correct link order. # DPLIBS is a short-cut to ensure that DPADD and LDADD are # kept in sync. # # DPADD List of LIB* that should already be built. # # SRC_LIBS # List of LIB* that we want headers from, we do *not* # require that such libs have been built. # # The above all get added to DPMAGIC_LIBS which is what we # process. # # We expect LIB* to be set to absolute path of a library - # suitable for putting in DPADD. # eg. # # LIBC ?= ${OBJTOP}/lib/libc/libc.a # # From such a path we can derrive a number of other variables # for which we can supply sensible default values. # We name all these variables for the basename of the library # (libc in our example above -- ${__lib:T:R} in below): # # LDADD_${__lib:T:R}: # What should be added to LDADD (eg -lc) # # OBJ_${__lib:T:R}: # This is trivial - just the dirname of the built library. # # SRC_${__lib:T:R}: # Where the src for ${__lib} is, if LIB* is set as above # we can simply substitute ${SRCTOP} for ${OBJTOP} in # the dirname. # # INCLUDES_${__lib:T:R}: # What should be added to CFLAGS # # If the directory ${SRC_${__lib:T:R}}/h exists we will # only add -I${SRC_${__lib:T:R}}/h on the basis that # this is where the public api is kept. # # Otherwise default will be -I${OBJ_${__lib:T:R}} # -I${SRC_${__lib:T:R}} # # Note much of the above is skipped for staged libs # eg. # LIBC ?= ${STAGE_OBJTOP}/usr/lib/libc.a # # Since we can safely assume that -I${STAGE_OBJTOP}/usr/include # and -L${STAGE_OBJTOP}/usr/lib are sufficient, and we should # have no need of anything else. # .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: # sometimes we play games with .CURDIR etc # _* hold the original values of .* _OBJDIR?= ${.OBJDIR} _CURDIR?= ${.CURDIR} .if ${_CURDIR} == ${SRCTOP} RELDIR=. RELTOP=. .else RELDIR?= ${_CURDIR:S,${SRCTOP}/,,} .if ${RELDIR} == ${_CURDIR} RELDIR?= ${_OBJDIR:S,${OBJTOP}/,,} .endif RELTOP?= ${RELDIR:C,[^/]+,..,g} .endif RELOBJTOP?= ${OBJTOP} RELSRCTOP?= ${SRCTOP} # we get included just about everywhere so this is handy... # C*DEBUG_XTRA are for defining on cmd line etc # so do not use in makefiles. .ifdef CFLAGS_DEBUG_XTRA CFLAGS_LAST += ${CFLAGS_DEBUG_XTRA} .endif .ifdef CXXFLAGS_DEBUG_XTRA CXXFLAGS_LAST += ${CXXFLAGS_DEBUG_XTRA} .endif .-include # DPLIBS helps us ensure we keep DPADD and LDADD in sync DPLIBS+= ${DPLIBS_LAST} DPADD+= ${DPLIBS:N-*} .for __lib in ${DPLIBS} .if "${__lib:M-*}" != "" LDADD += ${__lib} .else LDADD += ${LDADD_${__lib:T:R}:U${__lib:T:R:S/lib/-l/:C/\.so.*//}} .endif .endfor # DPADD can contain things other than libs __dpadd_libs := ${DPADD:M*/lib*} .if defined(PROG) && ${MK_PROG_LDORDER_MK:Uno} != "no" # some libs have dependencies... # DPLIBS_* allows bsd.libnames.mk to flag libs which must be included # in DPADD for a given library. # Gather all such dependencies into __ldadd_all_xtras # dups will be dealt with later. # Note: libfoo_pic uses DPLIBS_libfoo __ldadd_all_xtras= .for __lib in ${__dpadd_libs:@d@${DPLIBS_${d:T:R:S,_pic,,}}@} __ldadd_all_xtras+= ${LDADD_${__lib}:U${__lib:T:R:S/lib/-l/:C/\.so.*//}} .if "${DPADD:M${__lib}}" == "" DPADD+= ${__lib} .endif .endfor .endif # Last of all... for libc and libgcc DPADD+= ${DPADD_LAST} # de-dupuplicate __ldadd_all_xtras into __ldadd_xtras # in reverse order so that libs end up listed after all that needed them. __ldadd_xtras= .for __lib in ${__ldadd_all_xtras:[-1..1]} .if "${__ldadd_xtras:M${__lib}}" == "" || ${NEED_IMPLICIT_LDADD:tl:Uno} != "no" __ldadd_xtras+= ${__lib} .endif .endfor .if !empty(__ldadd_xtras) # now back to the original order __ldadd_xtras:= ${__ldadd_xtras:[-1..1]} LDADD+= ${__ldadd_xtras} .endif # Convert DPADD into -I and -L options and add them to CPPFLAGS and LDADD # For the -I's convert the path to a relative one. For separate objdirs # the DPADD paths will be to the obj tree so we need to subst anyway. # update this __dpadd_libs := ${DPADD:M*/lib*} # Order -L's to search ours first. # Avoids picking up old versions already installed. __dpadd_libdirs := ${__dpadd_libs:R:H:S/^/-L/g:O:u:N-L} LDADD += ${__dpadd_libdirs:M-L${OBJTOP}/*} LDADD += ${__dpadd_libdirs:N-L${OBJTOP}/*:N-L${HOST_LIBDIR:U/usr/lib}} .if defined(HOST_LIBDIR) && ${HOST_LIBDIR} != "/usr/lib" LDADD+= -L${HOST_LIBDIR} .endif .if !make(dpadd) .ifdef LIB # Each lib is its own src_lib, we want to include it in SRC_LIBS # so that the correct INCLUDES_* will be picked up automatically. SRC_LIBS+= ${_OBJDIR}/lib${LIB}.a .endif .endif # # This little bit of magic, assumes that SRC_libfoo will be # set if it cannot be correctly derrived from ${LIBFOO} # Note that SRC_libfoo and INCLUDES_libfoo should be named for the # actual library name not the variable name that might refer to it. # 99% of the time the two are the same, but the DPADD logic # only has the library name available, so stick to that. # SRC_LIBS?= # magic_libs includes those we want to link with # as well as those we might look at __dpadd_magic_libs += ${__dpadd_libs} ${SRC_LIBS} DPMAGIC_LIBS += ${__dpadd_magic_libs} \ ${__dpadd_magic_libs:@d@${DPMAGIC_LIBS_${d:T:R}}@} # we skip this for staged libs .for __lib in ${DPMAGIC_LIBS:O:u:N${STAGE_OBJTOP:Unot}*/lib/*} # # if SRC_libfoo is not set, then we assume that the srcdir corresponding # to where we found the library is correct. # SRC_${__lib:T:R} ?= ${__lib:H:S,${OBJTOP},${RELSRCTOP},} # # This is a no-brainer but just to be complete... # OBJ_${__lib:T:R} ?= ${__lib:H:S,${OBJTOP},${RELOBJTOP},} # # If INCLUDES_libfoo is not set, then we'll use ${SRC_libfoo}/h if it exists, # else just ${SRC_libfoo}. # -INCLUDES_${__lib:T:R}?= -I${exists(${SRC_${__lib:T:R}}/h):?${SRC_${__lib:T:R}}/h:${SRC_${__lib:T:R}}} - +.if !empty(SRC_${__lib:T:R}) +INCLUDES_${__lib:T:R} ?= -I${exists(${SRC_${__lib:T:R}}/h):?${SRC_${__lib:T:R}}/h:${SRC_${__lib:T:R}}} +.endif .endfor # even for staged libs we sometimes # need to allow direct -I to avoid cicular dependencies .for __lib in ${DPMAGIC_LIBS:O:u:T:R} .if !empty(SRC_${__lib}) && empty(INCLUDES_${__lib}) # must be a staged lib .if exists(${SRC_${__lib}}/h) INCLUDES_${__lib} = -I${SRC_${__lib}}/h .else INCLUDES_${__lib} = -I${SRC_${__lib}} .endif .endif .endfor # when linking a shared lib, avoid non pic libs SHLDADD+= ${LDADD:N-[lL]*} .for __lib in ${__dpadd_libs:u} .if defined(SHLIB_NAME) && ${LDADD:M-l${__lib:T:R:S,lib,,}} != "" .if ${__lib:T:N*_pic.a:N*.so} == "" || exists(${__lib:R}.so) SHLDADD+= -l${__lib:T:R:S,lib,,} .elif exists(${__lib:R}_pic.a) SHLDADD+= -l${__lib:T:R:S,lib,,}_pic .else .warning ${RELDIR}.${TARGET_SPEC} needs ${__lib:T:R}_pic.a SHLDADD+= -l${__lib:T:R:S,lib,,} .endif SHLDADD+= -L${__lib:H} .endif .endfor # Now for the bits we actually need __dpadd_incs= .for __lib in ${__dpadd_libs:u} .if (make(${PROG}_p) || defined(NEED_GPROF)) && exists(${__lib:R}_p.a) __ldadd=-l${__lib:T:R:S,lib,,} LDADD := ${LDADD:S,^${__ldadd}$,${__ldadd}_p,g} .endif .endfor # # We take care of duplicate suppression later. # don't apply :T:R too early __dpadd_incs += ${__dpadd_magic_libs:u:@x@${INCLUDES_${x:T:R}}@} __dpadd_incs += ${__dpadd_magic_libs:O:u:@s@${SRC_LIBS_${s:T:R}:U}@:@x@${INCLUDES_${x:T:R}}@} __dpadd_last_incs += ${__dpadd_magic_libs:u:@x@${INCLUDES_LAST_${x:T:R}}@} __dpadd_last_incs += ${__dpadd_magic_libs:O:u:@s@${SRC_LIBS_${s:T:R}:U}@:@x@${INCLUDES_LAST_${x:T:R}}@} .if defined(HOSTPROG) || ${MACHINE:Nhost*} == "" # we want any -I/usr/* last __dpadd_last_incs := \ ${__dpadd_last_incs:N-I/usr/*} \ ${__dpadd_incs:M-I/usr/*} \ ${__dpadd_last_incs:M-I/usr/*} __dpadd_incs := ${__dpadd_incs:N-I/usr/*} .endif # # eliminate any duplicates - but don't mess with the order # force evaluation now - to avoid giving make a headache # .for t in CFLAGS CXXFLAGS # avoid duplicates __$t_incs:=${$t:M-I*:O:u} .for i in ${__dpadd_incs} .if "${__$t_incs:M$i}" == "" $t+= $i __$t_incs+= $i .endif .endfor .endfor .for t in CFLAGS_LAST CXXFLAGS_LAST # avoid duplicates __$t_incs:=${$t:M-I*:u} .for i in ${__dpadd_last_incs} .if "${__$t_incs:M$i}" == "" $t+= $i __$t_incs+= $i .endif .endfor .endfor # This target is used to gather a list of # dir: ${DPADD} # entries .if make(*dpadd*) .if !target(dpadd) dpadd: .NOTMAIN .if defined(DPADD) && ${DPADD} != "" @echo "${RELDIR}: ${DPADD:S,${OBJTOP}/,,}" .endif .endif .endif .ifdef SRC_PATHADD # We don't want to assume that we need to .PATH every element of # SRC_LIBS, but the Makefile cannot do # .PATH: ${SRC_libfoo} # since the value of SRC_libfoo must be available at the time .PATH: # is read - and we only just worked it out. # Further, they can't wait until after include of {lib,prog}.mk as # the .PATH is needed before then. # So we let the Makefile do # SRC_PATHADD+= ${SRC_libfoo} # and we defer the .PATH: until now so that SRC_libfoo will be available. .PATH: ${SRC_PATHADD} .endif # after all that, if doing -n we don't care .if ${.MAKEFLAGS:Ux:M-n} != "" DPADD = .elif ${.MAKE.MODE:Mmeta*} != "" && exists(${.MAKE.DEPENDFILE}) DPADD_CLEAR_DPADD ?= yes .if ${DPADD_CLEAR_DPADD} == "yes" # save this __dpadd_libs := ${__dpadd_libs} # we have made what use of it we can of DPADD DPADD = .endif .endif .endif diff --git a/contrib/bmake/mk/install-mk b/contrib/bmake/mk/install-mk index b014728ca450..96c35b1052ec 100755 --- a/contrib/bmake/mk/install-mk +++ b/contrib/bmake/mk/install-mk @@ -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.191 2021/01/30 23:16:42 sjg Exp $ +# $Id: install-mk,v 1.196 2021/06/19 15:30:41 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=20210130 +MK_VERSION=20210616 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 diff --git a/contrib/bmake/mk/meta.autodep.mk b/contrib/bmake/mk/meta.autodep.mk index b5cb39a30855..5d09dbd88e81 100644 --- a/contrib/bmake/mk/meta.autodep.mk +++ b/contrib/bmake/mk/meta.autodep.mk @@ -1,317 +1,321 @@ -# $Id: meta.autodep.mk,v 1.53 2020/11/08 05:47:56 sjg Exp $ +# $Id: meta.autodep.mk,v 1.54 2021/03/06 17:03:18 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 ${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:[#]}} +.if !target(_reldir_finish) #.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}" +.endif +.if !target(_reldir_failed) #.ERROR: _reldir_failed _reldir_failed: .NOMETA @echo "${TIME_STAMP} Failed ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}" +.endif .if !defined(WITHOUT_META_STATS) && ${.MAKE.LEVEL} > 0 .END: _reldir_finish .ERROR: _reldir_failed .endif .endif diff --git a/contrib/bmake/mk/meta2deps.py b/contrib/bmake/mk/meta2deps.py index 4627e08d7c11..193e303de3da 100755 --- a/contrib/bmake/mk/meta2deps.py +++ b/contrib/bmake/mk/meta2deps.py @@ -1,768 +1,768 @@ #!/usr/bin/env python from __future__ import print_function """ This script parses each "meta" file and extracts the information needed to deduce build and src dependencies. It works much the same as the original shell script, but is *much* more efficient. The parsing work is handled by the class MetaFile. We only pay attention to a subset of the information in the "meta" files. Specifically: 'CWD' to initialize our notion. 'C' to track chdir(2) on a per process basis 'R' files read are what we really care about. directories read, provide a clue to resolving subsequent relative paths. That is if we cannot find them relative to 'cwd', we check relative to the last dir read. 'W' files opened for write or read-write, for filemon V3 and earlier. 'E' files executed. 'L' files linked 'V' the filemon version, this record is used as a clue that we have reached the interesting bit. """ """ RCSid: - $Id: meta2deps.py,v 1.34 2020/10/02 03:11:17 sjg Exp $ + $Id: meta2deps.py,v 1.38 2021/06/17 05:20:08 sjg Exp $ Copyright (c) 2011-2020, Simon J. Gerraty Copyright (c) 2011-2017, Juniper Networks, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import os, re, sys -def getv(dict, key, d=None): - """Lookup key in dict and return value or the supplied default.""" - if key in dict: - return dict[key] - return d - def resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): """ Return an absolute path, resolving via cwd or last_dir if needed. """ if path.endswith('/.'): path = path[0:-2] if len(path) > 0 and path[0] == '/': if os.path.exists(path): return path if debug > 2: print("skipping non-existent:", path, file=debug_out) return None if path == '.': return cwd if path.startswith('./'): return cwd + path[1:] if last_dir == cwd: last_dir = None for d in [last_dir, cwd]: if not d: continue if path == '..': dw = d.split('/') p = '/'.join(dw[:-1]) if not p: p = '/' return p p = '/'.join([d,path]) if debug > 2: print("looking for:", p, end=' ', file=debug_out) if not os.path.exists(p): if debug > 2: print("nope", file=debug_out) p = None continue if debug > 2: print("found:", p, file=debug_out) return p return None def cleanpath(path): """cleanup path without using realpath(3)""" if path.startswith('/'): r = '/' else: r = '' p = [] w = path.split('/') for d in w: if not d or d == '.': continue if d == '..': try: p.pop() continue except: break p.append(d) return r + '/'.join(p) def abspath(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): """ Return an absolute path, resolving via cwd or last_dir if needed. this gets called a lot, so we try to avoid calling realpath. """ rpath = resolve(path, cwd, last_dir, debug, debug_out) if rpath: path = rpath elif len(path) > 0 and path[0] == '/': return None if (path.find('/') < 0 or path.find('./') > 0 or path.endswith('/..')): path = cleanpath(path) return path def sort_unique(list, cmp=None, key=None, reverse=False): - list.sort(cmp, key, reverse) + if sys.version_info[0] == 2: + list.sort(cmp, key, reverse) + else: + list.sort(reverse=reverse) nl = [] le = None for e in list: if e == le: continue le = e nl.append(e) return nl def add_trims(x): return ['/' + x + '/', '/' + x, x + '/', x] class MetaFile: """class to parse meta files generated by bmake.""" conf = None dirdep_re = None host_target = None srctops = [] objroots = [] excludes = [] seen = {} obj_deps = [] src_deps = [] file_deps = [] def __init__(self, name, conf={}): """if name is set we will parse it now. conf can have the follwing keys: SRCTOPS list of tops of the src tree(s). CURDIR the src directory 'bmake' was run from. RELDIR the relative path from SRCTOP to CURDIR MACHINE the machine we built for. set to 'none' if we are not cross-building. More specifically if machine cannot be deduced from objdirs. TARGET_SPEC Sometimes MACHINE isn't enough. HOST_TARGET when we build for the pseudo machine 'host' the object tree uses HOST_TARGET rather than MACHINE. OBJROOTS a list of the common prefix for all obj dirs it might end in '/' or '-'. DPDEPS names an optional file to which per file dependencies will be appended. For example if 'some/path/foo.h' is read from SRCTOP then 'DPDEPS_some/path/foo.h +=' "RELDIR" is output. This can allow 'bmake' to learn all the dirs within the tree that depend on 'foo.h' EXCLUDES A list of paths to ignore. ccache(1) can otherwise be trouble. debug desired debug level debug_out open file to send debug output to (sys.stderr) """ self.name = name - self.debug = getv(conf, 'debug', 0) - self.debug_out = getv(conf, 'debug_out', sys.stderr) - - self.machine = getv(conf, 'MACHINE', '') - self.machine_arch = getv(conf, 'MACHINE_ARCH', '') - self.target_spec = getv(conf, 'TARGET_SPEC', '') - self.curdir = getv(conf, 'CURDIR') - self.reldir = getv(conf, 'RELDIR') - self.dpdeps = getv(conf, 'DPDEPS') + self.debug = conf.get('debug', 0) + self.debug_out = conf.get('debug_out', sys.stderr) + + self.machine = conf.get('MACHINE', '') + self.machine_arch = conf.get('MACHINE_ARCH', '') + self.target_spec = conf.get('TARGET_SPEC', '') + self.curdir = conf.get('CURDIR') + self.reldir = conf.get('RELDIR') + self.dpdeps = conf.get('DPDEPS') self.line = 0 if not self.conf: # some of the steps below we want to do only once self.conf = conf - self.host_target = getv(conf, 'HOST_TARGET') - for srctop in getv(conf, 'SRCTOPS', []): + self.host_target = conf.get('HOST_TARGET') + for srctop in conf.get('SRCTOPS', []): if srctop[-1] != '/': srctop += '/' if not srctop in self.srctops: self.srctops.append(srctop) _srctop = os.path.realpath(srctop) if _srctop[-1] != '/': _srctop += '/' if not _srctop in self.srctops: self.srctops.append(_srctop) trim_list = add_trims(self.machine) if self.machine == 'host': trim_list += add_trims(self.host_target) if self.target_spec: trim_list += add_trims(self.target_spec) - for objroot in getv(conf, 'OBJROOTS', []): + for objroot in conf.get('OBJROOTS', []): for e in trim_list: if objroot.endswith(e): # this is not what we want - fix it objroot = objroot[0:-len(e)] if objroot[-1] != '/': objroot += '/' if not objroot in self.objroots: self.objroots.append(objroot) _objroot = os.path.realpath(objroot) if objroot[-1] == '/': _objroot += '/' if not _objroot in self.objroots: self.objroots.append(_objroot) # we want the longest match self.srctops.sort(reverse=True) self.objroots.sort(reverse=True) - self.excludes = getv(conf, 'EXCLUDES', []) + self.excludes = conf.get('EXCLUDES', []) if self.debug: print("host_target=", self.host_target, file=self.debug_out) print("srctops=", self.srctops, file=self.debug_out) print("objroots=", self.objroots, file=self.debug_out) print("excludes=", self.excludes, file=self.debug_out) self.dirdep_re = re.compile(r'([^/]+)/(.+)') if self.dpdeps and not self.reldir: if self.debug: print("need reldir:", end=' ', file=self.debug_out) if self.curdir: srctop = self.find_top(self.curdir, self.srctops) if srctop: self.reldir = self.curdir.replace(srctop,'') if self.debug: print(self.reldir, file=self.debug_out) if not self.reldir: self.dpdeps = None # we cannot do it? self.cwd = os.getcwd() # make sure this is initialized self.last_dir = self.cwd if name: self.try_parse() def reset(self): """reset state if we are being passed meta files from multiple directories.""" self.seen = {} self.obj_deps = [] self.src_deps = [] self.file_deps = [] def dirdeps(self, sep='\n'): """return DIRDEPS""" return sep.strip() + sep.join(self.obj_deps) def src_dirdeps(self, sep='\n'): """return SRC_DIRDEPS""" return sep.strip() + sep.join(self.src_deps) def file_depends(self, out=None): """Append DPDEPS_${file} += ${RELDIR} for each file we saw, to the output file.""" if not self.reldir: return None for f in sort_unique(self.file_deps): print('DPDEPS_%s += %s' % (f, self.reldir), file=out) # these entries provide for reverse DIRDEPS lookup for f in self.obj_deps: print('DEPDIRS_%s += %s' % (f, self.reldir), file=out) def seenit(self, dir): """rememer that we have seen dir.""" self.seen[dir] = 1 def add(self, list, data, clue=''): """add data to list if it isn't already there.""" if data not in list: list.append(data) if self.debug: print("%s: %sAdd: %s" % (self.name, clue, data), file=self.debug_out) def find_top(self, path, list): """the logical tree may be split across multiple trees""" for top in list: if path.startswith(top): if self.debug > 2: print("found in", top, file=self.debug_out) return top return None def find_obj(self, objroot, dir, path, input): """return path within objroot, taking care of .dirdep files""" ddep = None for ddepf in [path + '.dirdep', dir + '/.dirdep']: if not ddep and os.path.exists(ddepf): ddep = open(ddepf, 'r').readline().strip('# \n') if self.debug > 1: print("found %s: %s\n" % (ddepf, ddep), file=self.debug_out) if ddep.endswith(self.machine): ddep = ddep[0:-(1+len(self.machine))] elif self.target_spec and ddep.endswith(self.target_spec): ddep = ddep[0:-(1+len(self.target_spec))] if not ddep: # no .dirdeps, so remember that we've seen the raw input self.seenit(input) self.seenit(dir) if self.machine == 'none': if dir.startswith(objroot): return dir.replace(objroot,'') return None m = self.dirdep_re.match(dir.replace(objroot,'')) if m: ddep = m.group(2) dmachine = m.group(1) if dmachine != self.machine: if not (self.machine == 'host' and dmachine == self.host_target): if self.debug > 2: print("adding .%s to %s" % (dmachine, ddep), file=self.debug_out) ddep += '.' + dmachine return ddep def try_parse(self, name=None, file=None): """give file and line number causing exception""" try: self.parse(name, file) except: # give a useful clue print('{}:{}: '.format(self.name, self.line), end=' ', file=sys.stderr) raise def parse(self, name=None, file=None): """A meta file looks like: # Meta data file "path" CMD "command-line" CWD "cwd" TARGET "target" -- command output -- -- filemon acquired metadata -- # buildmon version 3 V 3 C "pid" "cwd" E "pid" "path" F "pid" "child" R "pid" "path" W "pid" "path" X "pid" "status" D "pid" "path" L "pid" "src" "target" M "pid" "old" "new" S "pid" "path" # Bye bye We go to some effort to avoid processing a dependency more than once. Of the above record types only C,E,F,L,R,V and W are of interest. """ version = 0 # unknown if name: self.name = name; if file: f = file cwd = self.last_dir = self.cwd else: f = open(self.name, 'r') skip = True pid_cwd = {} pid_last_dir = {} last_pid = 0 self.line = 0 if self.curdir: self.seenit(self.curdir) # we ignore this interesting = 'CEFLRV' for line in f: self.line += 1 # ignore anything we don't care about if not line[0] in interesting: continue if self.debug > 2: print("input:", line, end=' ', file=self.debug_out) w = line.split() if skip: if w[0] == 'V': skip = False version = int(w[1]) """ if version < 4: # we cannot ignore 'W' records # as they may be 'rw' interesting += 'W' """ elif w[0] == 'CWD': self.cwd = cwd = self.last_dir = w[1] self.seenit(cwd) # ignore this if self.debug: print("%s: CWD=%s" % (self.name, cwd), file=self.debug_out) continue pid = int(w[1]) if pid != last_pid: if last_pid: pid_last_dir[last_pid] = self.last_dir - cwd = getv(pid_cwd, pid, self.cwd) - self.last_dir = getv(pid_last_dir, pid, self.cwd) + cwd = pid_cwd.get(pid, self.cwd) + self.last_dir = pid_last_dir.get(pid, self.cwd) last_pid = pid # process operations if w[0] == 'F': npid = int(w[2]) pid_cwd[npid] = cwd pid_last_dir[npid] = cwd last_pid = npid continue elif w[0] == 'C': cwd = abspath(w[2], cwd, None, self.debug, self.debug_out) if not cwd: cwd = w[2] if self.debug > 1: print("missing cwd=", cwd, file=self.debug_out) if cwd.endswith('/.'): cwd = cwd[0:-2] self.last_dir = pid_last_dir[pid] = cwd pid_cwd[pid] = cwd if self.debug > 1: print("cwd=", cwd, file=self.debug_out) continue if w[2] in self.seen: if self.debug > 2: print("seen:", w[2], file=self.debug_out) continue # file operations if w[0] in 'ML': # these are special, tread src as read and # target as write self.parse_path(w[2].strip("'"), cwd, 'R', w) self.parse_path(w[3].strip("'"), cwd, 'W', w) continue elif w[0] in 'ERWS': path = w[2] if path == '.': continue self.parse_path(path, cwd, w[0], w) assert(version > 0) if not file: f.close() def is_src(self, base, dir, rdir): """is base in srctop""" for dir in [dir,rdir]: if not dir: continue path = '/'.join([dir,base]) srctop = self.find_top(path, self.srctops) if srctop: if self.dpdeps: self.add(self.file_deps, path.replace(srctop,''), 'file') self.add(self.src_deps, dir.replace(srctop,''), 'src') self.seenit(dir) return True return False def parse_path(self, path, cwd, op=None, w=[]): """look at a path for the op specified""" if not op: op = w[0] # we are never interested in .dirdep files as dependencies if path.endswith('.dirdep'): return for p in self.excludes: if p and path.startswith(p): if self.debug > 2: print("exclude:", p, path, file=self.debug_out) return # we don't want to resolve the last component if it is # a symlink path = resolve(path, cwd, self.last_dir, self.debug, self.debug_out) if not path: return dir,base = os.path.split(path) if dir in self.seen: if self.debug > 2: print("seen:", dir, file=self.debug_out) return # we can have a path in an objdir which is a link # to the src dir, we may need to add dependencies for each rdir = dir dir = abspath(dir, cwd, self.last_dir, self.debug, self.debug_out) - rdir = os.path.realpath(dir) + if dir: + rdir = os.path.realpath(dir) + else: + dir = rdir if rdir == dir: rdir = None # now put path back together path = '/'.join([dir,base]) if self.debug > 1: print("raw=%s rdir=%s dir=%s path=%s" % (w[2], rdir, dir, path), file=self.debug_out) if op in 'RWS': if path in [self.last_dir, cwd, self.cwd, self.curdir]: if self.debug > 1: print("skipping:", path, file=self.debug_out) return if os.path.isdir(path): if op in 'RW': self.last_dir = path; if self.debug > 1: print("ldir=", self.last_dir, file=self.debug_out) return if op in 'ER': # finally, we get down to it if dir == self.cwd or dir == self.curdir: return if self.is_src(base, dir, rdir): self.seenit(w[2]) if not rdir: return objroot = None for dir in [dir,rdir]: if not dir: continue objroot = self.find_top(dir, self.objroots) if objroot: break if objroot: ddep = self.find_obj(objroot, dir, path, w[2]) if ddep: self.add(self.obj_deps, ddep, 'obj') if self.dpdeps and objroot.endswith('/stage/'): sp = '/'.join(path.replace(objroot,'').split('/')[1:]) self.add(self.file_deps, sp, 'file') else: # don't waste time looking again self.seenit(w[2]) self.seenit(dir) def main(argv, klass=MetaFile, xopts='', xoptf=None): """Simple driver for class MetaFile. Usage: script [options] [key=value ...] "meta" ... Options and key=value pairs contribute to the dictionary passed to MetaFile. -S "SRCTOP" add "SRCTOP" to the "SRCTOPS" list. -C "CURDIR" -O "OBJROOT" add "OBJROOT" to the "OBJROOTS" list. -m "MACHINE" -a "MACHINE_ARCH" -H "HOST_TARGET" -D "DPDEPS" -d bumps debug level """ import getopt # import Psyco if we can # it can speed things up quite a bit have_psyco = 0 try: import psyco psyco.full() have_psyco = 1 except: pass conf = { 'SRCTOPS': [], 'OBJROOTS': [], 'EXCLUDES': [], } try: machine = os.environ['MACHINE'] if machine: conf['MACHINE'] = machine machine_arch = os.environ['MACHINE_ARCH'] if machine_arch: conf['MACHINE_ARCH'] = machine_arch srctop = os.environ['SB_SRC'] if srctop: conf['SRCTOPS'].append(srctop) objroot = os.environ['SB_OBJROOT'] if objroot: conf['OBJROOTS'].append(objroot) except: pass debug = 0 output = True opts, args = getopt.getopt(argv[1:], 'a:dS:C:O:R:m:D:H:qT:X:' + xopts) for o, a in opts: if o == '-a': conf['MACHINE_ARCH'] = a elif o == '-d': debug += 1 elif o == '-q': output = False elif o == '-H': conf['HOST_TARGET'] = a elif o == '-S': if a not in conf['SRCTOPS']: conf['SRCTOPS'].append(a) elif o == '-C': conf['CURDIR'] = a elif o == '-O': if a not in conf['OBJROOTS']: conf['OBJROOTS'].append(a) elif o == '-R': conf['RELDIR'] = a elif o == '-D': conf['DPDEPS'] = a elif o == '-m': conf['MACHINE'] = a elif o == '-T': conf['TARGET_SPEC'] = a elif o == '-X': if a not in conf['EXCLUDES']: conf['EXCLUDES'].append(a) elif xoptf: xoptf(o, a, conf) conf['debug'] = debug # get any var=val assignments eaten = [] for a in args: if a.find('=') > 0: k,v = a.split('=') if k in ['SRCTOP','OBJROOT','SRCTOPS','OBJROOTS']: if k == 'SRCTOP': k = 'SRCTOPS' elif k == 'OBJROOT': k = 'OBJROOTS' if v not in conf[k]: conf[k].append(v) else: conf[k] = v eaten.append(a) continue break for a in eaten: args.remove(a) - debug_out = getv(conf, 'debug_out', sys.stderr) + debug_out = conf.get('debug_out', sys.stderr) if debug: print("config:", file=debug_out) print("psyco=", have_psyco, file=debug_out) for k,v in list(conf.items()): print("%s=%s" % (k,v), file=debug_out) m = None for a in args: if a.endswith('.meta'): if not os.path.exists(a): continue m = klass(a, conf) elif a.startswith('@'): # there can actually multiple files per line for line in open(a[1:]): for f in line.strip().split(): if not os.path.exists(f): continue m = klass(f, conf) if output and m: print(m.dirdeps()) print(m.src_dirdeps('\nsrc:')) - dpdeps = getv(conf, 'DPDEPS') + dpdeps = conf.get('DPDEPS') if dpdeps: - m.file_depends(open(dpdeps, 'wb')) + m.file_depends(open(dpdeps, 'w')) return m if __name__ == '__main__': try: main(sys.argv) except: # yes, this goes to stdout print("ERROR: ", sys.exc_info()[1]) raise diff --git a/contrib/bmake/mk/rst2htm.mk b/contrib/bmake/mk/rst2htm.mk index 1db9792f4127..66eb8552f875 100644 --- a/contrib/bmake/mk/rst2htm.mk +++ b/contrib/bmake/mk/rst2htm.mk @@ -1,53 +1,55 @@ -# $Id: rst2htm.mk,v 1.11 2020/08/19 17:51:53 sjg Exp $ +# $Id: rst2htm.mk,v 1.12 2021/05/26 04:20:31 sjg Exp $ # # @(#) Copyright (c) 2009, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # convert reStructuredText to HTML, using rst2html.py from # docutils - http://docutils.sourceforge.net/ .if empty(TXTSRCS) TXTSRCS != 'ls' -1t ${.CURDIR}/*.txt ${.CURDIR}/*.rst 2>/dev/null; echo .endif RSTSRCS ?= ${TXTSRCS} HTMFILES ?= ${RSTSRCS:R:T:O:u:%=%.htm} -RST2HTML ?= rst2html.py +# can be empty, 4 or 5 +HTML_VERSION ?= +RST2HTML ?= rst2html${HTML_VERSION}.py RST2PDF ?= rst2pdf RST2S5 ?= rst2s5.py # the following will run RST2S5 if the target name contains the word 'slides' # otherwise it uses RST2HTML RST2HTM = ${"${.TARGET:T:M*slides*}":?${RST2S5}:${RST2HTML}} RST2HTM_SLIDES_FLAGS ?= ${RST2S5_FLAGS} RST2HTM_DOC_FLAGS ?= ${RST2HTML_FLAGS} RST2HTM_FLAGS ?= ${"${.TARGET:T:M*slides*}":?${RST2HTM_SLIDES_FLAGS}:${RST2HTM_DOC_FLAGS}} RST2PDF_FLAGS ?= ${"${.TARGET:T:M*slides*}":?${RST2PDF_SLIDES_FLAGS}:${RST2PDF_DOC_FLAGS}} RST_SUFFIXES ?= .rst .txt CLEANFILES += ${HTMFILES} html: ${HTMFILES} .SUFFIXES: ${RST_SUFFIXES} .htm .pdf ${RST_SUFFIXES:@s@$s.htm@}: ${RST2HTM} ${RST2HTM_FLAGS} ${FLAGS.${.TARGET}} ${.IMPSRC} ${.TARGET} ${RST_SUFFIXES:@s@$s.pdf@}: ${RST2PDF} ${RST2PDF_FLAGS} ${FLAGS.${.TARGET}} ${.IMPSRC} ${.TARGET} .for s in ${RSTSRCS:O:u} ${s:R:T}.htm: $s ${s:R:T}.pdf: $s .endfor diff --git a/contrib/bmake/nonints.h b/contrib/bmake/nonints.h index 38ac0c85518b..7119d798432b 100644 --- a/contrib/bmake/nonints.h +++ b/contrib/bmake/nonints.h @@ -1,408 +1,347 @@ -/* $NetBSD: nonints.h,v 1.202 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: nonints.h,v 1.213 2021/04/11 13:35:56 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 *); +bool Arch_ParseArchive(char **, GNodeList *, GNode *); void Arch_Touch(GNode *); void Arch_TouchLib(GNode *); void Arch_UpdateMTime(GNode *gn); void Arch_UpdateMemberMTime(GNode *gn); void Arch_FindLib(GNode *, SearchPath *); -Boolean Arch_LibOODate(GNode *); -Boolean Arch_IsLib(GNode *); +bool Arch_LibOODate(GNode *); +bool Arch_IsLib(GNode *); /* compat.c */ int Compat_RunCommand(const char *, GNode *, StringListNode *); void Compat_Run(GNodeList *); void Compat_Make(GNode *, GNode *); /* cond.c */ -CondEvalResult Cond_EvalCondition(const char *, Boolean *); +CondEvalResult Cond_EvalCondition(const char *, bool *); CondEvalResult Cond_EvalLine(const char *); void Cond_restore_depth(unsigned int); unsigned int Cond_save_depth(void); /* dir.c; see also dir.h */ MAKE_INLINE const char * str_basename(const char *pathname) { const char *lastSlash = strrchr(pathname, '/'); return lastSlash != NULL ? lastSlash + 1 : pathname; } MAKE_INLINE SearchPath * SearchPath_New(void) { SearchPath *path = bmake_malloc(sizeof *path); Lst_Init(&path->dirs); return path; } void SearchPath_Free(SearchPath *); /* for.c */ int For_Eval(const char *); -Boolean For_Accum(const char *); +bool For_Accum(const char *); void For_Run(int); /* job.c */ #ifdef WAIT_T -void JobReapChild(pid_t, WAIT_T, Boolean); +void JobReapChild(pid_t, WAIT_T, bool); #endif /* main.c */ -Boolean GetBooleanVar(const char *, Boolean); +bool GetBooleanExpr(const char *, bool); void Main_ParseArgLine(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 ParseBoolean(const char *, Boolean); +bool ParseBoolean(const char *, bool); 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 *(*ReadMoreProc)(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 *); +bool Parse_IsVar(const char *, VarAssign *out_var); +void Parse_Var(VarAssign *, GNode *); void Parse_AddIncludeDir(const char *); void Parse_File(const char *, int); void Parse_SetInput(const char *, int, int, ReadMoreProc, void *); void Parse_MainName(GNodeList *); int Parse_GetFatals(void); -/* str.c */ - -/* A read-only string that may need to be freed after use. */ -typedef struct FStr { - const char *str; - void *freeIt; -} FStr; - -/* A modifiable string that may need to be freed after use. */ -typedef struct MFStr { - char *str; - void *freeIt; -} MFStr; - -typedef struct Words { - char **words; - size_t len; - void *freeIt; -} Words; - -/* Return a string that is the sole owner of str. */ -MAKE_INLINE FStr -FStr_InitOwn(char *str) -{ - return (FStr){ str, str }; -} - -/* Return a string that refers to the shared str. */ -MAKE_INLINE FStr -FStr_InitRefer(const char *str) -{ - return (FStr){ str, NULL }; -} - -MAKE_INLINE void -FStr_Done(FStr *fstr) -{ - free(fstr->freeIt); -} - -/* Return a string that is the sole owner of str. */ -MAKE_INLINE MFStr -MFStr_InitOwn(char *str) -{ - return (MFStr){ str, str }; -} - -/* Return a string that refers to the shared str. */ -MAKE_INLINE MFStr -MFStr_InitRefer(char *str) -{ - return (MFStr){ str, NULL }; -} - -MAKE_INLINE void -MFStr_Done(MFStr *mfstr) -{ - free(mfstr->freeIt); -} - -Words Str_Words(const char *, Boolean); -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 *); +bool 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_ExtendPaths(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 *GNode_New(const char *); GNode *Targ_FindNode(const char *); GNode *Targ_GetNode(const char *); GNode *Targ_NewInternalNode(const char *); GNode *Targ_GetEndNode(void); void Targ_FindList(GNodeList *, StringList *); -Boolean Targ_Precious(const GNode *); +bool Targ_Precious(const GNode *); void Targ_SetMain(GNode *); void Targ_PrintCmds(GNode *); void Targ_PrintNode(GNode *, int); void Targ_PrintNodes(GNodeList *, int); const char *Targ_FmtTime(time_t); void Targ_PrintType(int); void Targ_PrintGraph(int); void Targ_Propagate(void); const char *GNodeMade_Name(GNodeMade); /* var.c */ void Var_Init(void); void Var_End(void); -typedef enum VarEvalFlags { - VARE_NONE = 0, +typedef enum VarEvalMode { /* - * Expand and evaluate variables during parsing. + * Only parse the expression but don't evaluate any part of it. * - * TODO: Document what Var_Parse and Var_Subst return when this flag - * is not set. + * TODO: Document what Var_Parse and Var_Subst return in this mode. + * As of 2021-03-15, they return unspecified, inconsistent results. */ - VARE_WANTRES = 1 << 0, + VARE_PARSE_ONLY, + + /* Parse and evaluate the expression. */ + VARE_WANTRES, /* - * Treat undefined variables as errors. - * Must only be used in combination with VARE_WANTRES. + * Parse and evaluate the expression. It is an error if a + * subexpression evaluates to undefined. */ - VARE_UNDEFERR = 1 << 1, + VARE_UNDEFERR, /* - * Keep '$$' as '$$' instead of reducing it to a single '$'. + * Parse and evaluate the expression. Keep '$$' as '$$' instead of + * reducing it to a single '$'. Subexpressions that evaluate to + * undefined expand to an empty string. * * 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. */ - VARE_KEEP_DOLLAR = 1 << 2, + VARE_EVAL_KEEP_DOLLAR, /* - * Keep undefined variables as-is instead of expanding them to an - * empty string. + * Parse and evaluate the expression. Keep undefined variables as-is + * instead of expanding them to an empty string. * * Example for a ':=' assignment: * CFLAGS = $(.INCLUDES) * CFLAGS := -I.. $(CFLAGS) * # If .INCLUDES (an undocumented special variable, by the * # way) is still undefined, the updated CFLAGS becomes * # "-I.. $(.INCLUDES)". */ - VARE_KEEP_UNDEF = 1 << 3 -} VarEvalFlags; + VARE_EVAL_KEEP_UNDEF, + + /* + * Parse and evaluate the expression. Keep '$$' as '$$' and preserve + * undefined subexpressions. + */ + VARE_KEEP_DOLLAR_UNDEF +} VarEvalMode; 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 = 1 << 1 } VarSetFlags; /* The state of error handling returned by Var_Parse. */ typedef enum VarParseResult { /* Both parsing and evaluation succeeded. */ VPR_OK, /* Parsing or evaluating failed, with an error message. */ VPR_ERR, /* * Parsing succeeded, undefined expressions are allowed and the * expression was still undefined after applying all modifiers. * No error message is printed in this case. * * Some callers handle this case differently, so return this * information to them, for now. * - * TODO: Replace this with a new flag VARE_KEEP_UNDEFINED. + * TODO: Instead of having this special return value, rather ensure + * that VARE_EVAL_KEEP_UNDEF is processed properly. */ VPR_UNDEF } VarParseResult; typedef enum VarExportMode { /* .export-env */ VEM_ENV, /* .export: Initial export or update an already exported variable. */ VEM_PLAIN, /* .export-literal: Do not expand the variable value. */ VEM_LITERAL } VarExportMode; void Var_Delete(GNode *, const char *); void Var_DeleteExpand(GNode *, const char *); void Var_Undef(const char *); void Var_Set(GNode *, const char *, const char *); void Var_SetExpand(GNode *, const char *, const char *); void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags); void Var_SetExpandWithFlags(GNode *, const char *, const char *, VarSetFlags); void Var_Append(GNode *, const char *, const char *); void Var_AppendExpand(GNode *, const char *, const char *); -Boolean Var_Exists(GNode *, const char *); -Boolean Var_ExistsExpand(GNode *, const char *); +bool Var_Exists(GNode *, const char *); +bool Var_ExistsExpand(GNode *, const char *); FStr Var_Value(GNode *, const char *); const char *GNode_ValueDirect(GNode *, const char *); -VarParseResult Var_Parse(const char **, GNode *, VarEvalFlags, FStr *); -VarParseResult Var_Subst(const char *, GNode *, VarEvalFlags, char **); +VarParseResult Var_Parse(const char **, GNode *, VarEvalMode, FStr *); +VarParseResult Var_Subst(const char *, GNode *, VarEvalMode, char **); void Var_Stats(void); void Var_Dump(GNode *); void Var_ReexportVars(void); void Var_Export(VarExportMode, const char *); void Var_ExportVars(const char *); -void Var_UnExport(Boolean, const char *); +void Var_UnExport(bool, const char *); void Global_Set(const char *, const char *); void Global_SetExpand(const char *, const char *); void Global_Append(const char *, const char *); void Global_Delete(const char *); /* util.c */ typedef void (*SignalProc)(int); SignalProc bmake_signal(int, SignalProc); diff --git a/contrib/bmake/parse.c b/contrib/bmake/parse.c index d14c7c46547a..3c2d75acdf9a 100644 --- a/contrib/bmake/parse.c +++ b/contrib/bmake/parse.c @@ -1,3306 +1,3326 @@ -/* $NetBSD: parse.c,v 1.549 2021/02/05 05:46:27 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.560 2021/06/21 10:42:06 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 IncludeFile instead. * - * Parse_IsVar Return TRUE if the given line is a variable + * 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.549 2021/02/05 05:46:27 rillig Exp $"); +MAKE_RCSID("$NetBSD: parse.c,v 1.560 2021/06/21 10:42:06 rillig Exp $"); /* types and constants */ /* * Structure for a file being read ("included file") */ typedef struct IFile { char *fname; /* name of file (relative? absolute?) */ - Boolean fromForLoop; /* simulated .include by the .for loop */ + bool 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 */ + bool 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; /* Function to read more data, with a single opaque argument. */ ReadMoreProc readMore; void *readMoreArg; 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 */ /* .MAIN and we don't have anything user-specified to make */ SP_MAIN, 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 = LST_INIT; #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 index 0 is the top-level makefile from * the command line, followed by the included files or .for loops, up to and * including the current file. * * See PrintStackTrace for how to interpret the data. */ 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; /* directories for "..." includes */ SearchPath *sysIncPath; /* directories 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, OP_NONE }, { ".DEFAULT", SP_DEFAULT, OP_NONE }, { ".DELETE_ON_ERROR", SP_DELETE_ON_ERROR, OP_NONE }, { ".END", SP_END, OP_NONE }, { ".ERROR", SP_ERROR, OP_NONE }, { ".EXEC", SP_ATTRIBUTE, OP_EXEC }, { ".IGNORE", SP_IGNORE, OP_IGNORE }, { ".INCLUDES", SP_INCLUDES, OP_NONE }, { ".INTERRUPT", SP_INTERRUPT, OP_NONE }, { ".INVISIBLE", SP_ATTRIBUTE, OP_INVISIBLE }, { ".JOIN", SP_ATTRIBUTE, OP_JOIN }, { ".LIBS", SP_LIBS, OP_NONE }, { ".MADE", SP_ATTRIBUTE, OP_MADE }, { ".MAIN", SP_MAIN, OP_NONE }, { ".MAKE", SP_ATTRIBUTE, OP_MAKE }, { ".MAKEFLAGS", SP_MFLAGS, OP_NONE }, { ".META", SP_META, OP_META }, { ".MFLAGS", SP_MFLAGS, OP_NONE }, { ".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, OP_NONE }, { ".NO_PARALLEL", SP_NOTPARALLEL, OP_NONE }, { ".NULL", SP_NULL, OP_NONE }, { ".OBJDIR", SP_OBJDIR, OP_NONE }, { ".OPTIONAL", SP_ATTRIBUTE, OP_OPTIONAL }, { ".ORDER", SP_ORDER, OP_NONE }, { ".PARALLEL", SP_PARALLEL, OP_NONE }, { ".PATH", SP_PATH, OP_NONE }, { ".PHONY", SP_PHONY, OP_PHONY }, #ifdef POSIX { ".POSIX", SP_POSIX, OP_NONE }, #endif { ".PRECIOUS", SP_PRECIOUS, OP_PRECIOUS }, { ".RECURSIVE", SP_ATTRIBUTE, OP_MAKE }, { ".SHELL", SP_SHELL, OP_NONE }, { ".SILENT", SP_SILENT, OP_SILENT }, { ".SINGLESHELL", SP_SINGLESHELL, OP_NONE }, { ".STALE", SP_STALE, OP_NONE }, { ".SUFFIXES", SP_SUFFIXES, OP_NONE }, { ".USE", SP_ATTRIBUTE, OP_USE }, { ".USEBEFORE", SP_ATTRIBUTE, OP_USEBEFORE }, { ".WAIT", SP_WAIT, OP_NONE }, }; /* 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 */ - Boolean used; /* XXX: have we used the data yet */ + bool 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, char *buf, size_t buflen) { struct loadedfile *lf; lf = bmake_malloc(sizeof *lf); lf->path = path == NULL ? "(stdin)" : path; lf->buf = buf; lf->len = buflen; - lf->used = FALSE; + lf->used = false; return lf; } static void loadedfile_destroy(struct loadedfile *lf) { free(lf->buf); free(lf); } /* * readMore() 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_readMore(void *x, size_t *len) { struct loadedfile *lf = x; if (lf->used) return NULL; - lf->used = TRUE; + lf->used = true; *len = lf->len; return lf->buf; } /* * Try to get the size of a file. */ -static Boolean +static bool load_getsize(int fd, size_t *ret) { struct stat st; if (fstat(fd, &st) < 0) - return FALSE; + return false; if (!S_ISREG(st.st_mode)) - return FALSE; + 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 1 GiB. 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 > 0x3fffffff) - return FALSE; + return false; *ret = (size_t)st.st_size; - return TRUE; + return true; } /* * 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. */ static struct loadedfile * loadfile(const char *path, int fd) { ssize_t n; Buffer buf; size_t filesize; if (path == NULL) { assert(fd == -1); fd = STDIN_FILENO; } if (load_getsize(fd, &filesize)) { /* * Avoid resizing the buffer later for no reason. * * At the same time leave space for adding a final '\n', * just in case it is missing in the file. */ filesize++; } else filesize = 1024; Buf_InitSize(&buf, filesize); for (;;) { assert(buf.len <= buf.cap); if (buf.len == buf.cap) { if (buf.cap > 0x1fffffff) { errno = EFBIG; Error("%s: file too large", path); exit(2); /* Not 1 so -q can distinguish error */ } Buf_Expand(&buf); } assert(buf.len < buf.cap); n = read(fd, buf.data + buf.len, buf.cap - buf.len); if (n < 0) { Error("%s: read error: %s", path, strerror(errno)); exit(2); /* Not 1 so -q can distinguish error */ } if (n == 0) break; buf.len += (size_t)n; } assert(buf.len <= buf.cap); if (!Buf_EndsWith(&buf, '\n')) Buf_AddByte(&buf, '\n'); if (path != NULL) close(fd); { struct loadedfile *lf = loadedfile_create(path, buf.data, buf.len); Buf_DoneData(&buf); return lf; } } static void PrintStackTrace(void) { const IFile *entries; size_t i, n; if (!(DEBUG(PARSE))) return; entries = GetInclude(0); n = includes.len; if (n == 0) return; n--; /* This entry is already in the diagnostic. */ /* * For the IFiles with fromForLoop, lineno seems to be sorted * backwards. This is because lineno is the number of completely * parsed lines, which for a .for loop is right after the * corresponding .endfor. The intuitive line number comes from * first_lineno instead, which points at the start of the .for loop. * * To make the stack trace intuitive, the entry below each chain of * .for loop entries must be ignored completely since neither its * lineno nor its first_lineno is useful. Instead, the topmost of * each chain of .for loop entries needs to be printed twice, once * with its first_lineno and once with its lineno. */ for (i = n; i-- > 0;) { const IFile *entry = entries + i; const char *fname = entry->fname; - Boolean printLineno; + bool printLineno; char dirbuf[MAXPATHLEN + 1]; if (fname[0] != '/' && strcmp(fname, "(stdin)") != 0) fname = realpath(fname, dirbuf); printLineno = !entry->fromForLoop; if (i + 1 < n && entries[i + 1].fromForLoop == printLineno) printLineno = entry->fromForLoop; if (printLineno) debug_printf("\tin .include from %s:%d\n", fname, entry->lineno); if (entry->fromForLoop) debug_printf("\tin .for loop from %s:%d\n", fname, entry->first_lineno); } } /* Check if the current character is escaped on the current line. */ -static Boolean +static bool ParseIsEscaped(const char *line, const char *c) { - Boolean active = FALSE; + bool 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 = 0; int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; do { int curr = start + (end - start) / 2; int diff = strcmp(str, parseKeywords[curr].name); if (diff == 0) return curr; if (diff < 0) end = curr - 1; else start = curr + 1; } while (start <= end); return -1; } static void PrintLocation(FILE *f, const char *fname, size_t lineno) { char dirbuf[MAXPATHLEN + 1]; FStr dir, base; if (*fname == '/' || strcmp(fname, "(stdin)") == 0) { (void)fprintf(f, "\"%s\" line %u: ", fname, (unsigned)lineno); return; } /* Find out which makefile is the culprit. * We try ${.PARSEDIR} and apply realpath(3) if not absolute. */ dir = Var_Value(SCOPE_GLOBAL, ".PARSEDIR"); if (dir.str == NULL) dir.str = "."; if (dir.str[0] != '/') dir.str = realpath(dir.str, dirbuf); base = Var_Value(SCOPE_GLOBAL, ".PARSEFILE"); if (base.str == NULL) base.str = str_basename(fname); (void)fprintf(f, "\"%s/%s\" line %u: ", dir.str, base.str, (unsigned)lineno); FStr_Done(&base); FStr_Done(&dir); } static void ParseVErrorInternal(FILE *f, const char *fname, size_t lineno, ParseErrorLevel type, const char *fmt, va_list ap) { - static Boolean fatal_warning_error_printed = FALSE; + static bool fatal_warning_error_printed = false; (void)fprintf(f, "%s: ", progname); 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) goto print_stack_trace; if (type == PARSE_WARNING && !opts.parseWarnFatal) goto print_stack_trace; fatals++; if (type == PARSE_WARNING && !fatal_warning_error_printed) { Error("parsing warnings being treated as errors"); - fatal_warning_error_printed = TRUE; + fatal_warning_error_printed = true; } print_stack_trace: PrintStackTrace(); } static void ParseErrorInternal(const char *fname, size_t lineno, ParseErrorLevel type, const char *fmt, ...) { va_list ap; (void)fflush(stdout); 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, fname, lineno, type, fmt, ap); va_end(ap); } } /* * 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 and handle an .info, .warning or .error directive. * For an .error directive, immediately exit. */ static void ParseMessage(ParseErrorLevel level, const char *levelName, const char *umsg) { char *xmsg; if (umsg[0] == '\0') { Parse_Error(PARSE_FATAL, "Missing argument for \".%s\"", levelName); return; } (void)Var_Subst(umsg, SCOPE_CMDLINE, VARE_WANTRES, &xmsg); /* TODO: handle errors */ Parse_Error(level, "%s", xmsg); free(xmsg); if (level == PARSE_FATAL) { PrintOnError(NULL, NULL); exit(1); } } /* * 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) +LinkSource(GNode *pgn, GNode *cgn, bool 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) +LinkToTargets(GNode *gn, bool isSpecial) { GNodeListNode *ln; for (ln = targets->first; ln != NULL; ln = ln->next) LinkSource(ln->datum, gn, isSpecial); } -static Boolean +static bool 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; + return false; } if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) { /* * 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. */ gn->type |= op; } - return TRUE; + return true; } static void ApplyDependencyOperator(GNodeType op) { GNodeListNode *ln; for (ln = targets->first; ln != NULL; ln = ln->next) if (!TryApplyDependencyOperator(ln->datum, op)) break; } /* * We add a .WAIT node in the dependency list. After any dynamic dependencies * (and filename globbing) 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 diagnostics). */ static void -ParseDependencySourceWait(Boolean isSpecial) +ParseDependencySourceWait(bool isSpecial) { static int wait_number = 0; char wait_src[16]; GNode *gn; 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, isSpecial); } -static Boolean +static bool ParseDependencySourceKeyword(const char *src, ParseSpecial specType) { int keywd; GNodeType op; if (*src != '.' || !ch_isupper(src[1])) - return FALSE; + return false; keywd = ParseFindKeyword(src); if (keywd == -1) - return FALSE; + return false; op = parseKeywords[keywd].op; if (op != OP_NONE) { ApplyDependencyOperator(op); - return TRUE; + return true; } if (parseKeywords[keywd].spec == SP_WAIT) { ParseDependencySourceWait(specType != SP_NOT); - return TRUE; + return true; } - return FALSE; + return false; } static void ParseDependencySourceMain(const char *src) { /* * In a line like ".MAIN: source1 source2", add all sources to the * list of things to create, but only if the user didn't specify a * target on the command line and .MAIN occurs for the first time. * - * See ParseDoDependencyTargetSpecial, branch SP_MAIN. + * See ParseDependencyTargetSpecial, branch SP_MAIN. * See unit-tests/cond-func-make-main.mk. */ Lst_Append(&opts.create, bmake_strdup(src)); /* * Add the name to the .TARGETS variable as well, so the user can * employ that, if desired. */ Global_Append(".TARGETS", src); } static void ParseDependencySourceOrder(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 ParseDependencySourceOther(const char *src, GNodeType tOp, ParseSpecial specType) { GNode *gn; /* * The source is not an attribute, so find/create a node for it. * After that, 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 != OP_NONE) gn->type |= tOp; 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 ParseDependencySource(GNodeType tOp, const char *src, ParseSpecial specType) { if (ParseDependencySourceKeyword(src, specType)) return; if (specType == SP_MAIN) ParseDependencySourceMain(src); else if (specType == SP_ORDER) ParseDependencySourceOrder(src); else ParseDependencySourceOther(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)) { DEBUG1(MAKE, "Setting main node to \"%s\"\n", gn->name); 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, "Invalid line type"); } 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; FStr nested_val; - (void)Var_Parse(&nested_p, SCOPE_CMDLINE, VARE_NONE, - &nested_val); + (void)Var_Parse(&nested_p, SCOPE_CMDLINE, + VARE_PARSE_ONLY, &nested_val); /* TODO: handle errors */ FStr_Done(&nested_val); cp += nested_p - cp; } else cp++; } *pp = cp; } -/* Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. */ +/* + * Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. + * + * See the tests deptgt-*.mk. + */ static void -ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, - const char *targetName, - SearchPathList **inout_paths) +ParseDependencyTargetSpecial(ParseSpecial *inout_specType, + const char *targetName, + SearchPathList **inout_paths) { switch (*inout_specType) { case SP_PATH: if (*inout_paths == NULL) *inout_paths = Lst_New(); Lst_Append(*inout_paths, &dirSearchPath); break; case SP_MAIN: /* * 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(targetName); if (doing_depend) ParseMark(gn); gn->type |= OP_NOTMAIN | OP_SPECIAL; Lst_Append(targets, gn); break; } case SP_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); defaultNode = gn; break; } case SP_DELETE_ON_ERROR: - deleteOnError = TRUE; + deleteOnError = true; break; case SP_NOTPARALLEL: opts.maxJobs = 1; break; case SP_SINGLESHELL: - opts.compatMake = TRUE; + 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 *suffixName, - SearchPathList **inout_paths) +static bool +ParseDependencyTargetPath(const char *suffixName, + SearchPathList **inout_paths) { SearchPath *path; path = Suff_GetPath(suffixName); if (path == NULL) { Parse_Error(PARSE_FATAL, "Suffix '%s' not defined (yet)", suffixName); - return FALSE; + return false; } if (*inout_paths == NULL) *inout_paths = Lst_New(); Lst_Append(*inout_paths, path); - return TRUE; + return true; } /* * See if it's a special target and if so set specType to match it. */ -static Boolean -ParseDoDependencyTarget(const char *targetName, - ParseSpecial *inout_specType, - GNodeType *out_tOp, SearchPathList **inout_paths) +static bool +ParseDependencyTarget(const char *targetName, + ParseSpecial *inout_specType, + GNodeType *out_tOp, SearchPathList **inout_paths) { int keywd; if (!(targetName[0] == '.' && ch_isupper(targetName[1]))) - return TRUE; + return true; /* * See if the target is a special target that must have it * or its sources handled specially. */ keywd = ParseFindKeyword(targetName); if (keywd != -1) { if (*inout_specType == SP_PATH && parseKeywords[keywd].spec != SP_PATH) { Parse_Error(PARSE_FATAL, "Mismatched special targets"); - return FALSE; + return false; } *inout_specType = parseKeywords[keywd].spec; *out_tOp = parseKeywords[keywd].op; - ParseDoDependencyTargetSpecial(inout_specType, targetName, + ParseDependencyTargetSpecial(inout_specType, targetName, inout_paths); } else if (strncmp(targetName, ".PATH", 5) == 0) { *inout_specType = SP_PATH; - if (!ParseDoDependencyTargetPath(targetName + 5, inout_paths)) - return FALSE; + if (!ParseDependencyTargetPath(targetName + 5, inout_paths)) + return false; } - return TRUE; + return true; } static void -ParseDoDependencyTargetMundane(char *targetName, StringList *curTargs) +ParseDependencyTargetMundane(char *targetName, StringList *curTargs) { if (Dir_HasWildcards(targetName)) { /* * 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 = SearchPath_New(); SearchPath_Expand(emptyPath, targetName, curTargs); SearchPath_Free(emptyPath); } else { /* * No wildcards, but we want to avoid code duplication, * so create a list with the word on it. */ Lst_Append(curTargs, targetName); } /* 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) +ParseDependencyTargetExtraWarn(char **pp, const char *lstart) { - Boolean warning = FALSE; + bool warning = false; char *cp = *pp; while (*cp != '\0') { if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':')) break; if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t')) - warning = TRUE; + warning = true; cp++; } if (warning) Parse_Error(PARSE_WARNING, "Extra target ignored"); *pp = cp; } static void -ParseDoDependencyCheckSpec(ParseSpecial specType) +ParseDependencyCheckSpec(ParseSpecial specType) { switch (specType) { default: Parse_Error(PARSE_WARNING, "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 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. */ break; } } -static Boolean -ParseDoDependencyParseOp(char **pp, const char *lstart, GNodeType *out_op) +/* + * In a dependency line like 'targets: sources' or 'targets! sources', parse + * the operator ':', '::' or '!' from between the targets and the sources. + */ +static bool +ParseDependencyOp(char **pp, const char *lstart, GNodeType *out_op) { const char *cp = *pp; if (*cp == '!') { *out_op = OP_FORCE; (*pp)++; - return TRUE; + return true; } if (*cp == ':') { if (cp[1] == ':') { *out_op = OP_DOUBLEDEP; (*pp) += 2; } else { *out_op = OP_DEPENDS; (*pp)++; } - return TRUE; + return true; } { const char *msg = lstart[0] == '.' ? "Unknown directive" : "Missing dependency operator"; Parse_Error(PARSE_FATAL, "%s", msg); - return FALSE; + return false; } } static void ClearPaths(SearchPathList *paths) { if (paths != NULL) { SearchPathListNode *ln; for (ln = paths->first; ln != NULL; ln = ln->next) SearchPath_Clear(ln->datum); } Dir_SetPATH(); } +/* + * 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). + */ static void -ParseDoDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) +ParseDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) { switch (specType) { case SP_SUFFIXES: Suff_ClearSuffixes(); break; case SP_PRECIOUS: - allPrecious = TRUE; + allPrecious = true; break; case SP_IGNORE: - opts.ignoreErrors = TRUE; + opts.ignoreErrors = true; break; case SP_SILENT: - opts.beSilent = TRUE; + opts.beSilent = true; break; case SP_PATH: ClearPaths(paths); break; #ifdef POSIX case SP_POSIX: Global_Set("%POSIX", "1003.2"); 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)SearchPath_Add(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) +ParseDependencySourceSpecial(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(FALSE, "%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) +static bool +ParseDependencyTargets(char **inout_cp, + char **inout_line, + const char *lstart, + ParseSpecial *inout_specType, + GNodeType *inout_tOp, + SearchPathList **inout_paths, + StringList *curTargs) { char *cp; char *tgt = *inout_line; char savec; const char *p; 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 = tgt; p = cp; ParseDependencyTargetWord(&p, lstart); cp += p - cp; /* * 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 + * 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(&tgt, targets, SCOPE_CMDLINE)) { Parse_Error(PARSE_FATAL, "Error in archive specification: \"%s\"", tgt); - return FALSE; + return false; } cp = tgt; continue; } if (*cp == '\0') { ParseErrorNoDependency(lstart); - return FALSE; + return false; } /* Insert a null terminator. */ savec = *cp; *cp = '\0'; - if (!ParseDoDependencyTarget(tgt, inout_specType, inout_tOp, + if (!ParseDependencyTarget(tgt, inout_specType, inout_tOp, inout_paths)) - return FALSE; + 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 && *tgt != '\0') - ParseDoDependencyTargetMundane(tgt, curTargs); + ParseDependencyTargetMundane(tgt, curTargs); else if (*inout_specType == SP_PATH && *tgt != '.' && *tgt != '\0') Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", tgt); /* 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) - ParseDoDependencyTargetExtraWarn(&cp, lstart); + ParseDependencyTargetExtraWarn(&cp, lstart); else pp_skip_whitespace(&cp); tgt = cp; if (*tgt == '\0') break; if ((*tgt == '!' || *tgt == ':') && !ParseIsEscaped(lstart, tgt)) break; } *inout_cp = cp; *inout_line = tgt; - return TRUE; + return true; } static void -ParseDoDependencySourcesSpecial(char *start, char *end, - ParseSpecial specType, SearchPathList *paths) +ParseDependencySourcesSpecial(char *start, char *end, + ParseSpecial specType, SearchPathList *paths) { char savec; while (*start != '\0') { while (*end != '\0' && !ch_isspace(*end)) end++; savec = *end; *end = '\0'; - ParseDoDependencySourceSpecial(specType, start, paths); + ParseDependencySourceSpecial(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) +static bool +ParseDependencySourcesMundane(char *start, char *end, + ParseSpecial specType, GNodeType tOp) { 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 != '\0' && !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_INIT; if (!Arch_ParseArchive(&start, &sources, SCOPE_CMDLINE)) { Parse_Error(PARSE_FATAL, "Error in source archive spec \"%s\"", start); - return FALSE; + return false; } while (!Lst_IsEmpty(&sources)) { GNode *gn = Lst_Dequeue(&sources); ParseDependencySource(tOp, gn->name, specType); } Lst_Done(&sources); end = start; } else { if (*end != '\0') { *end = '\0'; end++; } ParseDependencySource(tOp, start, specType); } pp_skip_whitespace(&end); start = end; } - return TRUE; + return true; +} + +/* + * In a dependency line like 'targets: sources', parse the sources. + * + * See the tests depsrc-*.mk. + */ +static void +ParseDependencySources(char *const line, char *const cp, + GNodeType const tOp, + ParseSpecial const specType, + SearchPathList ** inout_paths) +{ + if (line[0] == '\0') { + ParseDependencySourcesEmpty(specType, *inout_paths); + } else if (specType == SP_MFLAGS) { + Main_ParseArgLine(line); + /* + * Set the initial character to a null-character so the loop + * to get sources won't get anything. + */ + *line = '\0'; + } else if (specType == SP_SHELL) { + if (!Job_ParseShell(line)) { + Parse_Error(PARSE_FATAL, + "improper shell specification"); + return; + } + *line = '\0'; + } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL || + specType == SP_DELETE_ON_ERROR) { + *line = '\0'; + } + + /* Now go for the sources. */ + if (specType == SP_SUFFIXES || specType == SP_PATH || + specType == SP_INCLUDES || specType == SP_LIBS || + specType == SP_NULL || specType == SP_OBJDIR) { + ParseDependencySourcesSpecial(line, cp, specType, + *inout_paths); + if (*inout_paths != NULL) { + Lst_Free(*inout_paths); + *inout_paths = NULL; + } + if (specType == SP_PATH) + Dir_SetPATH(); + } else { + assert(*inout_paths == NULL); + if (!ParseDependencySourcesMundane(line, cp, specType, tOp)) + return; + } + + FindMainTarget(); } /* * 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 ParseOp 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 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) +ParseDependency(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 */ GNodeType tOp; /* operator from special target */ /* target names to be found and added to the targets list */ StringList curTargs = LST_INIT; 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); + DEBUG1(PARSE, "ParseDependency(%s)\n", line); tOp = OP_NONE; paths = NULL; /* * First, grind through the targets. */ - /* XXX: don't use line as an iterator variable */ - if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp, + /* XXX: don't use 'line' as an iterator variable */ + if (!ParseDependencyTargets(&cp, &line, lstart, &specType, &tOp, &paths, &curTargs)) goto out; /* * Don't need the list of target names anymore. * The targets themselves are now in the global variable 'targets'. */ Lst_Done(&curTargs); Lst_Init(&curTargs); if (!Lst_IsEmpty(targets)) - ParseDoDependencyCheckSpec(specType); + ParseDependencyCheckSpec(specType); /* * Have now parsed all the target names. Must parse the operator next. */ - if (!ParseDoDependencyParseOp(&cp, lstart, &op)) + if (!ParseDependencyOp(&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; /* 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[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. */ - 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 != NULL) { - Lst_Free(paths); - paths = NULL; - } - if (specType == SP_PATH) - Dir_SetPATH(); - } else { - assert(paths == NULL); - if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp)) - goto out; - } - - FindMainTarget(); + ParseDependencySources(line, cp, tOp, specType, &paths); out: if (paths != NULL) Lst_Free(paths); Lst_Done(&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 +bool Parse_IsVar(const char *p, VarAssign *out_var) { VarAssignParsed pvar; const char *firstSpace = NULL; int level = 0; 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 (*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 == ':' && 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; + 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; + return true; } if (firstSpace != NULL) - return FALSE; + return false; } - return FALSE; + return false; } /* * Check for syntax errors such as unclosed expressions or unknown modifiers. */ static void VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *scope) { if (opts.strict) { if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) { char *expandedValue; - (void)Var_Subst(uvalue, scope, VARE_NONE, + (void)Var_Subst(uvalue, scope, VARE_PARSE_ONLY, &expandedValue); /* TODO: handle errors */ free(expandedValue); } } } static void VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, FStr *out_avalue) { char *evalue; /* * make sure that we set the variable the first time to nothing * so that it gets substituted. * * TODO: Add a test that demonstrates why this code is needed, * apart from making the debug log longer. */ if (!Var_ExistsExpand(scope, name)) Var_SetExpand(scope, name, ""); - (void)Var_Subst(uvalue, scope, - VARE_WANTRES | VARE_KEEP_DOLLAR | VARE_KEEP_UNDEF, &evalue); + (void)Var_Subst(uvalue, scope, VARE_KEEP_DOLLAR_UNDEF, &evalue); /* TODO: handle errors */ Var_SetExpand(scope, name, evalue); *out_avalue = FStr_InitOwn(evalue); } static void VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope, FStr *out_avalue) { FStr cmd; const char *errfmt; char *cmdOut; cmd = FStr_InitRefer(uvalue); if (strchr(cmd.str, '$') != NULL) { char *expanded; - (void)Var_Subst(cmd.str, SCOPE_CMDLINE, - VARE_WANTRES | VARE_UNDEFERR, &expanded); + (void)Var_Subst(cmd.str, SCOPE_CMDLINE, VARE_UNDEFERR, + &expanded); /* TODO: handle errors */ cmd = FStr_InitOwn(expanded); } cmdOut = Cmd_Exec(cmd.str, &errfmt); Var_SetExpand(scope, name, cmdOut); *out_avalue = FStr_InitOwn(cmdOut); if (errfmt != NULL) Parse_Error(PARSE_WARNING, errfmt, cmd.str); FStr_Done(&cmd); } /* * Perform a variable assignment. * - * The actual value of the variable is returned in *out_TRUE_avalue. + * The actual value of the variable is returned in *out_true_avalue. * Especially for VAR_SUBST and VAR_SHELL this can differ from the literal * value. * * Return whether the assignment was actually performed, which is usually * the case. It is only skipped if the operator is '?=' and the variable * already exists. */ -static Boolean +static bool VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, - GNode *scope, FStr *out_TRUE_avalue) + GNode *scope, FStr *out_true_avalue) { FStr avalue = FStr_InitRefer(uvalue); if (op == VAR_APPEND) Var_AppendExpand(scope, name, uvalue); else if (op == VAR_SUBST) VarAssign_EvalSubst(scope, name, uvalue, &avalue); else if (op == VAR_SHELL) VarAssign_EvalShell(name, uvalue, scope, &avalue); else { if (op == VAR_DEFAULT && Var_ExistsExpand(scope, name)) - return FALSE; + return false; /* Normal assignment -- just do it. */ Var_SetExpand(scope, name, uvalue); } - *out_TRUE_avalue = avalue; - return TRUE; + *out_true_avalue = avalue; + return true; } static void VarAssignSpecial(const char *name, const char *avalue) { if (strcmp(name, MAKEOVERRIDES) == 0) - Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */ + 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) Job_SetPrefix(); else if (strcmp(name, MAKE_EXPORTED) == 0) Var_ExportVars(avalue); } /* Perform the variable variable assignment in the given scope. */ void -Parse_DoVar(VarAssign *var, GNode *scope) +Parse_Var(VarAssign *var, GNode *scope) { FStr avalue; /* actual value (maybe expanded) */ VarCheckSyntax(var->op, var->value, scope); if (VarAssign_Eval(var->varname, var->op, var->value, scope, &avalue)) { VarAssignSpecial(var->varname, avalue.str); FStr_Done(&avalue); } free(var->varname); } /* * See if the command possibly calls a sub-make by using the variable * expressions ${.MAKE}, ${MAKE} or the plain word "make". */ -static Boolean +static bool MaybeSubMake(const char *cmd) { 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; + return true; if (*p != '$') continue; 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 true; } - return FALSE; + 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 (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)SearchPath_Add(parseIncPath, dir); } /* * 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. * * 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 -IncludeFile(char *file, Boolean isSystem, Boolean depinc, Boolean silent) +IncludeFile(char *file, bool isSystem, bool depinc, bool silent) { struct loadedfile *lf; char *fullname; /* full pathname of file */ char *newName; char *slash, *incdir; int fd; int i; fullname = file[0] == '/' ? bmake_strdup(file) : NULL; if (fullname == NULL && !isSystem) { /* * 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 file. */ incdir = bmake_strdup(CurFile()->fname); 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) { slash = strrchr(incdir + 1, '/'); if (slash == NULL || strcmp(slash, "/..") == 0) break; *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. */ const char *suff; SearchPath *suffPath = NULL; if ((suff = strrchr(file, '.')) != NULL) { suffPath = Suff_GetPath(suff); if (suffPath != NULL) fullname = Dir_FindFile(file, suffPath); } if (fullname == NULL) { fullname = Dir_FindFile(file, parseIncPath); if (fullname == NULL) fullname = Dir_FindFile(file, &dirSearchPath); } } } /* Looking for a system file or file still not found */ if (fullname == NULL) { /* * Look for it on the system path */ SearchPath *path = Lst_IsEmpty(&sysIncPath->dirs) ? 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_readMore, lf); CurFile()->lf = lf; if (depinc) doing_depend = depinc; /* only turn it on */ } static void -ParseDoInclude(char *directive) +ParseInclude(char *directive) { char endc; /* the character which ends the file spec */ char *cp; /* current position in file spec */ - Boolean silent = directive[0] != 'i'; + bool silent = directive[0] != 'i'; char *file = directive + (silent ? 8 : 7); /* Skip to delimiter character so we know where to look */ 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 == '<') endc = '>'; else endc = '"'; /* Skip to matching delimiter */ for (cp = ++file; *cp != '\0' && *cp != endc; cp++) continue; if (*cp != endc) { Parse_Error(PARSE_FATAL, "Unclosed .include filename. '%c' expected", endc); return; } *cp = '\0'; /* * Substitute for any variables in the filename before trying to * find the file. */ (void)Var_Subst(file, SCOPE_CMDLINE, VARE_WANTRES, &file); /* TODO: handle errors */ IncludeFile(file, endc == '>', directive[0] == '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; + const char *slash, *basename; + FStr dirname; slash = strrchr(filename, '/'); if (slash == NULL) { - dirname = curdir; + dirname = FStr_InitRefer(curdir); basename = filename; - freeIt = NULL; } else { - dirname = freeIt = bmake_strsedup(filename, slash); + dirname = FStr_InitOwn(bmake_strsedup(filename, slash)); basename = slash + 1; } - Global_SetExpand(dirvar, dirname); + Global_SetExpand(dirvar, dirname.str); Global_SetExpand(filevar, basename); DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n", - __func__, dirvar, dirname, filevar, basename); - free(freeIt); + __func__, dirvar, dirname.str, filevar, basename); + FStr_Done(&dirname); } /* * 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 { Global_Delete(".INCLUDEDFROMDIR"); Global_Delete(".INCLUDEDFROMFILE"); } } -static Boolean +static bool 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 */ + 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 */ + return false; /* cannot contain word */ if (memcmp(p, word, wordLen) == 0 && (p[wordLen] == '\0' || p[wordLen] == ' ')) - return TRUE; + return true; } - return FALSE; + 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 +static bool VarContainsWord(const char *varname, const char *word) { FStr val = Var_Value(SCOPE_GLOBAL, varname); - Boolean found = val.str != NULL && StrContainsWord(val.str, word); + bool found = val.str != NULL && StrContainsWord(val.str, word); FStr_Done(&val); return found; } /* * Track the makefiles we read - so makefiles can set dependencies on them. * 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)) Global_Append(MAKE_MAKEFILES, name); } /* * Start parsing from the given source. * * The given file is added to the includes stack. */ void Parse_SetInput(const char *name, int lineno, int fd, ReadMoreProc readMore, void *readMoreArg) { IFile *curFile; char *buf; size_t len; - Boolean fromForLoop = name == NULL; + bool fromForLoop = name == NULL; if (fromForLoop) name = CurFile()->fname; else ParseTrackInput(name); DEBUG3(PARSE, "Parse_SetInput: %s %s, line %d\n", readMore == loadedfile_readMore ? "file" : ".for loop in", name, lineno); if (fd == -1 && readMore == NULL) /* sanity */ return; curFile = Vector_Push(&includes); curFile->fname = bmake_strdup(name); curFile->fromForLoop = fromForLoop; curFile->lineno = lineno; curFile->first_lineno = lineno; curFile->readMore = readMore; curFile->readMoreArg = readMoreArg; curFile->lf = NULL; curFile->depending = doing_depend; /* restore this on EOF */ assert(readMore != NULL); /* Get first block of input data */ buf = curFile->readMore(curFile->readMoreArg, &len); if (buf == NULL) { /* Was all a waste of time ... */ if (curFile->fname != NULL) 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) +static bool +IsInclude(const char *dir, bool sysv) { if (dir[0] == 's' || dir[0] == '-' || (dir[0] == 'd' && !sysv)) dir++; if (strncmp(dir, "include", 7) != 0) - return FALSE; + 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 +static bool IsSysVInclude(const char *line) { const char *p; - if (!IsInclude(line, TRUE)) - return FALSE; + if (!IsInclude(line, true)) + return false; /* Avoid interpreting a dependency line as an include */ for (p = line; (p = strchr(p, ':')) != NULL;) { /* end of line -> it's a dependency */ if (*++p == '\0') - return FALSE; + return false; /* '::' operator or ': ' -> it's a dependency */ if (*p == ':' || ch_isspace(*p)) - return FALSE; + return false; } - return TRUE; + 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 */ - Boolean done = FALSE; - Boolean silent = line[0] != 'i'; + bool done = false; + bool 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, SCOPE_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 != '\0' && !ch_isspace(*cp); cp++) continue; if (*cp != '\0') *cp = '\0'; else - done = TRUE; + done = true; - IncludeFile(file, FALSE, FALSE, silent); + IncludeFile(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 != '\0' && *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, SCOPE_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 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. + * 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 +static bool ParseEOF(void) { char *ptr; size_t len; IFile *curFile = CurFile(); assert(curFile->readMore != NULL); doing_depend = curFile->depending; /* restore this */ /* get next input buffer, if any */ ptr = curFile->readMore(curFile->readMoreArg, &len); curFile->buf_ptr = ptr; curFile->buf_freeIt = ptr; curFile->buf_end = ptr == NULL ? NULL : ptr + len; curFile->lineno = curFile->first_lineno; if (ptr != NULL) - return TRUE; /* Iterate again */ + 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 */ Global_Delete(".PARSEDIR"); Global_Delete(".PARSEFILE"); Global_Delete(".INCLUDEDFROMDIR"); Global_Delete(".INCLUDEDFROMFILE"); - return FALSE; + return false; } curFile = CurFile(); DEBUG2(PARSE, "ParseEOF: returning to file %s, line %d\n", curFile->fname, curFile->lineno); ParseSetParseFile(curFile->fname); - return TRUE; + return true; } typedef enum ParseRawLineResult { PRLR_LINE, PRLR_EOF, PRLR_ERROR } ParseRawLineResult; /* * Parse until the end of a line, taking into account lines that end with * backslash-newline. */ static ParseRawLineResult ParseRawLine(IFile *curFile, char **out_line, char **out_line_end, char **out_firstBackslash, char **out_firstComment) { char *line = curFile->buf_ptr; char *p = line; char *line_end = line; char *firstBackslash = NULL; char *firstComment = NULL; ParseRawLineResult res = PRLR_LINE; curFile->lineno++; for (;;) { char ch; if (p == curFile->buf_end) { res = PRLR_EOF; break; } ch = *p; if (ch == '\0' || (ch == '\\' && p + 1 < curFile->buf_end && p[1] == '\0')) { Parse_Error(PARSE_FATAL, "Zero byte read from file"); return PRLR_ERROR; } /* Treat next character after '\' as literal. */ if (ch == '\\') { if (firstBackslash == NULL) firstBackslash = p; if (p[1] == '\n') { curFile->lineno++; if (p + 2 == curFile->buf_end) { line_end = p; *line_end = '\n'; p += 2; continue; } } p += 2; line_end = p; assert(p <= curFile->buf_end); continue; } /* * Remember the first '#' for comment stripping, unless * the previous char was '[', as in the modifier ':[#]'. */ if (ch == '#' && firstComment == NULL && !(p > line && p[-1] == '[')) firstComment = line_end; p++; if (ch == '\n') break; /* We are not interested in trailing whitespace. */ if (!ch_isspace(ch)) line_end = p; } *out_line = line; curFile->buf_ptr = p; *out_line_end = line_end; *out_firstBackslash = firstBackslash; *out_firstComment = firstComment; return res; } /* * Beginning at start, unescape '\#' to '#' and replace backslash-newline * with a single space. */ static void UnescapeBackslash(char *line, char *start) { char *src = start; char *dst = start; char *spaceStart = line; for (;;) { char ch = *src++; if (ch != '\\') { if (ch == '\0') break; *dst++ = ch; continue; } ch = *src++; if (ch == '\0') { /* Delete '\\' at end of buffer */ dst--; break; } /* Delete '\\' from before '#' on non-command lines */ if (ch == '#' && line[0] != '\t') { *dst++ = ch; continue; } if (ch != '\n') { /* Leave '\\' in buffer for later */ *dst++ = '\\'; /* * Make sure we don't delete an escaped ' ' from the * line end. */ spaceStart = dst + 1; *dst++ = ch; continue; } /* * Escaped '\n' -- replace following whitespace with a single * ' '. */ pp_skip_hspace(&src); *dst++ = ' '; } /* Delete any trailing spaces - eg from empty continuations */ while (dst > spaceStart && ch_isspace(dst[-1])) dst--; *dst = '\0'; } typedef enum GetLineMode { /* * Return the next line that is neither empty nor a comment. * Backslash line continuations are folded into a single space. * A trailing comment, if any, is discarded. */ GLM_NONEMPTY, /* * Return the next line, even if it is empty or a comment. * Preserve backslash-newline to keep the line numbers correct. * * Used in .for loops to collect the body of the loop while waiting * for the corresponding .endfor. */ GLM_FOR_BODY, /* * Return the next line that starts with a dot. * Backslash line continuations are folded into a single space. * A trailing comment, if any, is discarded. * * Used in .if directives to skip over irrelevant branches while * waiting for the corresponding .endif. */ GLM_DOT } GetLineMode; /* Return the next "interesting" logical line from the current file. */ static char * ParseGetLine(GetLineMode mode) { IFile *curFile = CurFile(); char *line; char *line_end; char *firstBackslash; char *firstComment; for (;;) { ParseRawLineResult res = ParseRawLine(curFile, &line, &line_end, &firstBackslash, &firstComment); if (res == PRLR_ERROR) return NULL; if (line_end == line || firstComment == line) { if (res == PRLR_EOF) return NULL; if (mode != GLM_FOR_BODY) continue; } /* We now have a line of data */ assert(ch_isspace(*line_end)); *line_end = '\0'; if (mode == GLM_FOR_BODY) return line; /* Don't join the physical lines. */ if (mode == GLM_DOT && line[0] != '.') continue; break; } /* Brutally ignore anything after a non-escaped '#' in non-commands. */ if (firstComment != NULL && line[0] != '\t') *firstComment = '\0'; /* If we didn't see a '\\' then the in-situ data is fine. */ if (firstBackslash == NULL) return line; /* Remove escapes from '\n' and '#' */ UnescapeBackslash(line, firstBackslash); return line; } -static Boolean +static bool ParseSkippedBranches(void) { char *line; while ((line = ParseGetLine(GLM_DOT)) != NULL) { if (Cond_EvalLine(line) == COND_PARSE) break; /* * TODO: Check for typos in .elif directives * such as .elsif or .elseif. * * This check will probably duplicate some of * the code in ParseLine. Most of the code * there cannot apply, only ParseVarassign and - * ParseDependency can, and to prevent code + * ParseDependencyLine can, and to prevent code * duplication, these would need to be called * with a flag called onlyCheckSyntax. * * See directive-elif.mk for details. */ } return line != NULL; } -static Boolean +static bool ParseForLoop(const char *line) { int rval; int firstLineno; rval = For_Eval(line); if (rval == 0) - return FALSE; /* Not a .for line */ + return false; /* Not a .for line */ if (rval < 0) - return TRUE; /* Syntax error - error printed, ignore line */ + return true; /* Syntax error - error printed, ignore line */ firstLineno = CurFile()->lineno; /* Accumulate loop lines until matching .endfor */ do { line = ParseGetLine(GLM_FOR_BODY); if (line == NULL) { Parse_Error(PARSE_FATAL, "Unexpected end of file in for loop."); break; } } while (For_Accum(line)); For_Run(firstLineno); /* Stash each iteration as a new 'input file' */ - return TRUE; /* Read next line from for-loop buffer */ + return true; /* Read next line from for-loop buffer */ } /* * Read an entire line from the input file. * * Empty lines, .if and .for are completely handled by this function, * leaving only variable assignments, other directives, dependency lines * and shell commands to the caller. * * Results: * A line without its newline and without any trailing whitespace, * or NULL. */ static char * ParseReadLine(void) { char *line; for (;;) { line = ParseGetLine(GLM_NONEMPTY); 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: if (!ParseSkippedBranches()) return NULL; continue; case COND_PARSE: continue; case COND_INVALID: /* Not a conditional line */ if (ParseForLoop(line)) continue; break; } return line; } } static void FinishDependencyGroup(void) { GNodeListNode *ln; if (targets == NULL) return; for (ln = targets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; 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 } } -MAKE_INLINE Boolean +MAKE_INLINE bool IsDirective(const char *dir, size_t dirlen, const char *name) { return dirlen == strlen(name) && memcmp(dir, name, dirlen) == 0; } /* * See if the line starts with one of the known directives, and if so, handle * the directive. */ -static Boolean +static bool ParseDirective(char *line) { char *cp = line + 1; const char *dir, *arg; size_t dirlen; pp_skip_whitespace(&cp); - if (IsInclude(cp, FALSE)) { - ParseDoInclude(cp); - return TRUE; + if (IsInclude(cp, false)) { + ParseInclude(cp); + return true; } dir = cp; while (ch_isalpha(*cp) || *cp == '-') cp++; dirlen = (size_t)(cp - dir); if (*cp != '\0' && !ch_isspace(*cp)) - return FALSE; + return false; pp_skip_whitespace(&cp); arg = cp; if (IsDirective(dir, dirlen, "undef")) { Var_Undef(cp); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "export")) { Var_Export(VEM_PLAIN, arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "export-env")) { Var_Export(VEM_ENV, arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "export-literal")) { Var_Export(VEM_LITERAL, arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "unexport")) { - Var_UnExport(FALSE, arg); - return TRUE; + Var_UnExport(false, arg); + return true; } else if (IsDirective(dir, dirlen, "unexport-env")) { - Var_UnExport(TRUE, arg); - return TRUE; + Var_UnExport(true, arg); + return true; } else if (IsDirective(dir, dirlen, "info")) { ParseMessage(PARSE_INFO, "info", arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "warning")) { ParseMessage(PARSE_WARNING, "warning", arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "error")) { ParseMessage(PARSE_FATAL, "error", arg); - return TRUE; + return true; } - return FALSE; + return false; } -static Boolean +static bool ParseVarassign(const char *line) { VarAssign var; if (!Parse_IsVar(line, &var)) - return FALSE; + return false; FinishDependencyGroup(); - Parse_DoVar(&var, SCOPE_GLOBAL); - return TRUE; + Parse_Var(&var, SCOPE_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] == '{')) level++; else if (level > 0 && (*p == ')' || *p == '}')) level--; else if (level == 0 && *p == ';') break; } return p; } /* - * dependency -> target... op [source...] + * dependency -> target... op [source...] [';' command] * op -> ':' | '::' | '!' */ static void -ParseDependency(char *line) +ParseDependencyLine(char *line) { - VarEvalFlags eflags; + VarEvalMode emode; 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", * 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 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 = opts.strict ? VARE_WANTRES : VARE_WANTRES | VARE_UNDEFERR; - (void)Var_Subst(line, SCOPE_CMDLINE, eflags, &expanded_line); + emode = opts.strict ? VARE_WANTRES : VARE_UNDEFERR; + (void)Var_Subst(line, SCOPE_CMDLINE, emode, &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); + ParseDependency(expanded_line); free(expanded_line); if (shellcmd != NULL) ParseLine_ShellCommand(shellcmd); } static void ParseLine(char *line) { /* * 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'. */ if (line[0] == '.' && ParseDirective(line)) return; if (line[0] == '\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); + ParseDependencyLine(line); } /* * 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); if (name == NULL) name = "(stdin)"; Parse_SetInput(name, 0, -1, loadedfile_readMore, 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 != 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 = SearchPath_New(); sysIncPath = SearchPath_New(); defSysIncPath = SearchPath_New(); Vector_Init(&includes, sizeof(IFile)); } /* Clean up the parsing module. */ void Parse_End(void) { #ifdef CLEANUP Lst_DoneCall(&targCmds, free); assert(targets == NULL); SearchPath_Free(defSysIncPath); SearchPath_Free(sysIncPath); SearchPath_Free(parseIncPath); assert(includes.len == 0); Vector_Done(&includes); #endif } /* * Return a list containing the single main target to create. * If no such target exists, we Punt with an obnoxious error message. */ void Parse_MainName(GNodeList *mainList) { if (mainNode == NULL) Punt("no target to make."); Lst_Append(mainList, mainNode); if (mainNode->type & OP_DOUBLEDEP) Lst_AppendAll(mainList, &mainNode->cohorts); Global_Append(".TARGETS", mainNode->name); } int Parse_GetFatals(void) { return fatals; } diff --git a/contrib/bmake/str.c b/contrib/bmake/str.c index c486df6d3d84..b4baede4d417 100644 --- a/contrib/bmake/str.c +++ b/contrib/bmake/str.c @@ -1,375 +1,398 @@ -/* $NetBSD: str.c,v 1.81 2021/02/01 22:36:28 rillig Exp $ */ +/* $NetBSD: str.c,v 1.85 2021/05/30 21:16:54 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.81 2021/02/01 22:36:28 rillig Exp $"); +MAKE_RCSID("$NetBSD: str.c,v 1.85 2021/05/30 21:16:54 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. * - * If expand is TRUE, quotes are removed and escape sequences such as \r, \t, + * If expand is true, quotes are removed and escape sequences such as \r, \t, * etc... are expanded. In this case, return NULL on parse errors. * * 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) +SubstringWords +Substring_Words(const char *str, bool expand) { size_t str_len; char *words_buf; size_t words_cap; - char **words; + Substring *words; size_t words_len; char inquote; char *word_start; char *word_end; const char *str_p; /* 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(str_len + 1); words_cap = str_len / 5 > 50 ? str_len / 5 : 50; - words = bmake_malloc((words_cap + 1) * sizeof(char *)); + words = bmake_malloc((words_cap + 1) * sizeof(words[0])); /* * 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 != '\0') { 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 != '\0') 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_cap *= 2; + new_size = (words_cap + 1) * sizeof(words[0]); words = bmake_realloc(words, new_size); } - words[words_len++] = word_start; + words[words_len++] = + Substring_Init(word_start, word_end - 1); word_start = NULL; if (ch == '\n' || ch == '\0') { if (expand && inquote != '\0') { + SubstringWords res; + free(words); free(words_buf); - return (Words){ NULL, 0, NULL }; + + res.words = NULL; + res.len = 0; + res.freeIt = NULL; + return res; } 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--; 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; /* useful for argv */ - return (Words){ words, words_len, words_buf }; + words[words_len] = Substring_Init(NULL, NULL); /* useful for argv */ + + { + SubstringWords result; + + result.words = words; + result.len = words_len; + result.freeIt = words_buf; + return result; + } +} + +Words +Str_Words(const char *str, bool expand) +{ + SubstringWords swords; + Words words; + size_t i; + + swords = Substring_Words(str, expand); + if (swords.words == NULL) { + words.words = NULL; + words.len = 0; + words.freeIt = NULL; + return words; + } + + words.words = bmake_malloc((swords.len + 1) * sizeof(words.words[0])); + words.len = swords.len; + words.freeIt = swords.freeIt; + for (i = 0; i < swords.len + 1; i++) + words.words[i] = UNCONST(swords.words[i].start); + free(swords.words); + return words; } /* * 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. */ -Boolean +bool Str_Match(const char *str, const char *pat) { for (;;) { /* * See if we're at the end of both the pattern and 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 != '*') - return FALSE; + 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') - return TRUE; + return true; while (*str != '\0') { if (Str_Match(str, pat)) - return TRUE; + return true; str++; } - return FALSE; + 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] == '^'; + bool neg = pat[1] == '^'; pat += neg ? 2 : 1; for (;;) { if (*pat == ']' || *pat == '\0') { if (neg) break; - return FALSE; + return false; } /* * XXX: This naive comparison makes the * control flow of the pattern parser * dependent on the actual value of the * string. This is unpredictable. It may be * though that the code only looks wrong but * actually all code paths result in the same * behavior. This needs further tests. */ if (*pat == *str) break; if (pat[1] == '-') { 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') - return FALSE; + return false; while (*pat != ']' && *pat != '\0') pat++; if (*pat == '\0') pat--; goto thisCharOK; } /* * A backslash in the pattern matches the character following * it exactly. */ if (*pat == '\\') { pat++; if (*pat == '\0') - return FALSE; + return false; } if (*pat != *str) - return FALSE; + return false; thisCharOK: pat++; str++; } } diff --git a/contrib/bmake/str.h b/contrib/bmake/str.h new file mode 100644 index 000000000000..ce0bb5ad82bc --- /dev/null +++ b/contrib/bmake/str.h @@ -0,0 +1,366 @@ +/* $NetBSD: str.h,v 1.9 2021/05/30 21:16:54 rillig Exp $ */ + +/* + Copyright (c) 2021 Roland Illig + 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 HOLDER 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. + */ + + +/* + * Memory-efficient string handling. + */ + + +/* A read-only string that may need to be freed after use. */ +typedef struct FStr { + const char *str; + void *freeIt; +} FStr; + +/* A modifiable string that may need to be freed after use. */ +typedef struct MFStr { + char *str; + void *freeIt; +} MFStr; + +/* A read-only range of a character array, NOT null-terminated. */ +typedef struct Substring { + const char *start; + const char *end; +} Substring; + +/* + * Builds a string, only allocating memory if the string is different from the + * expected string. + */ +typedef struct LazyBuf { + char *data; + size_t len; + size_t cap; + const char *expected; + void *freeIt; +} LazyBuf; + +/* The result of splitting a string into words. */ +typedef struct Words { + char **words; + size_t len; + void *freeIt; +} Words; + +/* The result of splitting a string into words. */ +typedef struct SubstringWords { + Substring *words; + size_t len; + void *freeIt; +} SubstringWords; + + +MAKE_INLINE FStr +FStr_Init(const char *str, void *freeIt) +{ + FStr fstr; + fstr.str = str; + fstr.freeIt = freeIt; + return fstr; +} + +/* Return a string that is the sole owner of str. */ +MAKE_INLINE FStr +FStr_InitOwn(char *str) +{ + return FStr_Init(str, str); +} + +/* Return a string that refers to the shared str. */ +MAKE_INLINE FStr +FStr_InitRefer(const char *str) +{ + return FStr_Init(str, NULL); +} + +MAKE_INLINE void +FStr_Done(FStr *fstr) +{ + free(fstr->freeIt); +#ifdef CLEANUP + fstr->str = NULL; + fstr->freeIt = NULL; +#endif +} + + +MAKE_INLINE MFStr +MFStr_Init(char *str, void *freeIt) +{ + MFStr mfstr; + mfstr.str = str; + mfstr.freeIt = freeIt; + return mfstr; +} + +/* Return a string that is the sole owner of str. */ +MAKE_INLINE MFStr +MFStr_InitOwn(char *str) +{ + return MFStr_Init(str, str); +} + +/* Return a string that refers to the shared str. */ +MAKE_INLINE MFStr +MFStr_InitRefer(char *str) +{ + return MFStr_Init(str, NULL); +} + +MAKE_INLINE void +MFStr_Done(MFStr *mfstr) +{ + free(mfstr->freeIt); +#ifdef CLEANUP + mfstr->str = NULL; + mfstr->freeIt = NULL; +#endif +} + + +MAKE_STATIC Substring +Substring_Init(const char *start, const char *end) +{ + Substring sub; + + sub.start = start; + sub.end = end; + return sub; +} + +MAKE_INLINE Substring +Substring_InitStr(const char *str) +{ + return Substring_Init(str, str + strlen(str)); +} + +MAKE_STATIC size_t +Substring_Length(Substring sub) +{ + return (size_t)(sub.end - sub.start); +} + +MAKE_STATIC bool +Substring_IsEmpty(Substring sub) +{ + return sub.start == sub.end; +} + +MAKE_INLINE bool +Substring_Equals(Substring sub, const char *str) +{ + size_t len = strlen(str); + return Substring_Length(sub) == len && + memcmp(sub.start, str, len) == 0; +} + +MAKE_STATIC Substring +Substring_Sub(Substring sub, size_t start, size_t end) +{ + assert(start <= Substring_Length(sub)); + assert(end <= Substring_Length(sub)); + return Substring_Init(sub.start + start, sub.start + end); +} + +MAKE_STATIC bool +Substring_HasPrefix(Substring sub, Substring prefix) +{ + return Substring_Length(sub) >= Substring_Length(prefix) && + memcmp(sub.start, prefix.start, Substring_Length(prefix)) == 0; +} + +MAKE_STATIC bool +Substring_HasSuffix(Substring sub, Substring suffix) +{ + size_t suffixLen = Substring_Length(suffix); + return Substring_Length(sub) >= suffixLen && + memcmp(sub.end - suffixLen, suffix.start, suffixLen) == 0; +} + +/* Returns an independent, null-terminated copy of the substring. */ +MAKE_STATIC FStr +Substring_Str(Substring sub) +{ + if (Substring_IsEmpty(sub)) + return FStr_InitRefer(""); + return FStr_InitOwn(bmake_strsedup(sub.start, sub.end)); +} + +MAKE_STATIC const char * +Substring_SkipFirst(Substring sub, char ch) +{ + const char *p; + + for (p = sub.start; p != sub.end; p++) + if (*p == ch) + return p + 1; + return sub.start; +} + +MAKE_STATIC const char * +Substring_LastIndex(Substring sub, char ch) +{ + const char *p; + + for (p = sub.end; p != sub.start; p--) + if (p[-1] == ch) + return p - 1; + return NULL; +} + +MAKE_STATIC Substring +Substring_Dirname(Substring pathname) +{ + const char *p; + + for (p = pathname.end; p != pathname.start; p--) + if (p[-1] == '/') + return Substring_Init(pathname.start, p - 1); + return Substring_InitStr("."); +} + +MAKE_STATIC Substring +Substring_Basename(Substring pathname) +{ + const char *p; + + for (p = pathname.end; p != pathname.start; p--) + if (p[-1] == '/') + return Substring_Init(p, pathname.end); + return pathname; +} + + +MAKE_STATIC void +LazyBuf_Init(LazyBuf *buf, const char *expected) +{ + buf->data = NULL; + buf->len = 0; + buf->cap = 0; + buf->expected = expected; + buf->freeIt = NULL; +} + +MAKE_INLINE void +LazyBuf_Done(LazyBuf *buf) +{ + free(buf->freeIt); +} + +MAKE_STATIC void +LazyBuf_Add(LazyBuf *buf, char ch) +{ + + if (buf->data != NULL) { + if (buf->len == buf->cap) { + buf->cap *= 2; + buf->data = bmake_realloc(buf->data, buf->cap); + } + buf->data[buf->len++] = ch; + + } else if (ch == buf->expected[buf->len]) { + buf->len++; + return; + + } else { + buf->cap = buf->len + 16; + buf->data = bmake_malloc(buf->cap); + memcpy(buf->data, buf->expected, buf->len); + buf->data[buf->len++] = ch; + } +} + +MAKE_STATIC void +LazyBuf_AddStr(LazyBuf *buf, const char *str) +{ + const char *p; + + for (p = str; *p != '\0'; p++) + LazyBuf_Add(buf, *p); +} + +MAKE_STATIC void +LazyBuf_AddBytesBetween(LazyBuf *buf, const char *start, const char *end) +{ + const char *p; + + for (p = start; p != end; p++) + LazyBuf_Add(buf, *p); +} + +MAKE_INLINE void +LazyBuf_AddSubstring(LazyBuf *buf, Substring sub) +{ + LazyBuf_AddBytesBetween(buf, sub.start, sub.end); +} + +MAKE_STATIC Substring +LazyBuf_Get(const LazyBuf *buf) +{ + const char *start = buf->data != NULL ? buf->data : buf->expected; + return Substring_Init(start, start + buf->len); +} + +MAKE_STATIC FStr +LazyBuf_DoneGet(LazyBuf *buf) +{ + if (buf->data != NULL) { + LazyBuf_Add(buf, '\0'); + return FStr_InitOwn(buf->data); + } + return Substring_Str(LazyBuf_Get(buf)); +} + + +Words Str_Words(const char *, bool); + +MAKE_INLINE void +Words_Free(Words w) +{ + free(w.words); + free(w.freeIt); +} + + +SubstringWords Substring_Words(const char *, bool); + +MAKE_INLINE void +SubstringWords_Free(SubstringWords w) +{ + free(w.words); + free(w.freeIt); +} + + +char *str_concat2(const char *, const char *); +char *str_concat3(const char *, const char *, const char *); + +bool Str_Match(const char *, const char *); diff --git a/contrib/bmake/suff.c b/contrib/bmake/suff.c index 91e8bc613eb8..b2c926a2b8bc 100644 --- a/contrib/bmake/suff.c +++ b/contrib/bmake/suff.c @@ -1,2178 +1,2179 @@ -/* $NetBSD: suff.c,v 1.345 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: suff.c,v 1.350 2021/04/04 10:05:08 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. */ /* * Maintain suffix lists and find implicit dependents using suffix * transformation rules such as ".c.o". * * Interface: * Suff_Init Initialize the module. * * Suff_End Clean up the module. * - * Suff_DoPaths Extend the search path of each suffix to include the + * Suff_ExtendPaths + * Extend the search path of each suffix to include the * default search path. * * Suff_ClearSuffixes * Clear out all the suffixes and transformations. * * Suff_IsTransform * 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. * * 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.345 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: suff.c,v 1.350 2021/04/04 10:05:08 rillig Exp $"); typedef List SuffixList; typedef ListNode SuffixListNode; typedef List CandidateList; typedef ListNode CandidateListNode; /* The defined suffixes, such as '.c', '.o', '.l'. */ static SuffixList sufflist = LST_INIT; #ifdef CLEANUP /* The suffixes to be cleaned up at the end. */ static SuffixList suffClean = LST_INIT; #endif /* * The transformation rules, such as '.c.o' to transform '.c' into '.o', * or simply '.c' to transform 'file.c' into 'file'. */ static GNodeList transforms = LST_INIT; /* * Counter for assigning suffix numbers. * TODO: What are these suffix numbers used for? */ static int sNum = 0; typedef enum SuffixFlags { SUFF_NONE = 0, /* * This suffix marks include files. Their search path ends up in the * undocumented special variable '.INCLUDES'. */ SUFF_INCLUDE = 1 << 0, /* * This suffix marks library files. Their search path ends up in the * undocumented special variable '.LIBS'. */ SUFF_LIBRARY = 1 << 1, /* * The empty suffix. * * XXX: What is the difference between the empty suffix and the null * suffix? * * XXX: Why is SUFF_NULL needed at all? Wouldn't nameLen == 0 mean * the same? */ SUFF_NULL = 1 << 2 } SuffixFlags; ENUM_FLAGS_RTTI_3(SuffixFlags, SUFF_INCLUDE, SUFF_LIBRARY, SUFF_NULL); typedef List SuffixListList; /* * A suffix such as ".c" or ".o" that is used in suffix transformation rules * such as ".c.o:". */ typedef struct Suffix { /* The suffix itself, such as ".c" */ char *name; /* Length of the name, to avoid strlen calls */ size_t nameLen; /* Type of suffix */ SuffixFlags 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 */ SuffixList parents; /* Suffixes we have a transformation from */ SuffixList 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. */ SuffixListList ref; } Suffix; /* * A candidate when searching for implied sources. * * For example, when "src.o" is to be made, a typical candidate is "src.c" * via the transformation rule ".c.o". If that doesn't exist, maybe there is * another transformation rule ".pas.c" that would make "src.pas" an indirect * candidate as well. The first such chain that leads to an existing file or * node is finally chosen to be made. */ typedef struct Candidate { /* The file or node to look for. */ char *file; /* The prefix from which file was formed. * Its memory is shared among all candidates. */ char *prefix; /* The suffix on the file. */ Suffix *suff; /* The candidate that can be made from this, * or NULL for the top-level candidate. */ struct Candidate *parent; /* The node describing the file. */ GNode *node; /* Count of existing children, only used for memory management, so we * don't free this candidate too early or too late. */ int numChildren; #ifdef DEBUG_SRC CandidateList childrenList; #endif } Candidate; typedef struct CandidateSearcher { CandidateList list; /* * TODO: Add HashSet for seen entries, to avoid endless loops such as * in suff-transform-endless.mk. */ } CandidateSearcher; /* TODO: Document the difference between nullSuff and emptySuff. */ /* The NULL suffix for this run */ static Suffix *nullSuff; /* The empty suffix required for POSIX single-suffix transformation rules */ static Suffix *emptySuff; static Suffix * Suffix_Ref(Suffix *suff) { suff->refCount++; return suff; } /* Change the value of a Suffix variable, adjusting the reference counts. */ static void Suffix_Reassign(Suffix **var, Suffix *suff) { if (*var != NULL) (*var)->refCount--; *var = suff; suff->refCount++; } /* Set a Suffix variable to NULL, adjusting the reference count. */ static void Suffix_Unassign(Suffix **var) { if (*var != NULL) (*var)->refCount--; *var = NULL; } /* * 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 * StrTrimPrefix(const char *pref, const char *str) { while (*str != '\0' && *pref == *str) { pref++; str++; } return *pref != '\0' ? NULL : str; } /* * See if suff is a suffix of str, and if so, return the pointer to the suffix * in str, which at the same time marks the end of the prefix. */ static const char * StrTrimSuffix(const char *str, size_t strLen, const char *suff, size_t suffLen) { const char *suffInStr; size_t i; if (strLen < suffLen) return NULL; suffInStr = str + strLen - suffLen; for (i = 0; i < suffLen; i++) if (suff[i] != suffInStr[i]) return NULL; return suffInStr; } /* * See if suff is a suffix of name, and if so, return the end of the prefix * in name. */ static const char * Suffix_TrimSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd) { return StrTrimSuffix(nameEnd - nameLen, nameLen, suff->name, suff->nameLen); } -static Boolean +static bool Suffix_IsSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd) { return Suffix_TrimSuffix(suff, nameLen, nameEnd) != NULL; } static Suffix * FindSuffixByNameLen(const char *name, size_t nameLen) { SuffixListNode *ln; for (ln = sufflist.first; ln != NULL; ln = ln->next) { Suffix *suff = ln->datum; if (suff->nameLen == nameLen && memcmp(suff->name, name, nameLen) == 0) return suff; } return NULL; } static Suffix * FindSuffixByName(const char *name) { return FindSuffixByNameLen(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; } static void SuffixList_Unref(SuffixList *list, Suffix *suff) { SuffixListNode *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 Suffix_Free(Suffix *suff) { if (suff == nullSuff) nullSuff = NULL; if (suff == emptySuff) emptySuff = NULL; #if 0 /* We don't delete suffixes in order, so we cannot use this */ if (suff->refCount != 0) Punt("Internal error deleting suffix `%s' with refcount = %d", suff->name, suff->refCount); #endif Lst_Done(&suff->ref); Lst_Done(&suff->children); Lst_Done(&suff->parents); SearchPath_Free(suff->searchPath); free(suff->name); free(suff); } static void SuffFree(void *p) { Suffix_Free(p); } /* Remove the suffix from the list, and free if it is otherwise unused. */ static void SuffixList_Remove(SuffixList *list, Suffix *suff) { SuffixList_Unref(list, suff); if (suff->refCount == 0) { /* XXX: can lead to suff->refCount == -1 */ SuffixList_Unref(&sufflist, suff); DEBUG1(SUFF, "Removing suffix \"%s\"\n", suff->name); SuffFree(suff); } } /* * Insert the suffix into the list, keeping the list ordered by suffix * number. */ static void SuffixList_Insert(SuffixList *list, Suffix *suff) { SuffixListNode *ln; Suffix *listSuff = NULL; for (ln = list->first; ln != NULL; ln = ln->next) { listSuff = ln->datum; if (listSuff->sNum >= suff->sNum) break; } if (ln == NULL) { DEBUG2(SUFF, "inserting \"%s\" (%d) at end of list\n", suff->name, suff->sNum); Lst_Append(list, Suffix_Ref(suff)); Lst_Append(&suff->ref, list); } else if (listSuff->sNum != suff->sNum) { DEBUG4(SUFF, "inserting \"%s\" (%d) before \"%s\" (%d)\n", suff->name, suff->sNum, listSuff->name, listSuff->sNum); Lst_InsertBefore(list, ln, Suffix_Ref(suff)); Lst_Append(&suff->ref, list); } else { DEBUG2(SUFF, "\"%s\" (%d) is already there\n", suff->name, suff->sNum); } } static void Relate(Suffix *srcSuff, Suffix *targSuff) { SuffixList_Insert(&targSuff->children, srcSuff); SuffixList_Insert(&srcSuff->parents, targSuff); } static Suffix * Suffix_New(const char *name) { Suffix *suff = bmake_malloc(sizeof *suff); suff->name = bmake_strdup(name); suff->nameLen = strlen(suff->name); suff->searchPath = SearchPath_New(); Lst_Init(&suff->children); Lst_Init(&suff->parents); Lst_Init(&suff->ref); suff->sNum = sNum++; suff->flags = SUFF_NONE; suff->refCount = 1; /* XXX: why 1? It's not assigned anywhere yet. */ return suff; } /* * 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 DEBUG0(SUFF, "Clearing all suffixes\n"); Lst_Init(&sufflist); sNum = 0; if (nullSuff != NULL) SuffFree(nullSuff); emptySuff = nullSuff = Suffix_New(""); SearchPath_AddAll(nullSuff->searchPath, &dirSearchPath); nullSuff->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. + * Return true if the string is a valid transformation. */ -static Boolean +static bool ParseTransform(const char *str, Suffix **out_src, Suffix **out_targ) { SuffixListNode *ln; Suffix *single = 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) { Suffix *src = ln->datum; if (StrTrimPrefix(src->name, str) == NULL) continue; if (str[src->nameLen] == '\0') { single = src; } else { Suffix *targ = FindSuffixByName(str + src->nameLen); if (targ != NULL) { *out_src = src; *out_targ = targ; - return TRUE; + return true; } } } if (single != NULL) { /* * There was a suffix that encompassed the entire string, so we * assume it was a transformation to the null suffix (thank you * POSIX; search for "single suffix" or "single-suffix"). * * We still prefer to find a double rule over a singleton, * hence we leave this check until the end. * * XXX: Use emptySuff over nullSuff? */ *out_src = single; *out_targ = nullSuff; - return TRUE; + return true; } - return FALSE; + return false; } /* - * Return TRUE if the given string is a transformation rule, that is, a + * Return true if the given string is a transformation rule, that is, a * concatenation of two known suffixes such as ".c.o" or a single suffix * such as ".o". */ -Boolean +bool Suff_IsTransform(const char *str) { Suffix *src, *targ; return ParseTransform(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) { Suffix *srcSuff; Suffix *targSuff; 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 = 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_Done(&gn->commands); Lst_Init(&gn->commands); Lst_Done(&gn->children); Lst_Init(&gn->children); } gn->type = OP_TRANSFORM; { /* TODO: Avoid the redundant parsing here. */ - Boolean ok = ParseTransform(name, &srcSuff, &targSuff); + bool ok = ParseTransform(name, &srcSuff, &targSuff); assert(ok); (void)ok; } /* Link the two together in the proper relationship and order. */ DEBUG2(SUFF, "defining transformation from `%s' to `%s'\n", srcSuff->name, targSuff->name); Relate(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) { Suffix *srcSuff, *targSuff; SuffixList *srcSuffParents; if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(&gn->cohorts)) gn = gn->cohorts.last->datum; if (!(gn->type & OP_TRANSFORM)) return; if (!Lst_IsEmpty(&gn->commands) || !Lst_IsEmpty(&gn->children)) { DEBUG1(SUFF, "transformation %s complete\n", gn->name); return; } /* * SuffParseTransform() may fail for special rules which are not * actual transformation rules. (e.g. .DEFAULT) */ if (!ParseTransform(gn->name, &srcSuff, &targSuff)) return; DEBUG2(SUFF, "deleting incomplete transformation from `%s' to `%s'\n", srcSuff->name, targSuff->name); /* * Remember the parents since srcSuff could be deleted in * SuffixList_Remove. */ srcSuffParents = &srcSuff->parents; SuffixList_Remove(&targSuff->children, srcSuff); SuffixList_Remove(srcSuffParents, targSuff); } /* * 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 RebuildGraph(GNode *transform, Suffix *suff) { const char *name = transform->name; size_t nameLen = strlen(name); const char *toName; /* See if it is a transformation from this suffix to another suffix. */ toName = StrTrimPrefix(suff->name, name); if (toName != NULL) { Suffix *to = FindSuffixByName(toName); if (to != NULL) { Relate(suff, to); return; } } /* See if it is a transformation from another suffix to this suffix. */ toName = Suffix_TrimSuffix(suff, nameLen, name + nameLen); if (toName != NULL) { Suffix *from = FindSuffixByNameLen(name, (size_t)(toName - name)); if (from != NULL) Relate(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. + * true iff a new main target has been selected. */ -static Boolean +static bool UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, - Boolean *inout_removedMain) + bool *inout_removedMain) { Suffix *srcSuff, *targSuff; char *ptr; if (*inout_main == NULL && *inout_removedMain && !(target->type & OP_NOTARGET)) { DEBUG1(MAKE, "Setting main node to \"%s\"\n", target->name); *inout_main = target; Targ_SetMain(target); /* - * XXX: Why could it be a good idea to return TRUE here? + * XXX: Why could it be a good idea to return true here? * The main task of this function is to turn ordinary nodes * into transformations, no matter whether or not a new .MAIN * node has been found. */ /* - * XXX: Even when changing this to FALSE, none of the existing + * XXX: Even when changing this to false, none of the existing * unit tests fails. */ - return TRUE; + return true; } if (target->type == OP_TRANSFORM) - return FALSE; + return false; /* * XXX: What about a transformation ".cpp.c"? If ".c" is added as * a new suffix, it seems wrong that this transformation would be * skipped just because ".c" happens to be a prefix of ".cpp". */ ptr = strstr(target->name, suff->name); if (ptr == NULL) - return FALSE; + return false; /* * XXX: In suff-rebuild.mk, in the line '.SUFFIXES: .c .b .a', this * condition prevents the rule '.b.c' from being added again during * Suff_AddSuffix(".b"). * * XXX: Removing this paragraph makes suff-add-later.mk use massive * amounts of memory. */ if (ptr == target->name) - return FALSE; + return false; if (ParseTransform(target->name, &srcSuff, &targSuff)) { if (*inout_main == target) { DEBUG1(MAKE, "Setting main node from \"%s\" back to null\n", target->name); - *inout_removedMain = TRUE; + *inout_removedMain = true; *inout_main = NULL; Targ_SetMain(NULL); } Lst_Done(&target->children); Lst_Init(&target->children); target->type = OP_TRANSFORM; /* * Link the two together in the proper relationship and order. */ DEBUG2(SUFF, "defining transformation from `%s' to `%s'\n", srcSuff->name, targSuff->name); Relate(srcSuff, targSuff); } - return FALSE; + 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, Suffix *suff) { - Boolean removedMain = FALSE; + bool removedMain = false; GNodeListNode *ln; for (ln = Targ_List()->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; if (UpdateTarget(gn, inout_main, suff, &removedMain)) 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 (XXX: this sounds completely wrong) and * a Suffix 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; Suffix *suff = FindSuffixByName(name); if (suff != NULL) return; suff = Suffix_New(name); Lst_Append(&sufflist, suff); DEBUG1(SUFF, "Adding suffix \"%s\"\n", suff->name); UpdateTargets(inout_main, suff); /* * 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) RebuildGraph(ln->datum, suff); } /* Return the search path for the given suffix, or NULL. */ SearchPath * Suff_GetPath(const char *sname) { Suffix *suff = FindSuffixByName(sname); return suff != NULL ? suff->searchPath : NULL; } /* * Extend the search paths for all suffixes to include the default search * path (dirSearchPath). * * 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) +Suff_ExtendPaths(void) { SuffixListNode *ln; char *flags; SearchPath *includesPath = SearchPath_New(); SearchPath *libsPath = SearchPath_New(); for (ln = sufflist.first; ln != NULL; ln = ln->next) { Suffix *suff = ln->datum; if (!Lst_IsEmpty(&suff->searchPath->dirs)) { #ifdef INCLUDES if (suff->flags & SUFF_INCLUDE) SearchPath_AddAll(includesPath, suff->searchPath); #endif #ifdef LIBRARIES if (suff->flags & SUFF_LIBRARY) SearchPath_AddAll(libsPath, suff->searchPath); #endif SearchPath_AddAll(suff->searchPath, &dirSearchPath); } else { SearchPath_Free(suff->searchPath); suff->searchPath = Dir_CopyDirSearchPath(); } } flags = SearchPath_ToFlags(includesPath, "-I"); Global_Set(".INCLUDES", flags); free(flags); flags = SearchPath_ToFlags(libsPath, "-L"); Global_Set(".LIBS", flags); free(flags); SearchPath_Free(includesPath); SearchPath_Free(libsPath); } /* * Add the given suffix as a type of file which gets included. * Called when a '.INCLUDES: .h' line is parsed. * To have an effect, the suffix must already exist. * This affects the magic variable '.INCLUDES'. */ void Suff_AddInclude(const char *suffName) { Suffix *suff = FindSuffixByName(suffName); if (suff != NULL) suff->flags |= SUFF_INCLUDE; } /* * Add the given suffix as a type of file which is a library. * Called when a '.LIBS: .a' line is parsed. * To have an effect, the suffix must already exist. * This affects the magic variable '.LIBS'. */ void Suff_AddLib(const char *suffName) { Suffix *suff = FindSuffixByName(suffName); if (suff != NULL) suff->flags |= SUFF_LIBRARY; } /********** Implicit Source Search Functions *********/ static void CandidateSearcher_Init(CandidateSearcher *cs) { Lst_Init(&cs->list); } static void CandidateSearcher_Done(CandidateSearcher *cs) { Lst_Done(&cs->list); } static void CandidateSearcher_Add(CandidateSearcher *cs, Candidate *cand) { /* TODO: filter duplicates */ Lst_Append(&cs->list, cand); } static void CandidateSearcher_AddIfNew(CandidateSearcher *cs, Candidate *cand) { /* TODO: filter duplicates */ if (Lst_FindDatum(&cs->list, cand) == NULL) Lst_Append(&cs->list, cand); } static void CandidateSearcher_MoveAll(CandidateSearcher *cs, CandidateList *list) { /* TODO: filter duplicates */ Lst_MoveAll(&cs->list, list); } #ifdef DEBUG_SRC static void CandidateList_PrintAddrs(CandidateList *list) { CandidateListNode *ln; for (ln = list->first; ln != NULL; ln = ln->next) { Candidate *cand = ln->datum; debug_printf(" %p:%s", cand, cand->file); } debug_printf("\n"); } #endif static Candidate * Candidate_New(char *name, char *prefix, Suffix *suff, Candidate *parent, GNode *gn) { Candidate *cand = bmake_malloc(sizeof *cand); cand->file = name; cand->prefix = prefix; cand->suff = Suffix_Ref(suff); cand->parent = parent; cand->node = gn; cand->numChildren = 0; #ifdef DEBUG_SRC Lst_Init(&cand->childrenList); #endif return cand; } /* Add a new candidate to the list. */ /*ARGSUSED*/ static void CandidateList_Add(CandidateList *list, char *srcName, Candidate *targ, Suffix *suff, const char *debug_tag) { Candidate *cand = Candidate_New(srcName, targ->prefix, suff, targ, NULL); targ->numChildren++; Lst_Append(list, cand); #ifdef DEBUG_SRC Lst_Append(&targ->childrenList, cand); debug_printf("%s add suff %p:%s candidate %p:%s to list %p:", debug_tag, targ, targ->file, cand, cand->file, list); CandidateList_PrintAddrs(list); #endif } /* * Add all candidates to the list that can be formed by applying a suffix to * the candidate. */ static void CandidateList_AddCandidatesFor(CandidateList *list, Candidate *cand) { SuffixListNode *ln; for (ln = cand->suff->children.first; ln != NULL; ln = ln->next) { Suffix *suff = ln->datum; if ((suff->flags & SUFF_NULL) && suff->name[0] != '\0') { /* * If the suffix has been marked as the NULL suffix, * also create a candidate for a file with no suffix * attached. */ CandidateList_Add(list, bmake_strdup(cand->prefix), cand, suff, "1"); } CandidateList_Add(list, str_concat2(cand->prefix, suff->name), cand, suff, "2"); } } /* * Free the first candidate in the list that is not referenced anymore. * Return whether a candidate was removed. */ -static Boolean +static bool RemoveCandidate(CandidateList *srcs) { CandidateListNode *ln; #ifdef DEBUG_SRC debug_printf("cleaning list %p:", srcs); CandidateList_PrintAddrs(srcs); #endif for (ln = srcs->first; ln != NULL; ln = ln->next) { Candidate *src = ln->datum; if (src->numChildren == 0) { if (src->parent == NULL) free(src->prefix); else { #ifdef DEBUG_SRC /* XXX: Lst_RemoveDatum */ CandidateListNode *ln2; ln2 = Lst_FindDatum(&src->parent->childrenList, src); if (ln2 != NULL) Lst_Remove(&src->parent->childrenList, ln2); #endif src->parent->numChildren--; } #ifdef DEBUG_SRC debug_printf("free: list %p src %p:%s children %d\n", srcs, src, src->file, src->numChildren); Lst_Done(&src->childrenList); #endif Lst_Remove(srcs, ln); free(src->file); free(src); - return TRUE; + return true; } #ifdef DEBUG_SRC else { debug_printf("keep: list %p src %p:%s children %d:", srcs, src, src->file, src->numChildren); CandidateList_PrintAddrs(&src->childrenList); } #endif } - return FALSE; + return false; } /* Find the first existing file/target in srcs. */ static Candidate * FindThem(CandidateList *srcs, CandidateSearcher *cs) { HashSet seen; HashSet_Init(&seen); while (!Lst_IsEmpty(srcs)) { Candidate *src = Lst_Dequeue(srcs); #ifdef DEBUG_SRC debug_printf("remove from list %p src %p:%s\n", srcs, src, src->file); #endif DEBUG1(SUFF, "\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) { found: HashSet_Done(&seen); DEBUG0(SUFF, "got it\n"); return src; } { char *file = Dir_FindFile(src->file, src->suff->searchPath); if (file != NULL) { free(file); goto found; } } DEBUG0(SUFF, "not there\n"); if (HashSet_Add(&seen, src->file)) CandidateList_AddCandidatesFor(srcs, src); else { DEBUG1(SUFF, "FindThem: skipping duplicate \"%s\"\n", src->file); } CandidateSearcher_Add(cs, src); } HashSet_Done(&seen); return NULL; } /* * See if any of the children of the candidate's GNode is one from which the * target can be transformed. If there is one, a candidate is put together * for it and returned. */ static Candidate * FindCmds(Candidate *targ, CandidateSearcher *cs) { GNodeListNode *gln; GNode *tgn; /* Target GNode */ GNode *sgn; /* Source GNode */ size_t prefLen; /* The length of the defined prefix */ Suffix *suff; /* Suffix of the matching candidate */ Candidate *ret; /* Return value */ tgn = targ->node; prefLen = strlen(targ->prefix); for (gln = tgn->children.first; gln != NULL; gln = gln->next) { const char *base; 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; } base = str_basename(sgn->name); if (strncmp(base, targ->prefix, prefLen) != 0) continue; /* The node matches the prefix, see if it has a known suffix. */ suff = FindSuffixByName(base + 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. */ if (Lst_FindDatum(&suff->parents, targ->suff) != NULL) break; } if (gln == NULL) return NULL; ret = Candidate_New(bmake_strdup(sgn->name), targ->prefix, suff, targ, sgn); targ->numChildren++; #ifdef DEBUG_SRC debug_printf("3 add targ %p:%s ret %p:%s\n", targ, targ->file, ret, ret->file); Lst_Append(&targ->childrenList, ret); #endif CandidateSearcher_Add(cs, ret); DEBUG1(SUFF, "\tusing existing source %s\n", sgn->name); return ret; } static void ExpandWildcards(GNodeListNode *cln, GNode *pgn) { GNode *cgn = cln->datum; StringList expansions; if (!Dir_HasWildcards(cgn->name)) return; /* * Expand the word along the chosen path */ Lst_Init(&expansions); SearchPath_Expand(Suff_FindPath(cgn), cgn->name, &expansions); while (!Lst_IsEmpty(&expansions)) { GNode *gn; /* * Fetch next expansion off the list and find its GNode */ char *cp = Lst_Dequeue(&expansions); DEBUG1(SUFF, "%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_Done(&expansions); DEBUG0(SUFF, "\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)); } /* * 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 Str_Words because it doesn't understand about - * variable specifications with spaces in them. + * variable expressions with spaces in them. */ static void ExpandChildrenRegular(char *cp, GNode *pgn, GNodeList *members) { char *start; pp_skip_hspace(&cp); start = cp; while (*cp != '\0') { if (*cp == ' ' || *cp == '\t') { GNode *gn; /* * White-space -- terminate element, find the node, * add it, skip any further spaces. */ *cp++ = '\0'; gn = Targ_GetNode(start); Lst_Append(members, gn); pp_skip_hspace(&cp); /* Continue at the next non-space. */ start = cp; } else if (*cp == '$') { /* Skip over the variable expression. */ const char *nested_p = cp; FStr junk; - (void)Var_Parse(&nested_p, pgn, VARE_NONE, &junk); + (void)Var_Parse(&nested_p, pgn, VARE_PARSE_ONLY, &junk); /* TODO: handle errors */ if (junk.str == var_Error) { Parse_Error(PARSE_FATAL, "Malformed variable expression at \"%s\"", cp); cp++; } else { cp += nested_p - cp; } FStr_Done(&junk); } 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 */ GNode *gn = Targ_GetNode(start); Lst_Append(members, gn); } } /* * 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 ExpandChildren(GNodeListNode *cln, GNode *pgn) { GNode *cgn = cln->datum; 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) { ExpandWildcards(cln, pgn); return; } DEBUG1(SUFF, "Expanding \"%s\"...", cgn->name); - (void)Var_Subst(cgn->name, pgn, VARE_WANTRES | VARE_UNDEFERR, &cp); + (void)Var_Subst(cgn->name, pgn, VARE_UNDEFERR, &cp); /* TODO: handle errors */ { GNodeList members = LST_INIT; if (cgn->type & OP_ARCHV) { /* * Node was an 'archive(member)' target, so * call on the Arch module to find the nodes for us, * expanding variables in the parent's scope. */ char *p = cp; (void)Arch_ParseArchive(&p, &members, pgn); } else { ExpandChildrenRegular(cp, pgn, &members); } /* * Add all elements of the members list to the parent node. */ while (!Lst_IsEmpty(&members)) { GNode *gn = Lst_Dequeue(&members); DEBUG1(SUFF, "%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 */ ExpandWildcards(cln->prev, pgn); } Lst_Done(&members); free(cp); } DEBUG0(SUFF, "\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 ExpandAllChildren(GNode *gn) { GNodeListNode *ln, *nln; for (ln = gn->children.first; ln != NULL; ln = nln) { nln = ln->next; ExpandChildren(ln, gn); } } /* * Find a path along which to expand the node. * * 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) { Suffix *suff = gn->suffix; if (suff == NULL) { char *name = gn->name; size_t nameLen = strlen(gn->name); SuffixListNode *ln; for (ln = sufflist.first; ln != NULL; ln = ln->next) if (Suffix_IsSuffix(ln->datum, nameLen, name + nameLen)) break; DEBUG1(SUFF, "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) { DEBUG1(SUFF, "suffix is \"%s\"...\n", suff->name); return suff->searchPath; } else { DEBUG0(SUFF, "\n"); return &dirSearchPath; /* Use default search path */ } } /* * Apply a transformation rule, given the source and target nodes and * suffixes. * * 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. + * true if successful, false if not. */ -static Boolean +static bool ApplyTransform(GNode *tgn, GNode *sgn, Suffix *tsuff, Suffix *ssuff) { GNodeListNode *ln; char *tname; /* Name of transformation rule */ GNode *gn; /* Node for the transformation rule */ /* Form the proper links between the target and source. */ Lst_Append(&tgn->children, sgn); Lst_Append(&sgn->parents, tgn); tgn->unmade++; /* Locate the transformation rule itself. */ tname = str_concat2(ssuff->name, tsuff->name); gn = FindTransformByName(tname); free(tname); /* This can happen when linking an OP_MEMBER and OP_ARCHV node. */ if (gn == NULL) - return FALSE; + return false; DEBUG3(SUFF, "\tapplying %s -> %s to \"%s\"\n", ssuff->name, tsuff->name, tgn->name); /* Record last child; Make_HandleUse may add child nodes. */ ln = tgn->children.last; /* Apply the rule. */ Make_HandleUse(gn, tgn); /* Deal with wildcards and variables in any acquired sources. */ ln = ln != NULL ? ln->next : NULL; while (ln != NULL) { GNodeListNode *nln = ln->next; ExpandChildren(ln, tgn); ln = nln; } /* * 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); - return TRUE; + return true; } /* * 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. */ static void ExpandMember(GNode *gn, const char *eoarch, GNode *mem, Suffix *memSuff) { GNodeListNode *ln; size_t nameLen = (size_t)(eoarch - gn->name); /* Use first matching suffix... */ for (ln = memSuff->parents.first; ln != NULL; ln = ln->next) if (Suffix_IsSuffix(ln->datum, nameLen, eoarch)) break; if (ln != NULL) { /* Got one -- apply it */ Suffix *suff = ln->datum; if (!ApplyTransform(gn, mem, suff, memSuff)) { DEBUG2(SUFF, "\tNo transformation from %s -> %s\n", memSuff->name, suff->name); } } } static void FindDeps(GNode *, CandidateSearcher *); /* * Locate dependencies for an OP_ARCHV node. * * Input: * gn Node for which to locate dependencies * * Side Effects: * Same as Suff_FindDeps */ static void FindDepsArchive(GNode *gn, CandidateSearcher *cs) { char *eoarch; /* End of archive portion */ char *eoname; /* End of member portion */ GNode *mem; /* Node for member */ Suffix *memSuff; const 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); FindDeps(mem, cs); /* 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(gn, PREFIX, GNode_VarPrefix(mem)); Var_Set(gn, TARGET, GNode_VarTarget(mem)); memSuff = mem->suffix; if (memSuff == NULL) { /* Didn't know what it was. */ DEBUG0(SUFF, "using null suffix\n"); memSuff = nullSuff; } /* Set the other two local variables required for this target. */ Var_Set(gn, MEMBER, name); Var_Set(gn, ARCHIVE, gn->name); /* Set $@ for compatibility with other makes. */ Var_Set(gn, TARGET, gn->name); /* * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ ExpandAllChildren(gn); if (memSuff != NULL) ExpandMember(gn, eoarch, mem, memSuff); /* * Replace the opening and closing parens now we've no need of the * separate pieces. */ *eoarch = '('; *eoname = ')'; /* * Pretend gn appeared to the left of a dependency operator so the * user needn't provide a transformation from the member to the * archive. */ if (!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; } /* * 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). */ static void FindDepsLib(GNode *gn) { Suffix *suff = FindSuffixByName(LIBSUFF); if (suff != NULL) { Suffix_Reassign(&gn->suffix, suff); Arch_FindLib(gn, suff->searchPath); } else { Suffix_Unassign(&gn->suffix); Var_Set(gn, TARGET, gn->name); } /* * 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(gn, PREFIX, ""); } static void FindDepsRegularKnown(const char *name, size_t nameLen, GNode *gn, CandidateList *srcs, CandidateList *targs) { SuffixListNode *ln; Candidate *targ; char *pref; for (ln = sufflist.first; ln != NULL; ln = ln->next) { Suffix *suff = ln->datum; if (!Suffix_IsSuffix(suff, nameLen, name + nameLen)) continue; pref = bmake_strldup(name, (size_t)(nameLen - suff->nameLen)); targ = Candidate_New(bmake_strdup(gn->name), pref, suff, NULL, gn); CandidateList_AddCandidatesFor(srcs, targ); /* Record the target so we can nuke it. */ Lst_Append(targs, targ); } } static void FindDepsRegularUnknown(GNode *gn, const char *sopref, CandidateList *srcs, CandidateList *targs) { Candidate *targ; if (!Lst_IsEmpty(targs) || nullSuff == NULL) return; DEBUG1(SUFF, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name); targ = Candidate_New(bmake_strdup(gn->name), bmake_strdup(sopref), nullSuff, NULL, gn); /* * 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)) CandidateList_AddCandidatesFor(srcs, targ); else { DEBUG0(SUFF, "not "); } DEBUG0(SUFF, "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 FindDepsRegularPath(GNode *gn, Candidate *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(gn, TARGET, gn->path); 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; Suffix_Reassign(&gn->suffix, targ->suff); savec = gn->path[savep]; gn->path[savep] = '\0'; Var_Set(gn, PREFIX, str_basename(gn->path)); gn->path[savep] = savec; } else { /* * The .PREFIX gets the full path if the target has no * known suffix. */ Suffix_Unassign(&gn->suffix); Var_Set(gn, PREFIX, str_basename(gn->path)); } } /* * Locate implicit dependencies for regular targets. * * Input: * gn Node for which to find sources * * Side Effects: * Same as Suff_FindDeps */ static void FindDepsRegular(GNode *gn, CandidateSearcher *cs) { /* List of sources at which to look */ CandidateList srcs = LST_INIT; /* * List of targets to which things can be transformed. * They all have the same file, but different suff and prefix fields. */ CandidateList targs = LST_INIT; Candidate *bottom; /* Start of found transformation path */ Candidate *src; Candidate *targ; const char *name = gn->name; size_t nameLen = strlen(name); #ifdef DEBUG_SRC DEBUG1(SUFF, "FindDepsRegular \"%s\"\n", gn->name); #endif /* * 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)) { FindDepsRegularKnown(name, nameLen, gn, &srcs, &targs); /* Handle target of unknown suffix... */ FindDepsRegularUnknown(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 = FindThem(&srcs, cs); if (bottom == NULL) { /* * No known transformations -- use the first suffix * found for setting the local variables. */ if (targs.first != NULL) targ = targs.first->datum; 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(gn, TARGET, GNode_Path(gn)); Var_Set(gn, PREFIX, targ != NULL ? targ->prefix : gn->name); /* * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ { GNodeListNode *ln, *nln; for (ln = gn->children.first; ln != NULL; ln = nln) { nln = ln->next; ExpandChildren(ln, gn); } } if (targ == NULL) { DEBUG1(SUFF, "\tNo valid suffix on %s\n", gn->name); sfnd_abort: FindDepsRegularPath(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) gn->type |= OP_LIB; /* * Check for overriding transformation rule implied by sources */ if (!Lst_IsEmpty(&gn->children)) { src = FindCmds(targ, cs); if (src != NULL) { /* * Free up all the candidates in the transformation * path, up to but not including the parent node. */ while (bottom != NULL && bottom->parent != NULL) { CandidateSearcher_AddIfNew(cs, 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 candidates 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 with the commands of the final * transformation rule. */ if (bottom->node == NULL) bottom->node = Targ_GetNode(bottom->file); for (src = bottom; src->parent != NULL; src = src->parent) { targ = src->parent; Suffix_Reassign(&src->node->suffix, src->suff); if (targ->node == NULL) targ->node = Targ_GetNode(targ->file); ApplyTransform(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 variables. */ targ->node->type |= OP_DEPS_FOUND; Var_Set(targ->node, PREFIX, targ->prefix); Var_Set(targ->node, TARGET, targ->node->name); } } Suffix_Reassign(&gn->suffix, src->suff); /* * Nuke the transformation path and the candidates left over in the * two lists. */ sfnd_return: if (bottom != NULL) CandidateSearcher_AddIfNew(cs, bottom); while (RemoveCandidate(&srcs) || RemoveCandidate(&targs)) continue; CandidateSearcher_MoveAll(cs, &srcs); CandidateSearcher_MoveAll(cs, &targs); } static void CandidateSearcher_CleanUp(CandidateSearcher *cs) { while (RemoveCandidate(&cs->list)) continue; assert(Lst_IsEmpty(&cs->list)); } /* * Find implicit sources for the target. * * Nodes are added to the graph as children of 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 nonexistent 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) { CandidateSearcher cs; CandidateSearcher_Init(&cs); FindDeps(gn, &cs); CandidateSearcher_CleanUp(&cs); CandidateSearcher_Done(&cs); } static void FindDeps(GNode *gn, CandidateSearcher *cs) { if (gn->type & OP_DEPS_FOUND) return; gn->type |= OP_DEPS_FOUND; /* Make sure we have these set, may get revised below. */ Var_Set(gn, TARGET, GNode_Path(gn)); Var_Set(gn, PREFIX, gn->name); DEBUG1(SUFF, "SuffFindDeps \"%s\"\n", gn->name); if (gn->type & OP_ARCHV) FindDepsArchive(gn, cs); else if (gn->type & OP_LIB) FindDepsLib(gn); else FindDepsRegular(gn, cs); } /* * 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) { Suffix *suff = FindSuffixByName(name); if (suff == NULL) { Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.", name); return; } if (nullSuff != NULL) nullSuff->flags &= ~(unsigned)SUFF_NULL; suff->flags |= SUFF_NULL; /* XXX: Here's where the transformation mangling would take place. */ nullSuff = suff; } /* Initialize the suffixes module. */ void Suff_Init(void) { /* * 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_DoneCall(&sufflist, SuffFree); Lst_DoneCall(&suffClean, SuffFree); if (nullSuff != NULL) SuffFree(nullSuff); Lst_Done(&transforms); #endif } static void PrintSuffNames(const char *prefix, SuffixList *suffs) { SuffixListNode *ln; debug_printf("#\t%s: ", prefix); for (ln = suffs->first; ln != NULL; ln = ln->next) { Suffix *suff = ln->datum; debug_printf("%s ", suff->name); } debug_printf("\n"); } static void Suffix_Print(Suffix *suff) { debug_printf("# \"%s\" (num %d, ref %d)", suff->name, suff->sNum, suff->refCount); if (suff->flags != 0) { char flags_buf[SuffixFlags_ToStringSize]; debug_printf(" (%s)", SuffixFlags_ToString(flags_buf, suff->flags)); } debug_printf("\n"); PrintSuffNames("To", &suff->parents); PrintSuffNames("From", &suff->children); debug_printf("#\tSearch Path: "); SearchPath_Print(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"); { SuffixListNode *ln; for (ln = sufflist.first; ln != NULL; ln = ln->next) Suffix_Print(ln->datum); } debug_printf("#*** Transformations:\n"); { GNodeListNode *ln; for (ln = transforms.first; ln != NULL; ln = ln->next) PrintTransformation(ln->datum); } } diff --git a/contrib/bmake/targ.c b/contrib/bmake/targ.c index 2b02f233ac48..68573644ff35 100644 --- a/contrib/bmake/targ.c +++ b/contrib/bmake/targ.c @@ -1,617 +1,617 @@ -/* $NetBSD: targ.c,v 1.165 2021/02/04 21:42:46 rillig Exp $ */ +/* $NetBSD: targ.c,v 1.168 2021/04/03 12:01:00 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. */ /* * Maintaining the targets and sources, which are both implemented as GNode. * * Interface: * Targ_Init Initialize the module. * * Targ_End Clean up the module. * * Targ_List Return the list of all targets so far. * * 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_Precious Return TRUE if the target is precious and + * 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 graph, 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.165 2021/02/04 21:42:46 rillig Exp $"); +MAKE_RCSID("$NetBSD: targ.c,v 1.168 2021/04/03 12:01:00 rillig Exp $"); /* * All target nodes that appeared on the left-hand side of one of the * dependency operators ':', '::', '!'. */ static GNodeList allTargets = LST_INIT; static HashTable allTargetsByName; #ifdef CLEANUP static GNodeList allNodes = LST_INIT; static void GNode_Free(void *); #endif void Targ_Init(void) { HashTable_Init(&allTargetsByName); } void Targ_End(void) { Targ_Stats(); #ifdef CLEANUP Lst_Done(&allTargets); HashTable_Done(&allTargetsByName); Lst_DoneCall(&allNodes, GNode_Free); #endif } void Targ_Stats(void) { HashTable_DebugStats(&allTargetsByName, "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 a new graph node, but don't register it anywhere. * * 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 * GNode_New(const char *name) { GNode *gn; 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 : OP_NONE; gn->flags = GNF_NONE; gn->made = UNMADE; gn->unmade = 0; gn->mtime = 0; gn->youngestChild = NULL; Lst_Init(&gn->implicitParents); Lst_Init(&gn->parents); Lst_Init(&gn->children); Lst_Init(&gn->order_pred); Lst_Init(&gn->order_succ); Lst_Init(&gn->cohorts); gn->cohort_num[0] = '\0'; gn->unmade_cohorts = 0; gn->centurion = NULL; gn->checked_seqno = 0; HashTable_Init(&gn->vars); Lst_Init(&gn->commands); gn->suffix = NULL; gn->fname = NULL; gn->lineno = 0; #ifdef CLEANUP Lst_Append(&allNodes, gn); #endif return gn; } #ifdef CLEANUP static void GNode_Free(void *gnp) { GNode *gn = gnp; free(gn->name); free(gn->uname); free(gn->path); /* Don't free gn->youngestChild since it is not owned by this node. */ /* * In the following lists, only free the list nodes, but not the * GNodes in them since these are not owned by this node. */ Lst_Done(&gn->implicitParents); Lst_Done(&gn->parents); Lst_Done(&gn->children); Lst_Done(&gn->order_pred); Lst_Done(&gn->order_succ); Lst_Done(&gn->cohorts); /* * Do not free the variables themselves, even though they are owned * by this node. * * XXX: For the nodes that represent targets or sources (and not * SCOPE_GLOBAL), it should be safe to free the variables as well, * since each node manages the memory for all its variables itself. * - * XXX: The GNodes that are only used as variable scopes (VAR_CMD, + * XXX: The GNodes that are only used as variable scopes (SCOPE_CMD, * SCOPE_GLOBAL, SCOPE_INTERNAL) are not freed at all (see Var_End, * where they are not mentioned). These might be freed at all, if * their variable values are indeed not used anywhere else (see * Trace_Init for the only suspicious use). */ HashTable_Done(&gn->vars); /* * Do not free the commands themselves, as they may be shared with * other nodes. */ Lst_Done(&gn->commands); /* * 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. */ free(gn); } #endif /* Get the existing global node, or return NULL. */ GNode * Targ_FindNode(const char *name) { return HashTable_FindValue(&allTargetsByName, name); } /* Get the existing global node, or create it. */ GNode * Targ_GetNode(const char *name) { - Boolean isNew; + bool 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 * table of global nodes. This means it cannot be found by name. * * This is used for internal nodes, such as cohorts or .WAIT nodes. */ GNode * Targ_NewInternalNode(const char *name) { GNode *gn = GNode_New(name); Global_Append(".ALLTARGETS", name); Lst_Append(&allTargets, gn); DEBUG1(TARG, "Adding \"%s\" to all targets.\n", gn->name); if (doing_depend) gn->flags |= FROM_DEPEND; return gn; } /* * 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; } /* Add the named nodes to the list, creating them as necessary. */ void Targ_FindList(GNodeList *gns, StringList *names) { StringListNode *ln; for (ln = names->first; ln != NULL; ln = ln->next) { const char *name = ln->datum; GNode *gn = Targ_GetNode(name); Lst_Append(gns, gn); } } /* See if the given target is precious. */ -Boolean +bool Targ_Precious(const GNode *gn) { /* XXX: Why are '::' targets precious? */ return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP); } /* * The main target to be made; only for debugging output. * See mainNode in parse.c for the definitive source. */ static GNode *mainTarg; /* Remember the main target to make; only used for debugging. */ void Targ_SetMain(GNode *gn) { mainTarg = gn; } static void PrintNodeNames(GNodeList *gnodes) { GNodeListNode *ln; for (ln = gnodes->first; ln != NULL; ln = ln->next) { GNode *gn = ln->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 formatted time is placed in a static area, so it is overwritten * with each call. */ const char * Targ_FmtTime(time_t tm) { static char buf[128]; struct tm *parts = localtime(&tm); - (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts); + (void)strftime(buf, sizeof buf, "%H:%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; type &= ~OP_OPMASK; while (type != 0) { tbit = 1 << (ffs(type) - 1); type &= ~tbit; switch (tbit) { #define PRINTBIT(bit, attr) case bit: debug_printf(" " attr); break #define PRINTDBIT(bit, attr) case bit: DEBUG0(TARG, " " attr); break PRINTBIT(OP_OPTIONAL, ".OPTIONAL"); PRINTBIT(OP_USE, ".USE"); PRINTBIT(OP_EXEC, ".EXEC"); PRINTBIT(OP_IGNORE, ".IGNORE"); PRINTBIT(OP_PRECIOUS, ".PRECIOUS"); PRINTBIT(OP_SILENT, ".SILENT"); PRINTBIT(OP_MAKE, ".MAKE"); PRINTBIT(OP_JOIN, ".JOIN"); PRINTBIT(OP_INVISIBLE, ".INVISIBLE"); PRINTBIT(OP_NOTMAIN, ".NOTMAIN"); PRINTDBIT(OP_LIB, ".LIB"); PRINTDBIT(OP_MEMBER, ".MEMBER"); PRINTDBIT(OP_ARCHV, ".ARCHV"); PRINTDBIT(OP_MADE, ".MADE"); PRINTDBIT(OP_PHONY, ".PHONY"); #undef PRINTBIT #undef PRINTDBIT } } } const char * GNodeMade_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)) return; debug_printf("#\n"); if (gn == mainTarg) debug_printf("# *** MAIN TARGET ***\n"); if (pass >= 2) { 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->mtime != 0) { debug_printf("# last modified %s: %s\n", Targ_FmtTime(gn->mtime), GNodeMade_Name(gn->made)); } else if (gn->made != UNMADE) { debug_printf("# nonexistent (maybe): %s\n", GNodeMade_Name(gn->made)); } else debug_printf("# unmade\n"); } PrintNodeNamesLine("implicit parents", &gn->implicitParents); } else { if (gn->unmade != 0) 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"); debug_printf("\n"); debug_printf("#\n"); debug_printf("# Files that are only sources:\n"); PrintOnlySources(); debug_printf("#*** Global Variables:\n"); Var_Dump(SCOPE_GLOBAL); debug_printf("#*** Command-line Variables:\n"); Var_Dump(SCOPE_CMDLINE); debug_printf("\n"); Dir_PrintDirectories(); debug_printf("\n"); Suff_PrintAll(); } /* * 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; } } } diff --git a/contrib/bmake/unit-tests/Makefile b/contrib/bmake/unit-tests/Makefile index d649c552a03a..784223a56652 100644 --- a/contrib/bmake/unit-tests/Makefile +++ b/contrib/bmake/unit-tests/Makefile @@ -1,739 +1,753 @@ -# $Id: Makefile,v 1.143 2021/02/06 18:31:30 sjg Exp $ +# $Id: Makefile,v 1.148 2021/06/16 19:18:56 sjg Exp $ # -# $NetBSD: Makefile,v 1.269 2021/02/06 18:26:03 sjg Exp $ +# $NetBSD: Makefile,v 1.279 2021/06/16 09:39:48 rillig 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. # .MAIN: all # we use these below but we might be an older make .MAKE.OS?= ${uname -s:L:sh} .MAKE.UID?= ${id -u:L: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-jobs TESTS+= cmd-errors-lint TESTS+= cmd-interrupt TESTS+= cmdline TESTS+= cmdline-redirect-stdin TESTS+= cmdline-undefined TESTS+= comment TESTS+= compat-error 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-eof 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-make-main 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-begin-fail TESTS+= deptgt-begin-fail-indirect TESTS+= deptgt-default TESTS+= deptgt-delete_on_error TESTS+= deptgt-end TESTS+= deptgt-end-fail TESTS+= deptgt-end-fail-all TESTS+= deptgt-end-fail-indirect 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-endfor TESTS+= directive-endif TESTS+= directive-error TESTS+= directive-export TESTS+= directive-export-env TESTS+= directive-export-impl TESTS+= directive-export-gmake TESTS+= directive-export-literal TESTS+= directive-for TESTS+= directive-for-errors TESTS+= directive-for-escape TESTS+= directive-for-generating-endif TESTS+= directive-for-lines TESTS+= directive-for-null 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-misspellings TESTS+= directive-sinclude TESTS+= directive-undef TESTS+= directive-unexport TESTS+= directive-unexport-env TESTS+= directive-warning 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+= job-output-null TESTS+= jobs-empty-commands +TESTS+= jobs-empty-commands-error TESTS+= jobs-error-indirect TESTS+= jobs-error-nested TESTS+= jobs-error-nested-make TESTS+= lint TESTS+= make-exported TESTS+= meta-cmd-cmp TESTS+= moderrs TESTS+= modmatch TESTS+= modmisc TESTS+= modts TESTS+= modword .if ${.MAKE.UID} > 0 TESTS+= objdir-writable .endif 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-errors-jobs 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-jobs-no-action TESTS+= opt-keep-going TESTS+= opt-keep-going-multiple TESTS+= opt-m-include-dir TESTS+= opt-no-action TESTS+= opt-no-action-at-all TESTS+= opt-no-action-runflags TESTS+= opt-no-action-touch 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+= recursive TESTS+= sh TESTS+= sh-dots TESTS+= sh-errctl TESTS+= sh-flags 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-incomplete TESTS+= suff-lookup TESTS+= suff-main TESTS+= suff-main-several TESTS+= suff-phony TESTS+= suff-rebuild TESTS+= suff-self TESTS+= suff-transform-debug 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-eval-short 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-indirect TESTS+= varmod-l-name-to-value TESTS+= varmod-localtime TESTS+= varmod-loop +TESTS+= varmod-loop-varname 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-sun-shell 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-makeflags 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 # 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) # 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.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, especially not -k FLAGS.jobs-error-indirect= # none, especially not -k FLAGS.jobs-error-nested= # none, especially not -k FLAGS.jobs-error-nested-make= # none, especially not -k FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain' # Some tests need extra postprocessing. SED_CMDS.dir= ${:D remove output from -DCLEANUP mode } SED_CMDS.dir+= -e '/^OpenDirs_Done:/d' SED_CMDS.dir+= -e '/^CachedDir /d' SED_CMDS.export= -e '/^[^=_A-Za-z0-9]*=/d' SED_CMDS.export-all= ${SED_CMDS.export} SED_CMDS.export-env= ${SED_CMDS.export} SED_CMDS.cmdline= -e 's,uid${.MAKE.UID}/,,' 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.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,' SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1} SED_CMDS.opt-debug-graph2= ${STD_SED_CMDS.dg2} SED_CMDS.opt-debug-graph3= ${STD_SED_CMDS.dg3} 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: ,' SED_CMDS.opt-debug-jobs+= -e 's,Command: ${.SHELL:T},Command: ,' # The "-q" may be there or not, see jobs.c, variable shells. SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: \) -q,\1,' +SED_CMDS.opt-debug-lint+= ${STD_SED_CMDS.regex} SED_CMDS.opt-jobs-no-action= ${STD_SED_CMDS.hide-from-output} SED_CMDS.opt-no-action-runflags= ${STD_SED_CMDS.hide-from-output} -# For Compat_RunCommand, useShell == FALSE. +# For Compat_RunCommand, useShell == false. SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,,' -# For Compat_RunCommand, useShell == TRUE. +# For Compat_RunCommand, useShell == true. SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)) failed (.*)$$,,' SED_CMDS.sh-dots+= -e 's,^\(\*\*\* Error code \)[1-9][0-9]*,\1,' SED_CMDS.sh-errctl= ${STD_SED_CMDS.dj} SED_CMDS.sh-flags= ${STD_SED_CMDS.hide-from-output} SED_CMDS.suff-main+= ${STD_SED_CMDS.dg1} SED_CMDS.suff-main-several+= ${STD_SED_CMDS.dg1} SED_CMDS.suff-transform-debug+= ${STD_SED_CMDS.dg1} SED_CMDS.var-op-shell+= ${STD_SED_CMDS.shell} SED_CMDS.var-op-shell+= -e '/command/s,No such.*,not found,' SED_CMDS.vardebug+= -e 's,${.SHELL},,' -SED_CMDS.varmod-subst-regex+= \ - -e 's,\(Regex compilation error:\).*,\1 (details omitted),' +SED_CMDS.varmod-subst-regex+= ${STD_SED_CMDS.regex} 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' SED_CMDS.varname-empty= -e 's,${.CURDIR},,g' SED_CMDS.varname-empty+= -e '/\.PARSEDIR/d' SED_CMDS.varname-empty+= -e '/\.SHELL/d' # Some tests need an additional round of postprocessing. POSTPROC.deptgt-suffixes= awk '/^\#\*\*\* Suffixes/,/^never-stop/' POSTPROC.gnode-submake= awk '/Input graph/, /^$$/' # 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. # Some standard sed commands, to be used in the SED_CMDS above. # Omit details such as process IDs from the output of the -dg1 option. STD_SED_CMDS.dg1= -e 's,${.CURDIR}$$,,' STD_SED_CMDS.dg1+= -e '/\.MAKE.PATH_FILEMON/d' STD_SED_CMDS.dg1+= -e '/^MAKE_VERSION/d;/^\#.*\/mk/d' STD_SED_CMDS.dg1+= -e 's, ${DEFSYSPATH:U/usr/share/mk}$$, ,' STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE\.[A-Z_]* *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(MACHINE[_ARCH]* *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(MAKE *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(\.SHELL *=\) .*,\1
,' STD_SED_CMDS.dg2= ${STD_SED_CMDS.dg1} STD_SED_CMDS.dg2+= -e 's,\(last modified\) ..:..:.. ... ..\, ....,\1 ,' STD_SED_CMDS.dg3= ${STD_SED_CMDS.dg2} # Omit details such as process IDs from the output of the -dj option. STD_SED_CMDS.dj= \ -e '/Process/d;/JobFinish:/d' \ -e 's,^\(Job_TokenWithdraw\)([0-9]*),\1(),' \ -e 's,^([0-9][0-9]*) \(withdrew token\),() \1,' \ -e 's, \(pid\) [0-9][0-9]*, \1 ,' \ -e 's,^\( Command:\) .*,\1 ,' # Reduce the noise for tests running with the -n option, since there is no # other way to suppress the echoing of the commands. STD_SED_CMDS.hide-from-output= \ -e '/^echo hide-from-output/d' \ -e 's,hide-from-output ,,' \ -e 's,hide-from-output,,' # Normalize the output for error messages from the shell. # # $shell -c '...' # NetBSD sh ...: not found # NetBSD ksh ksh: ...: not found # bash 5.0.18 bash: ...: command not found # bash 5.1.0 bash: line 1: ...: command not found # dash dash: 1: ...: not found # # $shell -c '< /nonexistent' # NetBSD sh sh: cannot open /nonexistent: no such file # NetBSD ksh ksh: cannot open /nonexistent: No such file or directory # bash 5.0.18 bash: /nonexistent: No such file or directory # bash 5.1.0 bash: line 1: /nonexistent: No such file or directory # dash dash: 1: cannot open /nonexistent: No such file # # echo '< /nonexistent' | $shell # NetBSD sh sh: cannot open /nonexistent: no such file # NetBSD ksh ksh: [1]: cannot open /nonexistent: No such file or directory # bash 5.0.18 bash: line 1: /nonexistent: No such file or directory # bash 5.1.0 bash: line 1: /nonexistent: No such file or directory # dash dash: 1: cannot open /nonexistent: No such file # STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: line [0-9][0-9]*: ,,' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: [0-9][0-9]*: ,,' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: ,,' +# The actual error messages for a failed regcomp or regexec differ between the +# implementations. +STD_SED_CMDS.regex= \ + -e 's,\(Regex compilation error:\).*,\1 (details omitted),' + # End of the configuration helpers section. .-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 # for many tests we need a TMPDIR that will not collide # with other users. .if ${.OBJDIR} != ${.CURDIR} # easy TMPDIR:= ${.OBJDIR}/tmp +.elif defined(TMPDIR) +TMPDIR:= ${TMPDIR}/uid${.MAKE.UID} .else -TMPDIR:= ${TMPDIR:U/tmp}/uid${.MAKE.UID} +TMPDIR:= /tmp/uid${.MAKE.UID} .endif # make sure it exists .if !exist(${TMPDIR}) x!= echo; mkdir -p ${TMPDIR} .endif MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc MAKE_TEST_ENV+= TMPDIR=${TMPDIR} .if ${.MAKE.OS} == "NetBSD" LIMIT_RESOURCES?= ulimit -v 200000 .endif LIMIT_RESOURCES?= : # 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; \ ${LIMIT_RESOURCES}; \ 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,' _SED_CMDS+= -e 's,${TMPDIR},TMPDIR,g' # strip ${.CURDIR}/ from the output _SED_CMDS+= -e 's,${.CURDIR:S,.,\\.,g}/,,g' _SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g' # on AT&T derrived systems; false exits 255 not 1 .if ${.MAKE.OS:N*BSD} != "" _SED_CMDS+= -e 's,\(Error code\) 255,\1 1,' .endif .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} .if empty(DIFF_FLAGS) DIFF_ECHO= echo .else DIFF_ECHO= : .endif # Compare all output files test: ${OUTFILES} .PHONY @failed= ; \ for test in ${TESTS}; do \ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out && continue || \ ${DIFF_ECHO} diff ${UNIT_TESTS}/$${test}.exp $${test}.out; \ ${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 diff --git a/contrib/bmake/unit-tests/archive.mk b/contrib/bmake/unit-tests/archive.mk index f8815cf40a40..2cd43a99e9ad 100644 --- a/contrib/bmake/unit-tests/archive.mk +++ b/contrib/bmake/unit-tests/archive.mk @@ -1,60 +1,60 @@ -# $NetBSD: archive.mk,v 1.11 2020/11/15 14:07:53 rillig Exp $ +# $NetBSD: archive.mk,v 1.12 2021/04/09 14:42:00 christos 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 deviates from the tutorial style of # several other tests. ARCHIVE= libprog.a -FILES= archive.mk modmisc.mk varmisc.mk +FILES= archive.mk archive-suffix.mk modmisc.mk ternary.mk varmisc.mk all: .if ${.PARSEDIR:tA} != ${.CURDIR:tA} - @cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR} + @cd ${MAKEFILE:H} && cp ${FILES} ${.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. @${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 @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post @echo $@ depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post @echo $@ remove-archive: pre post rm -f ${ARCHIVE} pre: .USEBEFORE @echo Making ${.TARGET} ${.OODATE:C,.+,out-of-date,W} ${.OODATE:O} post: .USE @echo diff --git a/contrib/bmake/unit-tests/cmd-errors-jobs.exp b/contrib/bmake/unit-tests/cmd-errors-jobs.exp index 6d9c6bb7f890..9ed0557975b3 100644 --- a/contrib/bmake/unit-tests/cmd-errors-jobs.exp +++ b/contrib/bmake/unit-tests/cmd-errors-jobs.exp @@ -1,9 +1,9 @@ : undefined eol make: Unclosed variable "UNCLOSED" : unclosed-variable make: Unclosed variable expression (expecting '}') for "UNCLOSED" : unclosed-modifier -make: Unknown modifier 'Z' +make: Unknown modifier "Z" : unknown-modifier eol : end eol exit status 0 diff --git a/contrib/bmake/unit-tests/cmd-errors-lint.exp b/contrib/bmake/unit-tests/cmd-errors-lint.exp index 09924c538de0..90b63bbcb08e 100644 --- a/contrib/bmake/unit-tests/cmd-errors-lint.exp +++ b/contrib/bmake/unit-tests/cmd-errors-lint.exp @@ -1,9 +1,9 @@ : undefined make: Unclosed variable "UNCLOSED" : unclosed-variable make: Unclosed variable expression (expecting '}') for "UNCLOSED" : unclosed-modifier -make: Unknown modifier 'Z' +make: Unknown modifier "Z" : unknown-modifier : end exit status 2 diff --git a/contrib/bmake/unit-tests/cmd-errors.exp b/contrib/bmake/unit-tests/cmd-errors.exp index 6d9c6bb7f890..9ed0557975b3 100644 --- a/contrib/bmake/unit-tests/cmd-errors.exp +++ b/contrib/bmake/unit-tests/cmd-errors.exp @@ -1,9 +1,9 @@ : undefined eol make: Unclosed variable "UNCLOSED" : unclosed-variable make: Unclosed variable expression (expecting '}') for "UNCLOSED" : unclosed-modifier -make: Unknown modifier 'Z' +make: Unknown modifier "Z" : unknown-modifier eol : end eol exit status 0 diff --git a/contrib/bmake/unit-tests/cond-func-empty.mk b/contrib/bmake/unit-tests/cond-func-empty.mk index 5094924f1c8d..11a990cbbce1 100644 --- a/contrib/bmake/unit-tests/cond-func-empty.mk +++ b/contrib/bmake/unit-tests/cond-func-empty.mk @@ -1,184 +1,184 @@ -# $NetBSD: cond-func-empty.mk,v 1.11 2020/11/28 14:08:37 rillig Exp $ +# $NetBSD: cond-func-empty.mk,v 1.14 2021/04/11 13:35:56 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. # .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. 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 +# (DEF_UNDEF) by adding the DEF_DEFINED 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 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 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. +# 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_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 # Between 2020-06-28 and var.c 1.226 from 2020-07-02, this paragraph generated # a wrong error message "Variable VARNAME is recursive". # # The bug was that the !empty() condition was evaluated, even though this was # not necessary since the defined() condition already evaluated to false. # # When evaluating the !empty condition, the variable name was parsed as # "VARNAME${:U2}", but without expanding any nested variable expression, in # this case the ${:U2}. Therefore, the variable name came out as simply # "VARNAME". Since this variable name should have been discarded quickly after # parsing it, this unrealistic variable name should have done no harm. # # The variable expression was expanded though, and this was wrong. The -# expansion was done without the VARE_WANTRES flag (called VARF_WANTRES back +# expansion was done without VARE_WANTRES (called VARF_WANTRES back # then) though. This had the effect that the ${:U1} from the value of VARNAME # expanded to an empty string. This in turn created the seemingly recursive # definition VARNAME=${VARNAME}, and that definition was never meant to be # expanded. # # This was fixed by expanding nested variable expressions in the variable name # only if the flag VARE_WANTRES is given. VARNAME= ${VARNAME${:U1}} .if defined(VARNAME${:U2}) && !empty(VARNAME${:U2}) .endif all: @:; diff --git a/contrib/bmake/unit-tests/cond-func-make-main.mk b/contrib/bmake/unit-tests/cond-func-make-main.mk index 31b370afabde..97b91f869991 100644 --- a/contrib/bmake/unit-tests/cond-func-make-main.mk +++ b/contrib/bmake/unit-tests/cond-func-make-main.mk @@ -1,62 +1,62 @@ -# $NetBSD: cond-func-make-main.mk,v 1.1 2020/11/22 19:37:27 rillig Exp $ +# $NetBSD: cond-func-make-main.mk,v 1.2 2021/04/04 10:13:09 rillig Exp $ # # Test how accurately the make() function in .if conditions reflects # what is actually made. # # There are several ways to specify what is being made: # # 1. The default main target is the first target in the given makefiles that # is not one of the special targets. For example, .PHONY is special when # it appears on the left-hand side of the ':'. It is not special on the # right-hand side though. # # 2. Command line arguments that are neither options (-ds or -k) nor variable # assignments (VAR=value) are interpreted as targets to be made. These # override the default main target from above. # # 3. All sources of the first '.MAIN: sources' line. Any further .MAIN line # is treated as if .MAIN were a regular name. # # This test only covers items 1 and 3. For item 2, see cond-func-make.mk. first-main-target: : Making ${.TARGET}. # Even though the main-target would actually be made at this point, it is # ignored by the make() function. .if make(first-main-target) . error .endif # Declaring a target via the .MAIN dependency adds it to the targets to be # created (opts.create), but only that list was empty at the beginning of # the line. This implies that several main targets can be set at the name # time, but they have to be in the same dependency group. # -# See ParseDoDependencyTargetSpecial, branch SP_MAIN. +# See ParseDependencyTargetSpecial, branch SP_MAIN. .MAIN: dot-main-target-1a dot-main-target-1b .if !make(dot-main-target-1a) . error .endif .if !make(dot-main-target-1b) . error .endif dot-main-target-{1,2}{a,b}: : Making ${.TARGET}. # At this point, the list of targets to be made (opts.create) is not empty -# anymore. ParseDoDependencyTargetSpecial therefore treats the .MAIN as if +# anymore. ParseDependencyTargetSpecial therefore treats the .MAIN as if # it were an ordinary target. Since .MAIN is not listed as a dependency # anywhere, it is not made. .if target(.MAIN) . error .endif .MAIN: dot-main-target-2a dot-main-target-2b .if !target(.MAIN) . error .endif .if make(dot-main-target-2a) . error .endif diff --git a/contrib/bmake/unit-tests/cond-late.exp b/contrib/bmake/unit-tests/cond-late.exp index 46c4aa2f4230..e179e8c74cc4 100644 --- a/contrib/bmake/unit-tests/cond-late.exp +++ b/contrib/bmake/unit-tests/cond-late.exp @@ -1,4 +1,4 @@ -make: Bad conditional expression ` != "no"' in != "no"?: +make: Bad conditional expression ' != "no"' in ' != "no"?:' yes no exit status 0 diff --git a/contrib/bmake/unit-tests/cond-short.mk b/contrib/bmake/unit-tests/cond-short.mk index 46c7ea26a97b..113c3fd08fed 100644 --- a/contrib/bmake/unit-tests/cond-short.mk +++ b/contrib/bmake/unit-tests/cond-short.mk @@ -1,216 +1,214 @@ -# $NetBSD: cond-short.mk,v 1.15 2020/12/01 19:37:23 rillig Exp $ +# $NetBSD: cond-short.mk,v 1.16 2021/03/14 11:49:37 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'. # # Before 2020-06-28, the right-hand side of an && or || operator was always # evaluated, which was wrong. In cond.c 1.69 and var.c 1.197 on 2015-10-11, # Var_Parse got a new parameter named 'wantit'. Since then it would have been # possible to skip evaluation of irrelevant variable expressions and only # parse them. They were still evaluated though, the only difference to # relevant variable expressions was that in the irrelevant variable # expressions, undefined variables were allowed. +# +# See also: +# var-eval-short.mk, for short-circuited variable modifiers -# The && operator. +# 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. +# 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 # With cond.c 1.76 from 2020-07-03, the following condition triggered a # warning: "String comparison operator should be either == or !=". # This was because the variable expression ${iV2} was defined, but the # contained variable V66 was undefined. The left-hand side of the comparison # therefore evaluated to the string "${V66}", which is obviously not a number. # # This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant # comparisons. Instead, they are only parsed and then discarded. # # At that time, there was not enough debug logging to see the details in the # -dA log. To actually see it, add debug logging at the beginning and end of # Var_Parse. .if defined(V66) && ( ${iV2} < ${V42} ) x= Fail .else x= Ok .endif # XXX: This condition doesn't match the one above. The quotes are missing # above. This is a crucial detail since without quotes, the variable # expression ${iV2} evaluates to "${V66}", and with quotes, it evaluates to "" # since undefined variables are allowed and expand to an empty string. x!= echo 'defined(V66) && ( "$${iV2}" < $${V42} ): $x' >&2; echo .if 1 || ${iV1} < ${V42} x= Ok .else x= Fail .endif x!= echo '1 || $${iV1} < $${V42}: $x' >&2; echo # With cond.c 1.76 from 2020-07-03, the following condition triggered a # warning: "String comparison operator should be either == or !=". # This was because the variable expression ${iV2} was defined, but the # contained variable V66 was undefined. The left-hand side of the comparison # therefore evaluated to the string "${V66}", which is obviously not a number. # # This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant # comparisons. Instead, they are only parsed and then discarded. # # At that time, there was not enough debug logging to see the details in the # -dA log. To actually see it, add debug logging at the beginning and end of # Var_Parse. .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 # The right-hand side of the '&&' is irrelevant since the left-hand side # already evaluates to false. Before cond.c 1.79 from 2020-07-09, it was # expanded nevertheless, although with a small modification: undefined # variables may be used in these expressions without generating an error. .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: - @:;: diff --git a/contrib/bmake/unit-tests/cond-token-string.exp b/contrib/bmake/unit-tests/cond-token-string.exp index 07b318caa81a..45f9993457d3 100644 --- a/contrib/bmake/unit-tests/cond-token-string.exp +++ b/contrib/bmake/unit-tests/cond-token-string.exp @@ -1,18 +1,18 @@ -make: "cond-token-string.mk" line 13: Unknown modifier 'Z' +make: "cond-token-string.mk" line 13: Unknown modifier "Z" make: "cond-token-string.mk" line 13: Malformed conditional ("" != "${:Uvalue:Z}") make: "cond-token-string.mk" line 22: xvalue is not defined. make: "cond-token-string.mk" line 28: Malformed conditional (x${:Uvalue} == "") make: "cond-token-string.mk" line 37: Expected. CondParser_Eval: "UNDEF" make: "cond-token-string.mk" line 46: The string literal "UNDEF" is not empty. CondParser_Eval: " " make: "cond-token-string.mk" line 55: The string literal " " is not empty, even though it consists of whitespace only. CondParser_Eval: "${UNDEF}" make: "cond-token-string.mk" line 64: An undefined variable in quotes expands to an empty string, which then evaluates to false. CondParser_Eval: "${:Uvalue}" make: "cond-token-string.mk" line 68: A nonempty variable expression evaluates to true. CondParser_Eval: "${:U}" make: "cond-token-string.mk" line 76: An empty variable evaluates to false. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-token-var.mk b/contrib/bmake/unit-tests/cond-token-var.mk index 30eba87ad4d2..168c63c46ac1 100644 --- a/contrib/bmake/unit-tests/cond-token-var.mk +++ b/contrib/bmake/unit-tests/cond-token-var.mk @@ -1,48 +1,69 @@ -# $NetBSD: cond-token-var.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $ +# $NetBSD: cond-token-var.mk,v 1.6 2021/04/25 21:05:38 rillig Exp $ # # 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 on its own generates a parse error. .if ${UNDEF} .endif # 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 + +# If the value of the variable expression is a number, it is compared against +# zero. +.if ${:U0} +. error +.endif +.if !${:U1} +. error +.endif + +# If the value of the variable expression is not a number, any non-empty +# value evaluates to true, even if there is only whitespace. +.if ${:U} +. error +.endif +.if !${:U } +. error +.endif +.if !${:Uanything} +. error +.endif diff --git a/contrib/bmake/unit-tests/cond1.exp b/contrib/bmake/unit-tests/cond1.exp index 0acd935780a0..8b65d782524d 100644 --- a/contrib/bmake/unit-tests/cond1.exp +++ b/contrib/bmake/unit-tests/cond1.exp @@ -1,23 +1,23 @@ 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: String comparison operator must be either == or != -make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No +make: Bad conditional expression '"0" > 0' in '"0" > 0?OK:No' OK exit status 0 diff --git a/contrib/bmake/unit-tests/counter-append.mk b/contrib/bmake/unit-tests/counter-append.mk index 1c4e00d6118c..d234835e5ec3 100755 --- a/contrib/bmake/unit-tests/counter-append.mk +++ b/contrib/bmake/unit-tests/counter-append.mk @@ -1,28 +1,28 @@ -# $NetBSD: counter-append.mk,v 1.4 2020/10/17 16:57:17 rillig Exp $ +# $NetBSD: counter-append.mk,v 1.5 2021/04/04 10:13:09 rillig Exp $ # # Demonstrates how to let make count the number of times a variable # is actually accessed, using the ::+= variable modifier. # # This works since 2020-09-23. Before that, the counter ended up at having # 6 words, even though the NEXT variable was only accessed 3 times. # The cause for this surprising behavior was that the ::= variable modifiers # returned an error marker instead of a simple empty string. RELEVANT= yes (load-time part) # just to filter the output COUNTER= # zero NEXT= ${COUNTER::+=a}${COUNTER:[#]} # This variable is first set to empty and then expanded. -# See parse.c, function Parse_DoVar, keyword "!Var_Exists". +# See parse.c, function Parse_Var, keyword "!Var_Exists". A:= ${NEXT} B:= ${NEXT} C:= ${NEXT} RELEVANT= no all: @: ${RELEVANT::=yes (run-time part)} @echo A=${A:Q} B=${B:Q} C=${C:Q} COUNTER=${COUNTER:[#]:Q} @: ${RELEVANT::=no} diff --git a/contrib/bmake/unit-tests/counter.mk b/contrib/bmake/unit-tests/counter.mk index 3c75d7a5032a..7cf8fba72876 100644 --- a/contrib/bmake/unit-tests/counter.mk +++ b/contrib/bmake/unit-tests/counter.mk @@ -1,28 +1,28 @@ -# $NetBSD: counter.mk,v 1.5 2020/10/17 16:57:17 rillig Exp $ +# $NetBSD: counter.mk,v 1.6 2021/04/04 10:13:09 rillig Exp $ # # Demonstrates how to let make count the number of times a variable # is actually accessed, using the ::= variable modifier. # # This works since 2020-09-23. Before that, the counter ended up at having # 4 words, even though the NEXT variable was only accessed 3 times. # The cause for this surprising behavior was that the ::= variable modifiers # returned an error marker instead of a simple empty string. RELEVANT= yes (load-time part) # just to filter the output COUNTER= # zero NEXT= ${COUNTER::=${COUNTER} a}${COUNTER:[#]} # This variable is first set to empty and then expanded. -# See parse.c, function Parse_DoVar, keyword "!Var_Exists". +# See parse.c, function Parse_Var, keyword "!Var_Exists". A:= ${NEXT} B:= ${NEXT} C:= ${NEXT} RELEVANT= no all: @: ${RELEVANT::=yes (run-time part)} @echo A=${A:Q} B=${B:Q} C=${C:Q} COUNTER=${COUNTER:[#]:Q} @: ${RELEVANT::=no} diff --git a/contrib/bmake/unit-tests/dep-var.mk b/contrib/bmake/unit-tests/dep-var.mk index 438a8a84a60d..4503424e31ab 100755 --- a/contrib/bmake/unit-tests/dep-var.mk +++ b/contrib/bmake/unit-tests/dep-var.mk @@ -1,88 +1,88 @@ -# $NetBSD: dep-var.mk,v 1.5 2020/09/13 20:04:26 rillig Exp $ +# $NetBSD: dep-var.mk,v 1.6 2021/04/04 10:13:09 rillig Exp $ # # Tests for variable references in dependency declarations. # # Uh oh, this feels so strange that probably nobody uses it. But it seems to # be the only way to reach the lower half of SuffExpandChildren. # XXX: The -dv log says: # Var_Parse: ${UNDEF1} with VARE_UNDEFERR|VARE_WANTRES # but no error message is generated for this line. # The variable expression ${UNDEF1} simply expands to an empty string. all: ${UNDEF1} # Using a double dollar in order to circumvent immediate variable expansion # feels like unintended behavior. At least the manual page says nothing at # all about defined or undefined variables in dependency lines. # # At the point where the expression ${DEF2} is expanded, the variable DEF2 # is defined, so everything's fine. all: $${DEF2} a-$${DEF2}-b # This variable is not defined at all. # XXX: The -dv log says: # Var_Parse: ${UNDEF3} with VARE_UNDEFERR|VARE_WANTRES # but no error message is generated for this line, just like for UNDEF1. # The variable expression ${UNDEF3} simply expands to an empty string. all: $${UNDEF3} # Try out how many levels of indirection are really expanded in dependency # lines. # # The first level of indirection is the $$ in the dependency line. # When the dependency line is parsed, it is resolved to the string # "${INDIRECT_1}". At this point, the dollar is just an ordinary character, # waiting to be expanded at some later point. # # Later, in SuffExpandChildren, that expression is expanded again by calling # Var_Parse, and this time, the result is the string "1-2-${INDIRECT_2}-2-1". # # This string is not expanded anymore by Var_Parse. But there is another # effect. Now DirExpandCurly comes into play and expands the curly braces # in this filename pattern, resulting in the string "1-2-$INDIRECT_2-2-1". # As of 2020-09-03, the test dir.mk contains further details on this topic. # # Finally, this string is assigned to the local ${.TARGET} variable. This # variable is expanded when the shell command is generated. At that point, # the $I is expanded. Since the variable I is not defined, it expands to # the empty string. This way, the final output is the string # "1-2-NDIRECT_2-2-1", which differs from the actual name of the target. # For exactly this reason, it is not recommended to use dollar signs in # target names. # # The number of actual expansions is way more than one might expect, # therefore this feature is probably not widely used. # all: 1-$${INDIRECT_1}-1 INDIRECT_1= 2-$${INDIRECT_2}-2 INDIRECT_2= 3-$${INDIRECT_3}-3 INDIRECT_3= indirect UNDEF1= undef1 DEF2= def2 # Cover the code in SuffExpandChildren that deals with malformed variable # expressions. # # This seems to be an edge case that never happens in practice, and it would # probably be appropriate to just error out in such a case. # # To trigger this piece of code, the variable name must contain "$)" or "$:" # or "$)" or "$$". Using "$:" does not work since the dependency line is # fully expanded before parsing, therefore any ':' in a target or source name # would be interpreted as a dependency operator instead. all: $$$$) # The $$INDIRECT in the following line is treated like the dependency of the # "all" target, that is, the "$$I" is first expanded to "$I", and in a second # round of expansion, the "$I" expands to nothing since the variable "I" is # undefined. # # Since 2020-09-13, this generates a parse error in lint mode (-dL), but not -# in normal mode since ParseDoDependency does not handle any errors after +# in normal mode since ParseDependency does not handle any errors after # calling Var_Parse. undef1 def2 a-def2-b 1-2-$$INDIRECT_2-2-1 ${:U\$)}: @echo ${.TARGET:Q} # XXX: Why is the exit status still 0, even though Parse_Error is called # with PARSE_FATAL in SuffExpandChildren? diff --git a/contrib/bmake/unit-tests/deptgt-makeflags.exp b/contrib/bmake/unit-tests/deptgt-makeflags.exp index 7eb54eba7f30..11043bc5110c 100644 --- a/contrib/bmake/unit-tests/deptgt-makeflags.exp +++ b/contrib/bmake/unit-tests/deptgt-makeflags.exp @@ -1,10 +1,10 @@ Global:delete DOLLAR (not found) -Command:DOLLAR = $$$$ -Global:.MAKEOVERRIDES = VAR DOLLAR +Command: DOLLAR = $$$$ +Global: .MAKEOVERRIDES = VAR DOLLAR CondParser_Eval: ${DOLLAR} != "\$\$" -Var_Parse: ${DOLLAR} != "\$\$" with VARE_UNDEFERR|VARE_WANTRES +Var_Parse: ${DOLLAR} != "\$\$" (eval-defined) 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 +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 diff --git a/contrib/bmake/unit-tests/deptgt-order.exp b/contrib/bmake/unit-tests/deptgt-order.exp index 39a9383953dd..5f7dde0ac69d 100644 --- a/contrib/bmake/unit-tests/deptgt-order.exp +++ b/contrib/bmake/unit-tests/deptgt-order.exp @@ -1 +1,4 @@ +: 'Making two out of one.' +: 'Making three out of two.' +: 'Making all out of three.' exit status 0 diff --git a/contrib/bmake/unit-tests/deptgt-order.mk b/contrib/bmake/unit-tests/deptgt-order.mk index 003552f57a49..f241331ae1e1 100644 --- a/contrib/bmake/unit-tests/deptgt-order.mk +++ b/contrib/bmake/unit-tests/deptgt-order.mk @@ -1,8 +1,18 @@ -# $NetBSD: deptgt-order.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-order.mk,v 1.3 2021/06/17 15:25:33 rillig Exp $ # # Tests for the special target .ORDER in dependency declarations. -# TODO: Implementation +all one two three: .PHONY -all: - @:; +two: one + : 'Making $@ out of $>.' +three: two + : 'Making $@ out of $>.' + +# This .ORDER creates a circular dependency since 'three' depends on 'one' +# but 'one' is supposed to be built after 'three'. +.ORDER: three one + +# XXX: The circular dependency should be detected here. +all: three + : 'Making $@ out of $>.' diff --git a/contrib/bmake/unit-tests/deptgt.exp b/contrib/bmake/unit-tests/deptgt.exp index b2aeaa5a2850..bdac2aee3e6c 100644 --- a/contrib/bmake/unit-tests/deptgt.exp +++ b/contrib/bmake/unit-tests/deptgt.exp @@ -1,14 +1,14 @@ 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) +ParseDependency(: empty-source) ParseReadLine (35): ' : command for empty targets list' ParseReadLine (36): ': empty-source' -ParseDoDependency(: empty-source) +ParseDependency(: empty-source) ParseReadLine (37): ' : command for empty targets list' ParseReadLine (38): '.MAKEFLAGS: -d0' -ParseDoDependency(.MAKEFLAGS: -d0) -make: "deptgt.mk" line 46: Unknown modifier 'Z' +ParseDependency(.MAKEFLAGS: -d0) +make: "deptgt.mk" line 46: Unknown modifier "Z" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/deptgt.mk b/contrib/bmake/unit-tests/deptgt.mk index 09f381715e6d..15d7e59aeced 100644 --- a/contrib/bmake/unit-tests/deptgt.mk +++ b/contrib/bmake/unit-tests/deptgt.mk @@ -1,49 +1,49 @@ -# $NetBSD: deptgt.mk,v 1.10 2020/12/27 18:20:26 rillig Exp $ +# $NetBSD: deptgt.mk,v 1.11 2021/04/04 10:13:09 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 +# ParseDependency 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 # Just to show that a malformed expression is only expanded once in # ParseDependencyTargetWord. The only way to produce an expression that # is well-formed on the first expansion and ill-formed on the second # expansion would be to use the variable modifier '::=' to modify the # targets. This in turn would be such an extreme and unreliable edge case # that nobody uses it. $$$$$$$${:U:Z}: all: @:; diff --git a/contrib/bmake/unit-tests/directive-export-impl.exp b/contrib/bmake/unit-tests/directive-export-impl.exp index 1a5cf34dbfb8..740daa605129 100644 --- a/contrib/bmake/unit-tests/directive-export-impl.exp +++ b/contrib/bmake/unit-tests/directive-export-impl.exp @@ -1,56 +1,56 @@ ParseReadLine (21): 'UT_VAR= <${REF}>' -Global:UT_VAR = <${REF}> +Global: UT_VAR = <${REF}> ParseReadLine (28): '.export UT_VAR' -Global:.MAKE.EXPORTED = UT_VAR +Global: .MAKE.EXPORTED = UT_VAR ParseReadLine (32): ': ${UT_VAR:N*}' -Var_Parse: ${UT_VAR:N*} with VARE_UNDEFERR|VARE_WANTRES -Var_Parse: ${REF}> with VARE_UNDEFERR|VARE_WANTRES -Applying ${UT_VAR:N...} to "<>" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none) -Pattern[UT_VAR] for [<>] is [*] +Var_Parse: ${UT_VAR:N*} (eval-defined) +Var_Parse: ${REF}> (eval-defined) +Evaluating modifier ${UT_VAR:N...} on value "<>" +Pattern for ':N' is "*" ModifyWords: split "<>" into 1 words -Result of ${UT_VAR:N*} is "" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none) -ParseDoDependency(: ) +Result of ${UT_VAR:N*} is "" +ParseDependency(: ) CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>" -Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:!...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) +Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined) +Evaluating modifier ${:!...} on value "" (eval-defined, undefined) Modifier part: "echo "$UT_VAR"" -Var_Parse: ${.MAKE.EXPORTED:O:u} with VARE_WANTRES -Applying ${.MAKE.EXPORTED:O} to "UT_VAR" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:O} is "UT_VAR" (VARE_WANTRES, none, none) -Applying ${.MAKE.EXPORTED:u} to "UT_VAR" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:u} is "UT_VAR" (VARE_WANTRES, none, none) -Var_Parse: ${UT_VAR} with VARE_WANTRES -Var_Parse: ${REF}> with VARE_WANTRES -Result of ${:!echo "\$UT_VAR"!} is "<>" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) +Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) +Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR" +Result of ${.MAKE.EXPORTED:O} is "UT_VAR" +Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_VAR" +Result of ${.MAKE.EXPORTED:u} is "UT_VAR" +Var_Parse: ${UT_VAR} (eval) +Var_Parse: ${REF}> (eval) +Result of ${:!echo "\$UT_VAR"!} is "<>" (eval-defined, defined) lhs = "<>", rhs = "<>", op = != -ParseReadLine (49): ': ${UT_VAR:N*}' -Var_Parse: ${UT_VAR:N*} with VARE_UNDEFERR|VARE_WANTRES -Var_Parse: ${REF}> with VARE_UNDEFERR|VARE_WANTRES -Applying ${UT_VAR:N...} to "<>" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none) -Pattern[UT_VAR] for [<>] is [*] +ParseReadLine (50): ': ${UT_VAR:N*}' +Var_Parse: ${UT_VAR:N*} (eval-defined) +Var_Parse: ${REF}> (eval-defined) +Evaluating modifier ${UT_VAR:N...} on value "<>" +Pattern for ':N' is "*" ModifyWords: split "<>" into 1 words -Result of ${UT_VAR:N*} is "" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none) -ParseDoDependency(: ) -ParseReadLine (53): 'REF= defined' -Global:REF = defined +Result of ${UT_VAR:N*} is "" +ParseDependency(: ) +ParseReadLine (54): 'REF= defined' +Global: REF = defined CondParser_Eval: ${:!echo "\$UT_VAR"!} != "" -Var_Parse: ${:!echo "\$UT_VAR"!} != "" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:!...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) +Var_Parse: ${:!echo "\$UT_VAR"!} != "" (eval-defined) +Evaluating modifier ${:!...} on value "" (eval-defined, undefined) Modifier part: "echo "$UT_VAR"" -Var_Parse: ${.MAKE.EXPORTED:O:u} with VARE_WANTRES -Applying ${.MAKE.EXPORTED:O} to "UT_VAR" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:O} is "UT_VAR" (VARE_WANTRES, none, none) -Applying ${.MAKE.EXPORTED:u} to "UT_VAR" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:u} is "UT_VAR" (VARE_WANTRES, none, none) -Var_Parse: ${UT_VAR} with VARE_WANTRES -Var_Parse: ${REF}> with VARE_WANTRES -Result of ${:!echo "\$UT_VAR"!} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) +Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) +Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR" +Result of ${.MAKE.EXPORTED:O} is "UT_VAR" +Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_VAR" +Result of ${.MAKE.EXPORTED:u} is "UT_VAR" +Var_Parse: ${UT_VAR} (eval) +Var_Parse: ${REF}> (eval) +Result of ${:!echo "\$UT_VAR"!} is "" (eval-defined, defined) lhs = "", rhs = "", op = != -ParseReadLine (61): 'all:' -ParseDoDependency(all:) -Global:.ALLTARGETS = all -ParseReadLine (62): '.MAKEFLAGS: -d0' -ParseDoDependency(.MAKEFLAGS: -d0) -Global:.MAKEFLAGS = -r -k -d cpv -d -Global:.MAKEFLAGS = -r -k -d cpv -d 0 +ParseReadLine (62): 'all:' +ParseDependency(all:) +Global: .ALLTARGETS = all +ParseReadLine (63): '.MAKEFLAGS: -d0' +ParseDependency(.MAKEFLAGS: -d0) +Global: .MAKEFLAGS = -r -k -d cpv -d +Global: .MAKEFLAGS = -r -k -d cpv -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/directive-export-impl.mk b/contrib/bmake/unit-tests/directive-export-impl.mk index 556e5352d1c3..0ad290f653d4 100644 --- a/contrib/bmake/unit-tests/directive-export-impl.mk +++ b/contrib/bmake/unit-tests/directive-export-impl.mk @@ -1,62 +1,63 @@ -# $NetBSD: directive-export-impl.mk,v 1.1 2020/12/29 01:45:06 rillig Exp $ +# $NetBSD: directive-export-impl.mk,v 1.3 2021/04/03 23:08:30 rillig Exp $ # # Test for the implementation of exporting variables to child processes. # This involves marking variables for export, actually exporting them, # or marking them for being re-exported. # # See also: # Var_Export # ExportVar # VarExportedMode (global) -# VAR_EXPORTED (per variable) -# VAR_REEXPORT (per variable) +# VarFlags.exported (per variable) +# VarFlags.reexport (per variable) # VarExportMode (per call of Var_Export and ExportVar) : ${:U:sh} # side effect: initialize .SHELL .MAKEFLAGS: -dcpv # This is a variable that references another variable. At this point, the # other variable is still undefined. UT_VAR= <${REF}> # At this point, ExportVar("UT_VAR", VEM_PLAIN) is called. Since the # variable value refers to another variable, ExportVar does not actually -# export the variable but only marks it as VAR_EXPORTED and VAR_REEXPORT. -# After that, ExportVars registers the variable name in .MAKE.EXPORTED. -# That's all for now. +# export the variable but only marks it as VarFlags.exported and +# VarFlags.reexport. After that, ExportVars registers the variable name in +# .MAKE.EXPORTED. That's all for now. .export UT_VAR -# Evaluating this expression shows the variable flags in the debug log, -# which are VAR_EXPORTED|VAR_REEXPORT. +# The following expression has both flags 'exported' and 'reexport' set. +# These flags do not show up anywhere, not even in the debug log. : ${UT_VAR:N*} # At the last moment before actually forking off the child process for the # :!...! modifier, Cmd_Exec calls Var_ReexportVars to have all relevant # variables exported. Since this variable has both of the above-mentioned # flags set, it is actually exported to the environment. The variable flags # are not modified though, since the next time the :!...! modifier is # evaluated, the referenced variables could have changed, therefore the # variable will be exported anew for each ':sh' modifier, ':!...!' modifier, # '!=' variable assignment. .if ${:!echo "\$UT_VAR"!} != "<>" . error .endif -# Evaluating this expression shows the variable flags in the debug log, -# which are still VAR_EXPORTED|VAR_REEXPORT, which means that the variable -# is still marked as being re-exported for each child process. +# The following expression still has 'exported' and 'reexport' set. +# These flags do not show up anywhere though, not even in the debug log. +# These flags means that the variable is still marked as being re-exported +# for each child process. : ${UT_VAR:N*} # Now the referenced variable gets defined. This does not influence anything # in the process of exporting the variable value, though. REF= defined # Nothing surprising here. The variable UT_VAR gets exported, and this time, # REF is defined and gets expanded into the exported environment variable. .if ${:!echo "\$UT_VAR"!} != "" . error .endif all: .MAKEFLAGS: -d0 diff --git a/contrib/bmake/unit-tests/directive-export.mk b/contrib/bmake/unit-tests/directive-export.mk index 40fda0968cb0..942d4b371bbd 100644 --- a/contrib/bmake/unit-tests/directive-export.mk +++ b/contrib/bmake/unit-tests/directive-export.mk @@ -1,35 +1,44 @@ -# $NetBSD: directive-export.mk,v 1.6 2020/12/13 01:07:54 rillig Exp $ +# $NetBSD: directive-export.mk,v 1.8 2021/02/16 19:01:18 rillig Exp $ # # Tests for the .export directive. # # See also: # directive-misspellings.mk # TODO: Implementation INDIRECT= indirect VAR= value $$ ${INDIRECT} # Before 2020-12-13, this unusual expression invoked undefined behavior since # it accessed out-of-bounds memory via Var_Export -> ExportVar -> MayExport. .export ${:U } # 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 -# No argument means to export all variables. +# No syntactical argument means to export all variables. .export +# An empty argument means no additional variables to export. +.export ${:U} + + +# Trigger the "This isn't going to end well" in ExportVarEnv. +EMPTY_SHELL= ${:sh} +.export EMPTY_SHELL # only marked for export at this point +_!= :;: # Force the variable to be actually exported. + + all: - @:; diff --git a/contrib/bmake/unit-tests/directive-for-errors.exp b/contrib/bmake/unit-tests/directive-for-errors.exp index 6088a93c9a4a..da5eee473ec2 100644 --- a/contrib/bmake/unit-tests/directive-for-errors.exp +++ b/contrib/bmake/unit-tests/directive-for-errors.exp @@ -1,22 +1,22 @@ make: "directive-for-errors.mk" line 7: Unknown directive "fori" make: "directive-for-errors.mk" line 8: warning: make: "directive-for-errors.mk" line 9: for-less endfor make: "directive-for-errors.mk" line 19: Unknown directive "for" make: "directive-for-errors.mk" line 20: warning: make: "directive-for-errors.mk" line 21: for-less endfor make: "directive-for-errors.mk" line 37: Dollar $ 1 1 and backslash 2 2 2. make: "directive-for-errors.mk" line 37: Dollar $ 3 3 and backslash 4 4 4. make: "directive-for-errors.mk" line 43: no iteration variables in for make: "directive-for-errors.mk" line 47: warning: Should not be reached. make: "directive-for-errors.mk" line 48: for-less endfor make: "directive-for-errors.mk" line 53: Wrong number of words (5) in .for substitution list with 3 variables make: "directive-for-errors.mk" line 64: missing `in' in for make: "directive-for-errors.mk" line 66: warning: Should not be reached. make: "directive-for-errors.mk" line 67: for-less endfor -make: "directive-for-errors.mk" line 73: Unknown modifier 'Z' +make: "directive-for-errors.mk" line 73: Unknown modifier "Z" make: "directive-for-errors.mk" line 74: warning: Should not be reached. make: "directive-for-errors.mk" line 74: warning: Should not be reached. make: "directive-for-errors.mk" line 74: warning: Should not be reached. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-for-errors.mk b/contrib/bmake/unit-tests/directive-for-errors.mk index 7890e2375af4..602ecbf32e4e 100644 --- a/contrib/bmake/unit-tests/directive-for-errors.mk +++ b/contrib/bmake/unit-tests/directive-for-errors.mk @@ -1,75 +1,75 @@ -# $NetBSD: directive-for-errors.mk,v 1.1 2020/12/31 03:05:12 rillig Exp $ +# $NetBSD: directive-for-errors.mk,v 1.3 2021/04/04 10:13:09 rillig Exp $ # # Tests for error handling in .for loops. # A .for directive must be followed by whitespace, everything else results # in a parse error. .fori in 1 2 3 . warning ${i} .endfor # A slash is not whitespace, therefore this is not parsed as a .for loop. # # XXX: The error message is misleading though. As of 2020-12-31, it says # "Unknown directive "for"", but that directive is actually known. This is # because ForEval does not detect the .for loop as such, so parsing -# continues in ParseLine > ParseDependency > ParseDoDependency > -# ParseDoDependencyTargets > ParseErrorNoDependency, and there the directive +# continues in ParseLine > ParseDependencyLine > ParseDependency > +# ParseDependencyTargets > ParseErrorNoDependency, and there the directive # name is parsed a bit differently. .for/i in 1 2 3 . warning ${i} .endfor # As of 2020-12-31, the variable name can be an arbitrary word, it just needs # to be separated by whitespace. Even '$' and '\' are valid variable names, # which is not useful in practice. # # The '$$' is not replaced with the values '1' or '3' from the .for loop, # instead it is kept as-is, and when the .info directive expands its argument, # each '$$' gets replaced with a single '$'. The "long variable expression" # ${$} gets replaced though, even though this would be a parse error everywhere # outside a .for loop. # # The '\' on the other hand is treated as a normal variable name. ${:U\$}= dollar # see whether the "variable" '$' is local ${:U\\}= backslash # see whether the "variable" '\' is local .for $ \ in 1 2 3 4 . info Dollar $$ ${$} $($) and backslash $\ ${\} $(\). .endfor # If there are no variables, there is no point in expanding the .for loop # since this would end up in an endless loop, each time consuming 0 of the # 3 values. .for in 1 2 3 # XXX: This should not be reached. It should be skipped, as already done # when the number of values is not a multiple of the number of variables, # see below. . warning Should not be reached. .endfor # There are 3 variables and 5 values. These 5 values cannot be split evenly # among the variables, therefore the loop is not expanded at all, it is # rather skipped. .for a b c in 1 2 3 4 5 . warning Should not be reached. .endfor # The list of values after the 'in' may be empty, no matter if this emptiness # comes from an empty expansion or even from a syntactically empty line. .for i in . info Would be reached if there were items to loop over. .endfor # A missing 'in' should parse the .for loop but skip the body. .for i : k # XXX: As of 2020-12-31, this line is reached once. . warning Should not be reached. .endfor # A malformed modifier should be detected and skip the body of the loop. # # XXX: As of 2020-12-31, Var_Subst doesn't report any errors, therefore # the loop body is expanded as if no error had happened. .for i in 1 2 ${:U3:Z} 4 . warning Should not be reached. .endfor diff --git a/contrib/bmake/unit-tests/directive-for-escape.exp b/contrib/bmake/unit-tests/directive-for-escape.exp index 89a8cbc2e229..59d4c2324f15 100644 --- a/contrib/bmake/unit-tests/directive-for-escape.exp +++ b/contrib/bmake/unit-tests/directive-for-escape.exp @@ -1,75 +1,75 @@ For: end for 1 For: loop body: . info ${:U!"#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} -make: Unclosed variable specification (expecting '}') for "" (value "!"") modifier U +make: Unclosed variable expression, expecting '}' for modifier "U!"" of variable "" with value "!"" make: "directive-for-escape.mk" line 19: !" For: end for 1 For: loop body: . info ${:U!"\\\\#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} -make: Unclosed variable specification (expecting '}') for "" (value "!"\\") modifier U +make: Unclosed variable expression, expecting '}' for modifier "U!"\\\\" of variable "" with value "!"\\" make: "directive-for-escape.mk" line 29: !"\\ For: end for 1 For: loop body: . info ${:U\$} make: "directive-for-escape.mk" line 41: $ For: loop body: . info ${:U${V}} make: "directive-for-escape.mk" line 41: value For: loop body: . info ${:U${V:=-with-modifier}} make: "directive-for-escape.mk" line 41: value-with-modifier For: loop body: . info ${:U$(V)} make: "directive-for-escape.mk" line 41: value For: loop body: . info ${:U$(V:=-with-modifier)} make: "directive-for-escape.mk" line 41: value-with-modifier For: end for 1 For: loop body: . info ${:U\${UNDEF\:U\\$\\$} make: "directive-for-escape.mk" line 55: ${UNDEF:U\$ For: loop body: . info ${:U{{\}\}} make: "directive-for-escape.mk" line 55: {{}} For: loop body: . info ${:Uend\}} make: "directive-for-escape.mk" line 55: end} For: end for 1 For: loop body: . info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end} -make: "directive-for-escape.mk" line 66: beginend +make: "directive-for-escape.mk" line 67: beginend For: end for 1 For: loop body: . info ${:U\$} -make: "directive-for-escape.mk" line 74: $ +make: "directive-for-escape.mk" line 75: $ For: end for 1 For: loop body: . info ${NUMBERS} ${:Ureplaced} -make: "directive-for-escape.mk" line 82: one two three replaced +make: "directive-for-escape.mk" line 83: one two three replaced For: end for 1 For: loop body: . info ${:Ureplaced} -make: "directive-for-escape.mk" line 92: replaced +make: "directive-for-escape.mk" line 93: replaced For: end for 1 For: loop body: . info . $$i: ${:Uinner} . info . $${i}: ${:Uinner} . info . $${i:M*}: ${:Uinner:M*} . info . $$(i): $(:Uinner) . info . $$(i:M*): $(:Uinner:M*) . info . $${i$${:U}}: ${i${:U}} . info . $${i\}}: ${:Uinner\}} # XXX: unclear why ForLoop_SubstVarLong needs this . info . $${i2}: ${i2} . info . $${i,}: ${i,} . info . adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner} -make: "directive-for-escape.mk" line 100: . $i: inner -make: "directive-for-escape.mk" line 101: . ${i}: inner -make: "directive-for-escape.mk" line 102: . ${i:M*}: inner -make: "directive-for-escape.mk" line 103: . $(i): inner -make: "directive-for-escape.mk" line 104: . $(i:M*): inner -make: "directive-for-escape.mk" line 105: . ${i${:U}}: outer -make: "directive-for-escape.mk" line 106: . ${i\}}: inner} -make: "directive-for-escape.mk" line 107: . ${i2}: two -make: "directive-for-escape.mk" line 108: . ${i,}: comma -make: "directive-for-escape.mk" line 109: . adjacent: innerinnerinnerinner +make: "directive-for-escape.mk" line 101: . $i: inner +make: "directive-for-escape.mk" line 102: . ${i}: inner +make: "directive-for-escape.mk" line 103: . ${i:M*}: inner +make: "directive-for-escape.mk" line 104: . $(i): inner +make: "directive-for-escape.mk" line 105: . $(i:M*): inner +make: "directive-for-escape.mk" line 106: . ${i${:U}}: outer +make: "directive-for-escape.mk" line 107: . ${i\}}: inner} +make: "directive-for-escape.mk" line 108: . ${i2}: two +make: "directive-for-escape.mk" line 109: . ${i,}: comma +make: "directive-for-escape.mk" line 110: . adjacent: innerinnerinnerinner exit status 0 diff --git a/contrib/bmake/unit-tests/directive-for-escape.mk b/contrib/bmake/unit-tests/directive-for-escape.mk index d61f05cc53cc..babc4b8c6e88 100644 --- a/contrib/bmake/unit-tests/directive-for-escape.mk +++ b/contrib/bmake/unit-tests/directive-for-escape.mk @@ -1,112 +1,113 @@ -# $NetBSD: directive-for-escape.mk,v 1.6 2021/01/25 19:05:39 rillig Exp $ +# $NetBSD: directive-for-escape.mk,v 1.7 2021/02/15 07:58:19 rillig Exp $ # # Test escaping of special characters in the iteration values of a .for loop. # These values get expanded later using the :U variable modifier, and this # escaping and unescaping must pass all characters and strings effectively # unmodified. .MAKEFLAGS: -df -# Even though the .for loops takes quotes into account when splitting the -# string into words, the quotes don't need to be balances, as of 2020-12-31. +# Even though the .for loops take quotes into account when splitting the +# string into words, the quotes don't need to be balanced, as of 2020-12-31. # This could be considered a bug. ASCII= !"\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ # XXX: As of 2020-12-31, the '#' is not preserved in the expanded body of # the loop since it would not need only the escaping for the :U variable # modifier but also the escaping for the line-end comment. .for chars in ${ASCII} . info ${chars} .endfor # As of 2020-12-31, using 2 backslashes before be '#' would treat the '#' # as comment character. Using 3 backslashes doesn't help either since # then the situation is essentially the same as with 1 backslash. # This means that a '#' sign cannot be passed in the value of a .for loop # at all. ASCII.2020-12-31= !"\\\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ .for chars in ${ASCII.2020-12-31} . info ${chars} .endfor # Cover the code in for_var_len. # # XXX: It is unexpected that the variable V gets expanded in the loop body. # The double '$$' should prevent exactly this. Probably nobody was -# adventurous enough to use literal dollar signs in the values for a .for +# adventurous enough to use literal dollar signs in the values of a .for # loop. V= value VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier) .for i in ${VALUES} . info $i .endfor # Try to cover the code for nested '{}' in for_var_len, without success. # -# The value of VALUES is not meant to be a variable expression. Instead, it -# is meant to represent dollar, lbrace, "UNDEF:U", backslash, dollar, -# backslash, dollar, space, nested braces, space, "end}". +# The value of the variable VALUES is not meant to be a variable expression. +# Instead, it is meant to represent literal text, the only escaping mechanism +# being that each '$' is written as '$$'. # # The .for loop splits ${VALUES} into 3 words, at the space characters, since # these are not escaped. VALUES= $${UNDEF:U\$$\$$ {{}} end} -# XXX: Where does the '\$$\$$' get converted into a single '\$'? +# XXX: Where in the code does the '\$\$' get converted into a single '\$'? .for i in ${VALUES} . info $i .endfor # Second try to cover the code for nested '{}' in for_var_len. # # XXX: It is wrong that for_var_len requires the braces to be balanced. # Each variable modifier has its own inconsistent way of parsing nested -# variable expressions, braces and parentheses. The only sensible thing -# to do is therefore to let Var_Parse do all the parsing work. +# variable expressions, braces and parentheses. (Compare ':M', ':S', and +# ':D' for details.) The only sensible thing to do is therefore to let +# Var_Parse do all the parsing work. VALUES= begin<$${UNDEF:Ufallback:N{{{}}}}>end .for i in ${VALUES} . info $i .endfor # A single trailing dollar doesn't happen in practice. # The dollar sign is correctly passed through to the body of the .for loop. # There, it is expanded by the .info directive, but even there a trailing # dollar sign is kept as-is. .for i in ${:U\$} . info ${i} .endfor # As of 2020-12-31, the name of the iteration variable can even contain # colons, which then affects variable expressions having this exact modifier. # This is clearly an unintended side effect of the implementation. NUMBERS= one two three .for NUMBERS:M*e in replaced . info ${NUMBERS} ${NUMBERS:M*e} .endfor # As of 2020-12-31, the name of the iteration variable can contain braces, # which gets even more surprising than colons, since it allows to replace # sequences of variable expressions. There is no practical use case for # this, though. BASENAME= one EXT= .c .for BASENAME}${EXT in replaced . info ${BASENAME}${EXT} .endfor # Demonstrate the various ways to refer to the iteration variable. i= outer i2= two i,= comma .for i in inner . info . $$i: $i . info . $${i}: ${i} . info . $${i:M*}: ${i:M*} . info . $$(i): $(i) . info . $$(i:M*): $(i:M*) . info . $${i$${:U}}: ${i${:U}} . info . $${i\}}: ${i\}} # XXX: unclear why ForLoop_SubstVarLong needs this . info . $${i2}: ${i2} . info . $${i,}: ${i,} . info . adjacent: $i${i}${i:M*}$i .endfor all: diff --git a/contrib/bmake/unit-tests/directive-for.exp b/contrib/bmake/unit-tests/directive-for.exp index bdaf4492baf0..4e882aad7b68 100755 --- a/contrib/bmake/unit-tests/directive-for.exp +++ b/contrib/bmake/unit-tests/directive-for.exp @@ -1,24 +1,24 @@ 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\" -make: "directive-for.mk" line 154: Unknown modifier 'Z' +make: "directive-for.mk" line 154: Unknown modifier "Z" make: "directive-for.mk" line 155: XXX: Not reached word1 make: "directive-for.mk" line 155: XXX: Not reached word3 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-undef.exp b/contrib/bmake/unit-tests/directive-undef.exp index d64cb8b5afe0..56c871429397 100644 --- a/contrib/bmake/unit-tests/directive-undef.exp +++ b/contrib/bmake/unit-tests/directive-undef.exp @@ -1,5 +1,6 @@ make: "directive-undef.mk" line 29: The .undef directive requires an argument -make: "directive-undef.mk" line 86: Unknown modifier 'Z' +make: "directive-undef.mk" line 86: Unknown modifier "Z" +make: "directive-undef.mk" line 103: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-undef.mk b/contrib/bmake/unit-tests/directive-undef.mk index b9a69f733517..41ea6b5bf8fa 100644 --- a/contrib/bmake/unit-tests/directive-undef.mk +++ b/contrib/bmake/unit-tests/directive-undef.mk @@ -1,90 +1,107 @@ -# $NetBSD: directive-undef.mk,v 1.9 2020/12/22 20:10:21 rillig Exp $ +# $NetBSD: directive-undef.mk,v 1.10 2021/02/16 18:02:19 rillig Exp $ # # Tests for the .undef directive. # # See also: # directive-misspellings.mk # Before var.c 1.737 from 2020-12-19, .undef only undefined the first # variable, silently skipping all further variable names. # # Before var.c 1.761 from 2020-12-22, .undef complained about too many # arguments. # # Since var.c 1.761 from 2020-12-22, .undef handles multiple variable names # just like the .export directive. 1= 1 2= 2 3= 3 .undef 1 2 3 .if ${1:U_}${2:U_}${3:U_} != ___ . warning $1$2$3 .endif # Without any arguments, until var.c 1.736 from 2020-12-19, .undef tried # to delete the variable with the empty name, which never exists; see # varname-empty.mk. Since var.c 1.737 from 2020-12-19, .undef complains # about a missing argument. .undef # Trying to delete the variable with the empty name is ok, it just won't # ever do anything since that variable is never defined. .undef ${:U} # The argument of .undef is first expanded exactly once and then split into # words, just like everywhere else. This prevents variables whose names # contain spaces or unbalanced 'single' or "double" quotes from being # undefined, but these characters do not appear in variables names anyway. 1= 1 2= 2 3= 3 ${:U1 2 3}= one two three VARNAMES= 1 2 3 .undef ${VARNAMES} # undefines the variable "1 2 3" .if !defined(${:U1 2 3}) . error .endif .if ${1:U_}${2:U_}${3:U_} != "___" # these are still defined . error .endif # A variable named " " cannot be undefined. There's no practical use case # for such variables anyway. SPACE= ${:U } ${SPACE}= space .if !defined(${SPACE}) . error .endif .undef ${SPACE} .if !defined(${SPACE}) . error .endif # A variable named "$" can be undefined since the argument to .undef is # expanded exactly once, before being split into words. DOLLAR= $$ ${DOLLAR}= dollar .if !defined(${DOLLAR}) . error .endif .undef ${DOLLAR} .if defined(${DOLLAR}) . error .endif # Since var.c 1.762 from 2020-12-22, parse errors in the argument should be # properly detected and should stop the .undef directive from doing any work. # # As of var.c 1.762, this doesn't happen though because the error handling # in Var_Parse and Var_Subst is not done properly. .undef ${VARNAMES:L:Z} +UT_EXPORTED= exported-value +.export UT_EXPORTED +.if ${:!echo "\${UT_EXPORTED:-not-exported}"!} != "exported-value" +. error +.endif +.if !${.MAKE.EXPORTED:MUT_EXPORTED} +. error +.endif +.undef UT_EXPORTED # XXX: does not update .MAKE.EXPORTED +.if ${:!echo "\${UT_EXPORTED:-not-exported}"!} != "not-exported" +. error +.endif +.if ${.MAKE.EXPORTED:MUT_EXPORTED} +. warning UT_EXPORTED is still listed in .MAKE.EXPORTED even though $\ + it is not exported anymore. +.endif + + all: - @:; diff --git a/contrib/bmake/unit-tests/directive-unexport-env.exp b/contrib/bmake/unit-tests/directive-unexport-env.exp index 677596ea4aa8..6d653e65fd32 100644 --- a/contrib/bmake/unit-tests/directive-unexport-env.exp +++ b/contrib/bmake/unit-tests/directive-unexport-env.exp @@ -1,18 +1,18 @@ make: "directive-unexport-env.mk" line 13: Unknown directive "unexport-en" make: "directive-unexport-env.mk" line 15: Unknown directive "unexport-environment" -Global:UT_EXPORTED = value -Global:UT_UNEXPORTED = value -Global:.MAKE.EXPORTED = UT_EXPORTED +Global: UT_EXPORTED = value +Global: UT_UNEXPORTED = value +Global: .MAKE.EXPORTED = UT_EXPORTED make: "directive-unexport-env.mk" line 21: The directive .unexport-env does not take arguments -Var_Parse: ${.MAKE.EXPORTED:O:u} with VARE_WANTRES -Applying ${.MAKE.EXPORTED:O} to "UT_EXPORTED" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:O} is "UT_EXPORTED" (VARE_WANTRES, none, none) -Applying ${.MAKE.EXPORTED:u} to "UT_EXPORTED" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:u} is "UT_EXPORTED" (VARE_WANTRES, none, none) +Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) +Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_EXPORTED" +Result of ${.MAKE.EXPORTED:O} is "UT_EXPORTED" +Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_EXPORTED" +Result of ${.MAKE.EXPORTED:u} is "UT_EXPORTED" Unexporting "UT_EXPORTED" Global:delete .MAKE.EXPORTED -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +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 diff --git a/contrib/bmake/unit-tests/directive.exp b/contrib/bmake/unit-tests/directive.exp index b93d768169ab..ee866b7ee2b3 100644 --- a/contrib/bmake/unit-tests/directive.exp +++ b/contrib/bmake/unit-tests/directive.exp @@ -1,12 +1,12 @@ 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 +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 +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 diff --git a/contrib/bmake/unit-tests/include-main.exp b/contrib/bmake/unit-tests/include-main.exp index 61e716ad8ad7..c8a670a1c14a 100644 --- a/contrib/bmake/unit-tests/include-main.exp +++ b/contrib/bmake/unit-tests/include-main.exp @@ -1,17 +1,17 @@ make: "include-main.mk" line 14: main-before-ok make: "include-main.mk" line 21: main-before-for-ok make: "include-sub.mk" line 4: sub-before-ok make: "include-sub.mk" line 14: sub-before-for-ok ParseReadLine (5): '. info subsub-ok' make: "include-subsub.mk" line 5: subsub-ok in .for loop from include-sub.mk:31 in .for loop from include-sub.mk:30 in .for loop from include-sub.mk:29 in .include from include-main.mk:27 ParseReadLine (6): '.MAKEFLAGS: -d0' -ParseDoDependency(.MAKEFLAGS: -d0) +ParseDependency(.MAKEFLAGS: -d0) make: "include-sub.mk" line 38: sub-after-ok make: "include-sub.mk" line 45: sub-after-for-ok make: "include-main.mk" line 30: main-after-ok make: "include-main.mk" line 37: main-after-for-ok exit status 0 diff --git a/contrib/bmake/unit-tests/job-output-null.exp b/contrib/bmake/unit-tests/job-output-null.exp new file mode 100644 index 000000000000..af9b4e64dba3 --- /dev/null +++ b/contrib/bmake/unit-tests/job-output-null.exp @@ -0,0 +1,4 @@ +hello +hello +hello world without newline, hello world without newline, hello world without newline. +exit status 0 diff --git a/contrib/bmake/unit-tests/job-output-null.mk b/contrib/bmake/unit-tests/job-output-null.mk new file mode 100644 index 000000000000..7620bdf6a7ba --- /dev/null +++ b/contrib/bmake/unit-tests/job-output-null.mk @@ -0,0 +1,32 @@ +# $NetBSD: job-output-null.mk,v 1.1 2021/04/15 19:02:29 rillig Exp $ +# +# Test how null bytes in the output of a command are handled. Make processes +# them using null-terminated strings, which may cut off some of the output. +# +# As of 2021-04-15, make handles null bytes from the child process +# inconsistently. It's an edge case though since typically the child +# processes output text. + +.MAKEFLAGS: -j1 # force jobs mode + +all: .PHONY + # The null byte from the command output is kept as-is. + # See CollectOutput, which looks like it intended to replace these + # null bytes with simple spaces. + @printf 'hello\0world%s\n' '' + + # Give the parent process a chance to see the above output, but not + # yet the output from the next printf command. + @sleep 1 + + # All null bytes from the command output are kept as-is. + @printf 'hello\0world%s\n' '' '' '' '' '' '' + + @sleep 1 + + # The null bytes are replaced with spaces since they are not followed + # by a newline. + # + # The three null bytes in a row test whether this output is + # compressed to a single space like in DebugFailedTarget. It isn't. + @printf 'hello\0world\0without\0\0\0newline%s' ', ' ', ' '.' diff --git a/contrib/bmake/unit-tests/jobs-empty-commands-error.exp b/contrib/bmake/unit-tests/jobs-empty-commands-error.exp new file mode 100644 index 000000000000..1639425d9013 --- /dev/null +++ b/contrib/bmake/unit-tests/jobs-empty-commands-error.exp @@ -0,0 +1,5 @@ +: 'Making existing-target out of nothing.' +make: don't know how to make nonexistent-target (continuing) + +make: stopped in unit-tests +exit status 2 diff --git a/contrib/bmake/unit-tests/jobs-empty-commands-error.mk b/contrib/bmake/unit-tests/jobs-empty-commands-error.mk new file mode 100644 index 000000000000..b9ba4403078e --- /dev/null +++ b/contrib/bmake/unit-tests/jobs-empty-commands-error.mk @@ -0,0 +1,19 @@ +# $NetBSD: jobs-empty-commands-error.mk,v 1.1 2021/06/16 09:39:48 rillig Exp $ +# +# In jobs mode, the shell commands for creating a target are written to a +# temporary file first, which is then run by the shell. In chains of +# dependencies, these files would end up empty. Since job.c 1.399 from +# 2021-01-29, these empty files are no longer created. +# +# After 2021-01-29, before job.c 1.435 2021-06-16, targets that could not be +# made led to longer error messages than necessary. + +.MAKEFLAGS: -j1 + +all: existing-target + +existing-target: + : 'Making $@ out of nothing.' + +all: nonexistent-target + : 'Not reached' diff --git a/contrib/bmake/unit-tests/moderrs.exp b/contrib/bmake/unit-tests/moderrs.exp index 0ca1aa2aedd5..9d8bd308c36c 100644 --- a/contrib/bmake/unit-tests/moderrs.exp +++ b/contrib/bmake/unit-tests/moderrs.exp @@ -1,144 +1,137 @@ mod-unknown-direct: want: Unknown modifier 'Z' -make: Unknown modifier 'Z' +make: Unknown modifier "Z" VAR:Z=before--after mod-unknown-indirect: want: Unknown modifier 'Z' -make: Unknown modifier 'Z' +make: Unknown modifier "Z" VAR:Z=before-inner}-after unclosed-direct: -want: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S -make: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S +want: Unclosed variable expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable" +make: Unclosed variable expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable" VAR:S,V,v,=Thevariable unclosed-indirect: -want: Unclosed variable specification after complex modifier (expecting '}') for VAR -make: Unclosed variable specification after complex modifier (expecting '}') for VAR +want: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR" +make: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR" VAR:S,V,v,=Thevariable unfinished-indirect: want: Unfinished modifier for VAR (',' missing) -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) VAR:S,V,v= unfinished-loop: want: Unfinished modifier for UNDEF ('@' missing) -make: Unfinished modifier for UNDEF ('@' missing) +make: Unfinished modifier for "UNDEF" ('@' missing) want: Unfinished modifier for UNDEF ('@' missing) -make: Unfinished modifier for UNDEF ('@' missing) +make: Unfinished modifier for "UNDEF" ('@' missing) 1 2 3 loop-close: -make: Unclosed variable specification (expecting '}') for "UNDEF" (value "1}... 2}... 3}...") modifier @ +make: Unclosed variable expression, expecting '}' for modifier "@var@${var}}...@" of variable "UNDEF" with value "1}... 2}... 3}..." 1}... 2}... 3}... 1}... 2}... 3}... words: want: Unfinished modifier for UNDEF (']' missing) -make: Unfinished modifier for UNDEF (']' missing) +make: Unfinished modifier for "UNDEF" (']' missing) want: Unfinished modifier for UNDEF (']' missing) -make: Unfinished modifier for UNDEF (']' missing) +make: Unfinished modifier for "UNDEF" (']' missing) 13= -make: Bad modifier `:[123451234512345123451234512345]' for UNDEF +make: Bad modifier ":[123451234512345123451234512345]" for variable "UNDEF" 12345=S,^ok,:S,^3ok,} exclam: want: Unfinished modifier for VARNAME ('!' missing) -make: Unfinished modifier for VARNAME ('!' missing) +make: Unfinished modifier for "VARNAME" ('!' missing) want: Unfinished modifier for ! ('!' missing) -make: Unfinished modifier for ! ('!' missing) +make: Unfinished modifier for "!" ('!' missing) mod-subst-delimiter: -make: Missing delimiter for :S modifier +make: Missing delimiter for modifier ':S' 1: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 2: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 3: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 4: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 5: -make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier S +make: Unclosed variable expression, expecting '}' for modifier "S,from,to," of variable "VAR" with value "TheVariable" 6: TheVariable 7: TheVariable mod-regex-delimiter: make: Missing delimiter for :C modifier 1: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 2: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 3: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 4: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 5: -make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier C +make: Unclosed variable expression, expecting '}' for modifier "C,from,to," of variable "VAR" with value "TheVariable" 6: TheVariable 7: TheVariable -mod-regex-undefined-subexpression: -one one 2 3 5 8 one3 2one 34 -make: No match for subexpression \2 -make: No match for subexpression \2 -make: No match for subexpression \1 -make: No match for subexpression \2 -make: No match for subexpression \1 -()+() ()+() ()+() 3 5 8 (3)+() ()+(1) 34 - mod-ts-parse: 112358132134 15152535558513521534 -make: Bad modifier `:ts\65oct' for FIB +make: Bad modifier ":ts\65oct" for variable "FIB" +65oct} +make: Bad modifier ":ts\65oct" for variable "" 65oct} -make: Bad modifier `:tsxy' for FIB +make: Bad modifier ":tsxy" for variable "FIB" xy} mod-t-parse: -make: Bad modifier `:t' for FIB +make: Bad modifier ":t" for variable "FIB" -make: Bad modifier `:txy' for FIB +make: Bad modifier ":txy" for variable "FIB" y} -make: Bad modifier `:t' for FIB +make: Bad modifier ":t" for variable "FIB" -make: Bad modifier `:t' for FIB +make: Bad modifier ":t" for variable "FIB" M*} mod-ifelse-parse: -make: Unfinished modifier for FIB (':' missing) +make: Unfinished modifier for "FIB" (':' missing) -make: Unfinished modifier for FIB (':' missing) +make: Unfinished modifier for "FIB" (':' missing) -make: Unfinished modifier for FIB ('}' missing) +make: Unfinished modifier for "FIB" ('}' missing) -make: Unfinished modifier for FIB ('}' missing) +make: Unfinished modifier for "FIB" ('}' missing) then mod-remember-parse: 1 1 2 3 5 8 13 21 34 -make: Unknown modifier '_' +make: Unknown modifier "__" mod-sysv-parse: -make: Unknown modifier '3' -make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3 +make: Unknown modifier "3" +make: Unclosed variable expression, expecting '}' for modifier "3" of variable "FIB" with value "" -make: Unknown modifier '3' -make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3 +make: Unknown modifier "3=" +make: Unclosed variable expression, expecting '}' for modifier "3=" of variable "FIB" with value "" -make: Unknown modifier '3' -make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3 +make: Unknown modifier "3=x3" +make: Unclosed variable expression, expecting '}' for modifier "3=x3" of variable "FIB" with value "" 1 1 2 x3 5 8 1x3 21 34 exit status 0 diff --git a/contrib/bmake/unit-tests/moderrs.mk b/contrib/bmake/unit-tests/moderrs.mk index 8fdcb496ee29..ffd920314c5d 100644 --- a/contrib/bmake/unit-tests/moderrs.mk +++ b/contrib/bmake/unit-tests/moderrs.mk @@ -1,171 +1,154 @@ -# $NetBSD: moderrs.mk,v 1.25 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: moderrs.mk,v 1.30 2021/06/21 08:28:37 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 'want: Unclosed variable expression, expecting $'}$' for modifier "S,V,v," of variable "VAR" with value "Thevariable"' @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 'want: Unclosed variable expression after indirect modifier, expecting $'}$' for variable "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 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 ${:U${FIB}:ts\65oct} # bad modifier, variable name is "" @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 diff --git a/contrib/bmake/unit-tests/modts.exp b/contrib/bmake/unit-tests/modts.exp index 5db79fc96586..18837016add4 100644 --- a/contrib/bmake/unit-tests/modts.exp +++ b/contrib/bmake/unit-tests/modts.exp @@ -1,14 +1,14 @@ -make: Bad modifier `:tx' for LIST +make: Bad modifier ":tx" for variable "LIST" LIST:tx="}" -make: Bad modifier `:ts\X' for LIST +make: Bad modifier ":ts\X" for variable "LIST" LIST:ts/x:tu="\X:tu}" FU_mod-ts="a/b/cool" FU_mod-ts:ts:T="cool" == cool? B.${AAA:ts}="Baaa" == Baaa? :ts :S => aaxBbxaaxbbxaaxbb :ts :S space => axa a axc :ts :S space :M => axaxaxaxc :ts :S => axa a axc :ts :S :@ => axa a axc :ts :S :@ :M => axaxaxaxc exit status 0 diff --git a/contrib/bmake/unit-tests/modword.exp b/contrib/bmake/unit-tests/modword.exp index 9fd7f1b494fe..02e9974c02d6 100644 --- a/contrib/bmake/unit-tests/modword.exp +++ b/contrib/bmake/unit-tests/modword.exp @@ -1,124 +1,126 @@ -make: Bad modifier `:[]' for LIST +make: Bad modifier ":[]" for variable "LIST" LIST:[]="" is an error LIST:[0]="one two three four five six" LIST:[0x0]="one two three four five six" LIST:[000]="one two three four five six" LIST:[*]="one two three four five six" LIST:[@]="one two three four five six" LIST:[0]:C/ /,/="one,two three four five six" LIST:[0]:C/ /,/g="one,two,three,four,five,six" LIST:[0]:C/ /,/1g="one,two,three,four,five,six" LIST:[*]:C/ /,/="one,two three four five six" LIST:[*]:C/ /,/g="one,two,three,four,five,six" LIST:[*]:C/ /,/1g="one,two,three,four,five,six" LIST:[@]:C/ /,/="one two three four five six" LIST:[@]:C/ /,/g="one two three four five six" LIST:[@]:C/ /,/1g="one two three four five six" LIST:[@]:[0]:C/ /,/="one,two three four five six" LIST:[0]:[@]:C/ /,/="one two three four five six" LIST:[@]:[*]:C/ /,/="one,two three four five six" LIST:[*]:[@]:C/ /,/="one two three four five six" EMPTY="" EMPTY:[#]="1" == 1 ? ESCAPEDSPACE="\ " ESCAPEDSPACE:[#]="1" == 1 ? REALLYSPACE=" " REALLYSPACE:[#]="1" == 1 ? LIST:[#]="6" LIST:[0]:[#]="1" == 1 ? LIST:[*]:[#]="1" == 1 ? LIST:[@]:[#]="6" LIST:[1]:[#]="1" LIST:[1..3]:[#]="3" EMPTY:[1]="" ESCAPEDSPACE="\ " ESCAPEDSPACE:[1]="\ " REALLYSPACE=" " REALLYSPACE:[1]="" == "" ? REALLYSPACE:[*]:[1]=" " == " " ? LIST:[1]="one" -make: Bad modifier `:[1.]' for LIST +make: Bad modifier ":[1.]" for variable "LIST" LIST:[1.]="" is an error -make: Bad modifier `:[1].' for LIST +make: Bad modifier ":[1]." for variable "LIST" LIST:[1].="}" is an error LIST:[2]="two" LIST:[6]="six" LIST:[7]="" LIST:[999]="" -make: Bad modifier `:[-]' for LIST +make: Bad modifier ":[-]" for variable "LIST" LIST:[-]="" is an error -make: Bad modifier `:[--]' for LIST +make: Bad modifier ":[--]" for variable "LIST" LIST:[--]="" is an error LIST:[-1]="six" LIST:[-2]="five" LIST:[-6]="one" LIST:[-7]="" LIST:[-999]="" LONGLIST:[17]="17" LONGLIST:[0x11]="17" LONGLIST:[021]="17" LIST:[0]:[1]="one two three four five six" LIST:[*]:[1]="one two three four five six" LIST:[@]:[1]="one" LIST:[0]:[2]="" LIST:[*]:[2]="" LIST:[@]:[2]="two" LIST:[*]:C/ /,/:[2]="" LIST:[*]:C/ /,/:[*]:[2]="" LIST:[*]:C/ /,/:[@]:[2]="three" LONGLIST:[012..0x12]="10 11 12 13 14 15 16 17 18" -make: Bad modifier `:[1.]' for LIST +make: Bad modifier ":[1.]" for variable "LIST" LIST:[1.]="" is an error -make: Bad modifier `:[1..]' for LIST +make: Bad modifier ":[1..]" for variable "LIST" LIST:[1..]="" is an error +make: Bad modifier ":[1.. ]" for variable "LIST" +LIST:[1.. ]="" is an error LIST:[1..1]="one" -make: Bad modifier `:[1..1.]' for LIST +make: Bad modifier ":[1..1.]" for variable "LIST" LIST:[1..1.]="" is an error LIST:[1..2]="one two" LIST:[2..1]="two one" LIST:[3..-2]="three four five" LIST:[-4..4]="three four" -make: Bad modifier `:[0..1]' for LIST +make: Bad modifier ":[0..1]" for variable "LIST" LIST:[0..1]="" is an error -make: Bad modifier `:[-1..0]' for LIST +make: Bad modifier ":[-1..0]" for variable "LIST" LIST:[-1..0]="" is an error LIST:[-1..1]="six five four three two one" LIST:[0..0]="one two three four five six" LIST:[3..99]="three four five six" LIST:[-3..-99]="four three two one" LIST:[-99..-3]="one two three four" HASH="#" == "#" ? LIST:[${HASH}]="6" LIST:[${ZERO}]="one two three four five six" LIST:[${ZERO}x${ONE}]="one" LIST:[${ONE}]="one" LIST:[${MINUSONE}]="six" LIST:[${STAR}]="one two three four five six" LIST:[${AT}]="one two three four five six" -make: Bad modifier `:[${EMPTY' for LIST +make: Bad modifier ":[${EMPTY" for variable "LIST" LIST:[${EMPTY}]="" is an error LIST:[${LONGLIST:[21]:S/2//}]="one" LIST:[${LIST:[#]}]="six" LIST:[${LIST:[${HASH}]}]="six" LIST:[ -1.. +3]="six five four three" LIST:S/ /,/="one two three four five six" LIST:S/ /,/W="one,two three four five six" LIST:S/ /,/gW="one,two,three,four,five,six" EMPTY:S/^/,/="," EMPTY:S/^/,/W="," LIST:C/ /,/="one two three four five six" LIST:C/ /,/W="one,two three four five six" LIST:C/ /,/gW="one,two,three,four,five,six" EMPTY:C/^/,/="," EMPTY:C/^/,/W="," LIST:tW="one two three four five six" LIST:tw="one two three four five six" LIST:tW:C/ /,/="one,two three four five six" LIST:tW:C/ /,/g="one,two,three,four,five,six" LIST:tW:C/ /,/1g="one,two,three,four,five,six" LIST:tw:C/ /,/="one two three four five six" LIST:tw:C/ /,/g="one two three four five six" LIST:tw:C/ /,/1g="one two three four five six" LIST:tw:tW:C/ /,/="one,two three four five six" LIST:tW:tw:C/ /,/="one two three four five six" exit status 0 diff --git a/contrib/bmake/unit-tests/modword.mk b/contrib/bmake/unit-tests/modword.mk index 383c9dca975b..95bb1fec78c3 100644 --- a/contrib/bmake/unit-tests/modword.mk +++ b/contrib/bmake/unit-tests/modword.mk @@ -1,160 +1,161 @@ -# $NetBSD: modword.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: modword.mk,v 1.6 2021/03/14 16:00:07 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.. ]="${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/ /,/}"' diff --git a/contrib/bmake/unit-tests/opt-chdir.mk b/contrib/bmake/unit-tests/opt-chdir.mk index 20241f02740e..a8806149f31c 100644 --- a/contrib/bmake/unit-tests/opt-chdir.mk +++ b/contrib/bmake/unit-tests/opt-chdir.mk @@ -1,27 +1,29 @@ -# $NetBSD: opt-chdir.mk,v 1.5 2020/11/15 05:43:56 sjg Exp $ +# $NetBSD: opt-chdir.mk,v 1.6 2021/05/18 17:05:45 sjg Exp $ # # Tests for the -C command line option, which changes the directory at the # beginning. # # This option has been available since 2009-08-27. .MAKEFLAGS: -d0 # switch stdout to line-buffered 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. +# Note: just because the whole point of /nonexistent is that it should +# not exist - doesn't mean it doesn't. chdir-nonexistent: .PHONY .IGNORE - @${MAKE} -C /nonexistent + @${MAKE} -C /nonexistent.${.MAKE.PID} diff --git a/contrib/bmake/unit-tests/opt-debug-errors-jobs.exp b/contrib/bmake/unit-tests/opt-debug-errors-jobs.exp new file mode 100644 index 000000000000..25eb2b470b72 --- /dev/null +++ b/contrib/bmake/unit-tests/opt-debug-errors-jobs.exp @@ -0,0 +1,48 @@ +echo '3 spaces'; false +3 spaces + +*** Failed target: fail-spaces +*** Failed commands: + echo '3 spaces'; false +*** [fail-spaces] Error code 1 + +make: stopped in unit-tests +echo \ indented; false + indented + +*** Failed target: fail-escaped-space +*** Failed commands: + echo \ indented; false +*** [fail-escaped-space] Error code 1 + +make: stopped in unit-tests +echo 'line1 +line2'; false +line1 +line2 + +*** Failed target: fail-newline +*** Failed commands: + echo 'line1${.newline}line2'; false +*** [fail-newline] Error code 1 + +make: stopped in unit-tests +echo 'line1 line2'; false +line1 line2 + +*** Failed target: fail-multiline +*** Failed commands: + echo 'line1 line2'; false +*** [fail-multiline] Error code 1 + +make: stopped in unit-tests +echo 'word1' 'word2'; false +word1 word2 + +*** Failed target: fail-multiline-intention +*** Failed commands: + echo 'word1' 'word2'; false +*** [fail-multiline-intention] Error code 1 + +make: stopped in unit-tests +exit status 1 diff --git a/contrib/bmake/unit-tests/opt-debug-errors-jobs.mk b/contrib/bmake/unit-tests/opt-debug-errors-jobs.mk new file mode 100644 index 000000000000..83b50987a752 --- /dev/null +++ b/contrib/bmake/unit-tests/opt-debug-errors-jobs.mk @@ -0,0 +1,36 @@ +# $NetBSD: opt-debug-errors-jobs.mk,v 1.1 2021/04/27 16:20:06 rillig Exp $ +# +# Tests for the -de command line option, which adds debug logging for +# failed commands and targets; since 2021-04-27 also in jobs mode. + +.MAKEFLAGS: -de -j1 + +all: fail-spaces +all: fail-escaped-space +all: fail-newline +all: fail-multiline +all: fail-multiline-intention + +fail-spaces: + echo '3 spaces'; false + +fail-escaped-space: + echo \ indented; false + +fail-newline: + echo 'line1${.newline}line2'; false + +# The line continuations in multiline commands are turned into an ordinary +# space before the command is actually run. +fail-multiline: + echo 'line1\ + line2'; false + +# It is a common style to align the continuation backslashes at the right +# of the lines, usually at column 73. All spaces before the continuation +# backslash are preserved and are usually outside a shell word and thus +# irrelevant. Since "usually" is not "always", these space characters are +# not merged into a single space. +fail-multiline-intention: + echo 'word1' \ + 'word2'; false diff --git a/contrib/bmake/unit-tests/opt-debug-lint.exp b/contrib/bmake/unit-tests/opt-debug-lint.exp index f2123f20e37f..05b341b30dae 100644 --- a/contrib/bmake/unit-tests/opt-debug-lint.exp +++ b/contrib/bmake/unit-tests/opt-debug-lint.exp @@ -1,8 +1,8 @@ make: "opt-debug-lint.mk" line 19: Variable "X" is undefined make: "opt-debug-lint.mk" line 41: Variable "UNDEF" is undefined make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "L" make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "P" -make: "opt-debug-lint.mk" line 69: Unknown modifier '$' +make: "opt-debug-lint.mk" line 69: Unknown modifier "${" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/opt-debug-lint.mk b/contrib/bmake/unit-tests/opt-debug-lint.mk index bb1b38feb717..155e1a3de3be 100644 --- a/contrib/bmake/unit-tests/opt-debug-lint.mk +++ b/contrib/bmake/unit-tests/opt-debug-lint.mk @@ -1,81 +1,95 @@ -# $NetBSD: opt-debug-lint.mk,v 1.12 2020/12/20 19:10:53 rillig Exp $ +# $NetBSD: opt-debug-lint.mk,v 1.14 2021/03/14 10:57:12 rillig Exp $ # # Tests for the -dL command line option, which runs additional checks # to catch common mistakes, such as unclosed variable expressions. .MAKEFLAGS: -dL # Since 2020-09-13, undefined variables that are used on the left-hand side # of a condition at parse time get a proper error message. Before, the # error message was "Malformed conditional" only, which was wrong and # misleading. The form of the condition is totally fine, it's the evaluation # that fails. # # Since 2020-09-13, the "Malformed conditional" error message is not printed # anymore. # # See also: # cond-undef-lint.mk .if $X . error .endif # The dynamic variables like .TARGET are treated specially. It does not make # sense to expand them in the global scope since they will never be defined # there under normal circumstances. Therefore they expand to a string that # will later be expanded correctly, when the variable is evaluated again in # the scope of an actual target. # # Even though the "@" variable is not defined at this point, this is not an # error. In all practical cases, this is no problem. This particular test # case is made up and unrealistic. .if $@ != "\$(.TARGET)" . error .endif # Since 2020-09-13, Var_Parse properly reports errors for undefined variables, # but only in lint mode. Before, it had only silently returned var_Error, # hoping for the caller to print an error message. This resulted in the # well-known "Malformed conditional" error message, even though the # conditional was well-formed and the only error was an undefined variable. .if ${UNDEF} . error .endif # Since 2020-09-14, dependency lines may contain undefined variables. # Before, undefined variables were forbidden, but this distinction was not # observable from the outside of the function Var_Parse. ${UNDEF}: ${UNDEF} # In a condition that has a defined(UNDEF) guard, all guarded conditions # may assume that the variable is defined since they will only be evaluated # if the variable is indeed defined. Otherwise they are only parsed, and # for parsing it doesn't make a difference whether the variable is defined # or not. .if defined(UNDEF) && exists(${UNDEF}) . error .endif # Since 2020-10-03, in lint mode the variable modifier must be separated # by colons. See varparse-mod.mk. .if ${value:LPL} != "value" . error .endif # Between 2020-10-03 and var.c 1.752 from 2020-12-20, in lint mode the # variable modifier had to be separated by colons. This was wrong though # since make always fell back trying to parse the indirect modifier as a # SysV modifier. .if ${value:${:UL}PL} != "LPL}" # FIXME: "LPL}" is unexpected here. . error ${value:${:UL}PL} .endif # Typically, an indirect modifier is followed by a colon or the closing # brace. This one isn't, therefore make falls back to parsing it as the SysV # modifier ":lue=lid". .if ${value:L:${:Ulue}=${:Ulid}} != "valid" . error .endif -all: - @:; +# In lint mode, the whole variable text is evaluated to check for unclosed +# expressions and unknown operators. During this check, the subexpression +# '${:U2}' is not expanded, instead it is copied verbatim into the regular +# expression, leading to '.*=.{1,${:U2}}$'. +# +# Before var.c 1.856 from 2021-03-14, this regular expression was then +# compiled even though that was not necessary for checking the syntax at the +# level of variable expressions. The unexpanded '$' then resulted in a wrong +# error message. +# +# This only happened in lint mode since in default mode the early check for +# unclosed expressions and unknown modifiers is skipped. +# +# See VarCheckSyntax, ApplyModifier_Regex. +# +VARMOD_REGEX= ${:UA=111 B=222 C=33:C/.*=.{1,${:U2}}$//g} diff --git a/contrib/bmake/unit-tests/opt-debug.exp b/contrib/bmake/unit-tests/opt-debug.exp index 52a36c71b4ee..6a5f7b4cb3e7 100644 --- a/contrib/bmake/unit-tests/opt-debug.exp +++ b/contrib/bmake/unit-tests/opt-debug.exp @@ -1,4 +1,4 @@ -Global:VAR = value -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +Global: VAR = value +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/opt-file.mk b/contrib/bmake/unit-tests/opt-file.mk index 3ab8ef4e3c7d..b7a1c09e6d16 100644 --- a/contrib/bmake/unit-tests/opt-file.mk +++ b/contrib/bmake/unit-tests/opt-file.mk @@ -1,105 +1,105 @@ -# $NetBSD: opt-file.mk,v 1.11 2020/12/22 08:57:23 rillig Exp $ +# $NetBSD: opt-file.mk,v 1.12 2021/04/04 10:13:09 rillig Exp $ # # Tests for the -f command line option. # TODO: Implementation all: .PHONY all: file-ending-in-backslash all: file-ending-in-backslash-mmap all: line-with-trailing-whitespace all: file-containing-null-byte # Passing '-' as the filename reads from stdin. This is unusual but possible. # # In the unlikely case where a file ends in a backslash instead of a newline, # that backslash is trimmed. See ParseGetLine. # # make-2014.01.01.00.00.00 invoked undefined behavior, reading text from # outside of the file buffer. # # printf '%s' 'VAR=value\' \ # | MALLOC_OPTIONS=JA make-2014.01.01.00.00.00 -r -f - -V VAR -dA 2>&1 \ # | less # # The debug output shows how make happily uses freshly allocated memory (the # ) and already freed memory ('Z'). # # ParseReadLine (1): 'VAR=value\' # Global:VAR = value\value\ # ParseReadLine (2): 'alue\' -# ParseDoDependency(alue\) +# ParseDependency(alue\) # make-2014.01.01.00.00.00: "(stdin)" line 2: Need an operator # ParseReadLine (3): 'ZZZZZZZZZZZZZZZZ' -# ParseDoDependency(ZZZZZZZZZZZZZZZZ) +# ParseDependency(ZZZZZZZZZZZZZZZZ) # file-ending-in-backslash: .PHONY @printf '%s' 'VAR=value\' \ | ${MAKE} -r -f - -V VAR # Between parse.c 1.170 from 2010-12-25 and parse.c 1.511 from 2020-12-22, # there was an out-of-bounds write in ParseGetLine, where line_end pointed at # the end of the allocated buffer, in the special case where loadedfile_mmap # had not added the final newline character. file-ending-in-backslash-mmap: .PHONY @printf '%s' 'VAR=value\' > opt-file-backslash @${MAKE} -r -f opt-file-backslash -V VAR @rm opt-file-backslash # Since parse.c 1.511 from 2020-12-22, an assertion in ParseGetLine failed # for lines that contained trailing whitespace. Worked around in parse.c # 1.513, properly fixed in parse.c 1.514. line-with-trailing-whitespace: .PHONY @printf '%s' 'VAR=$@ ' > opt-file-trailing-whitespace @${MAKE} -r -f opt-file-trailing-whitespace -V VAR @rm opt-file-trailing-whitespace # If a makefile contains null bytes, it is an error. Throughout the history # of make, the behavior has changed several times, sometimes intentionally, # sometimes by accident. # # echo 'VAR=value' | tr 'l' '\0' > zero-byte.in # printf '%s\n' 'all:' ': VAR=${VAR:Q}' >> zero-byte.in # # for year in $(seq 2003 2020); do # echo $year: # make-$year.01.01.00.00.00 -r -f zero-byte.in # echo "exit status $?" # echo # done 2>&1 \ # | sed "s,$PWD/,.," # # This program generated the following output: # # 2003 to 2007: # exit status 0 # # 2008 to 2010: # make: "zero-byte.in" line 1: Zero byte read from file # make: Fatal errors encountered -- cannot continue # # make: stopped in . # exit status 1 # # 2011 to 2013: # make: no target to make. # # make: stopped in . # exit status 2 # # 2014 to 2020-12-06: # make: "zero-byte.in" line 1: warning: Zero byte read from file, skipping rest of line. # exit status 0 # # Since 2020-12-07: # make: "zero-byte.in" line 1: Zero byte read from file # make: Fatal errors encountered -- cannot continue # make: stopped in . # exit status 1 file-containing-null-byte: .PHONY @printf '%s\n' 'VAR=value' 'VAR2=VALUE2' \ | tr 'l' '\0' \ | ${MAKE} -r -f - -V VAR -V VAR2 all: : Making ${.TARGET} diff --git a/contrib/bmake/unit-tests/opt-jobs-no-action.mk b/contrib/bmake/unit-tests/opt-jobs-no-action.mk index a75fc38cf2fa..19d82c5bf4b8 100644 --- a/contrib/bmake/unit-tests/opt-jobs-no-action.mk +++ b/contrib/bmake/unit-tests/opt-jobs-no-action.mk @@ -1,102 +1,102 @@ -# $NetBSD: opt-jobs-no-action.mk,v 1.8 2020/12/10 23:54:41 rillig Exp $ +# $NetBSD: opt-jobs-no-action.mk,v 1.9 2021/04/04 09:58:51 rillig Exp $ # # Tests for the combination of the options -j and -n, which prints the # commands instead of actually running them. # # The format of the output differs from the output of only the -n option, # without the -j. This is because all this code is implemented twice, once # in compat.c and once in job.c. # # See also: # opt-jobs.mk # The corresponding tests without the -n option # opt-no-action-combined.mk # The corresponding tests without the -j option .MAKEFLAGS: -j1 -n # Change the templates for running the commands in jobs mode, to make it # easier to see what actually happens. # # The shell attributes are handled by Job_ParseShell. # The shell attributes 'quiet' and 'echo' don't need a trailing newline, # this is handled by the [0] != '\0' checks in Job_ParseShell. # The '\#' is handled by ParseGetLine. # The '\n' is handled by Str_Words in Job_ParseShell. -# The '$$' is handled by Var_Subst in ParseDependency. +# The '$$' is handled by Var_Subst in ParseDependencyLine. .SHELL: \ name=sh \ path=${.SHELL} \ quiet="\# .echoOff" \ echo="\# .echoOn" \ filter="\# .noPrint\n" \ check="\# .echoTmpl\n""echo \"%s\"\n" \ ignore="\# .runIgnTmpl\n""%s\n" \ errout="\# .runChkTmpl\n""{ %s \n} || exit $$?\n" all: explained combined .ORDER: explained combined # Explain the most basic cases in detail. explained: .PHONY @+echo hide-from-output 'begin explain' # The following command is regular, it is printed twice: # - first using the template shell.echoTmpl, # - then using the template shell.runChkTmpl. false regular # The following command is silent, it is printed once, using the # template shell.runChkTmpl. @: silent # The following command ignores errors, it is printed once, using # the default template for cmdTemplate, which is "%s\n". # XXX: Why is it not printed using shell.echoTmpl as well? # XXX: The '-' should not influence the echoing of the command. -false ignore-errors # The following command ignores the -n command line option, it is # not handled by the Job module but by the Compat module, see the # '!silent' in Compat_RunCommand. +echo run despite the -n option @+echo hide-from-output 'end explain' @+echo hide-from-output # Test all combinations of the 3 RunFlags. # # TODO: Closely inspect the output whether it makes sense. # XXX: silent=no always=no ignerr={no,yes} should be almost the same. # SILENT.no= # none SILENT.yes= @ ALWAYS.no= # none ALWAYS.yes= + IGNERR.no= echo running IGNERR.yes= -echo running; false # combined: combined-begin combined-begin: .PHONY @+echo hide-from-output 'begin combined' @+echo hide-from-output .for silent in no yes . for always in no yes . for ignerr in no yes . for target in combined-silent-${silent}-always-${always}-ignerr-${ignerr} combined: .WAIT ${target} .WAIT ${target}: .PHONY @+echo hide-from-output silent=${silent} always=${always} ignerr=${ignerr} ${SILENT.${silent}}${ALWAYS.${always}}${IGNERR.${ignerr}} @+echo hide-from-output . endfor . endfor . endfor .endfor combined: combined-end combined-end: .PHONY @+echo hide-from-output 'end combined' diff --git a/contrib/bmake/unit-tests/recursive.mk b/contrib/bmake/unit-tests/recursive.mk index 73a8409fe030..5265cec59a2d 100644 --- a/contrib/bmake/unit-tests/recursive.mk +++ b/contrib/bmake/unit-tests/recursive.mk @@ -1,39 +1,38 @@ -# $NetBSD: recursive.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ +# $NetBSD: recursive.mk,v 1.5 2021/03/15 12:15:03 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. +# whether there are unclosed variables. The variable value is therefore +# parsed with VARE_PARSE_ONLY 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} - diff --git a/contrib/bmake/unit-tests/sh-jobs.mk b/contrib/bmake/unit-tests/sh-jobs.mk index e8d4f976109a..de80de56040c 100644 --- a/contrib/bmake/unit-tests/sh-jobs.mk +++ b/contrib/bmake/unit-tests/sh-jobs.mk @@ -1,35 +1,35 @@ -# $NetBSD: sh-jobs.mk,v 1.3 2020/12/11 01:06:10 rillig Exp $ +# $NetBSD: sh-jobs.mk,v 1.4 2021/04/16 16:49:27 rillig Exp $ # # Tests for the "run in jobs mode" part of the "Shell Commands" section # from the manual page. # TODO: Tutorial .MAKEFLAGS: -j1 all: .PHONY comment .WAIT comment-with-followup-line .WAIT no-comment # If a shell command starts with a comment character after stripping the # leading '@', it is run in ignore-errors mode since the default runChkTmpl # would lead to a syntax error in the generated shell file, at least for # bash and dash, but not for NetBSD sh and ksh. # -# See JobPrintCommand, cmdTemplate, runIgnTmpl +# See JobWriteCommand, cmdTemplate, runIgnTmpl comment: .PHONY @# comment # If a shell command starts with a comment character after stripping the # leading '@', it is run in ignore-errors mode. # -# See JobPrintCommand, cmdTemplate, runIgnTmpl +# See JobWriteCommand, cmdTemplate, runIgnTmpl comment-with-followup-line: .PHONY @# comment${.newline}echo '$@: This is printed.'; false @true # Without the comment, the commands are run in the default mode, which checks # the exit status of every makefile line. # -# See JobPrintCommand, cmdTemplate, runChkTmpl +# See JobWriteCommand, cmdTemplate, runChkTmpl no-comment: .PHONY @echo '$@: This is printed.'; false @true diff --git a/contrib/bmake/unit-tests/shell-csh.mk b/contrib/bmake/unit-tests/shell-csh.mk index 99852e33ce16..47313563d22b 100644 --- a/contrib/bmake/unit-tests/shell-csh.mk +++ b/contrib/bmake/unit-tests/shell-csh.mk @@ -1,40 +1,40 @@ -# $NetBSD: shell-csh.mk,v 1.7 2020/12/13 02:09:55 sjg Exp $ +# $NetBSD: shell-csh.mk,v 1.8 2021/04/04 09:58:51 rillig Exp $ # # Tests for using a C shell for running the commands. CSH!= which csh 2> /dev/null || true # The shell path must be an absolute path. # This is only obvious in parallel mode since in compat mode, # simple commands are executed via execve directly. .if ${CSH} != "" .SHELL: name="csh" path="${CSH}" .endif # In parallel mode, the shell->noPrint command is filtered from -# the output, rather naively (in JobOutput). +# the output, rather naively (in PrintOutput). # # Until 2020-10-03, the output in parallel mode was garbled because # the definition of the csh had been wrong since 1993 at least. .MAKEFLAGS: -j1 all: .if ${CSH} != "" # This command is both printed and executed. echo normal # This command is only executed. @echo hidden # This command is both printed and executed. +echo always # This command is both printed and executed. -echo ignore errors # In the C shell, "unset verbose" is set as the noPrint command. # Therefore it is filtered from the output, rather naively. @echo 'They chatted in the sunset verbosely.' .else @sed '$$d' ${MAKEFILE:.mk=.exp} # This is cheated. .endif diff --git a/contrib/bmake/unit-tests/suff-incomplete.exp b/contrib/bmake/unit-tests/suff-incomplete.exp index 23b959d4b4e5..2331436d378e 100644 --- a/contrib/bmake/unit-tests/suff-incomplete.exp +++ b/contrib/bmake/unit-tests/suff-incomplete.exp @@ -1,42 +1,42 @@ ParseReadLine (9): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (11): '.SUFFIXES: .a .b .c' -ParseDoDependency(.SUFFIXES: .a .b .c) +ParseDependency(.SUFFIXES: .a .b .c) Adding suffix ".a" Adding suffix ".b" Adding suffix ".c" ParseReadLine (17): '.a.b:' -ParseDoDependency(.a.b:) +ParseDependency(.a.b:) defining transformation from `.a' to `.b' inserting ".a" (1) at end of list inserting ".b" (2) at end of list ParseReadLine (21): '.a.c: ${.PREFIX}.dependency' deleting incomplete transformation from `.a' to `.b' -ParseDoDependency(.a.c: ${.PREFIX}.dependency) +ParseDependency(.a.c: ${.PREFIX}.dependency) defining transformation from `.a' to `.c' inserting ".a" (1) at end of list inserting ".c" (3) at end of list # LinkSource: added child .a.c - ${.PREFIX}.dependency # .a.c, unmade, type OP_DEPENDS|OP_TRANSFORM, flags none # ${.PREFIX}.dependency, unmade, type none, flags none ParseReadLine (23): '.DEFAULT:' transformation .a.c complete -ParseDoDependency(.DEFAULT:) +ParseDependency(.DEFAULT:) ParseReadLine (24): ' : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default.' transformation .DEFAULT complete Wildcard expanding "all"... SuffFindDeps "all" No known suffix on all. Using .NULL suffix adding suffix rules Wildcard expanding "suff-incomplete.c"...suffix is ".c"... SuffFindDeps "suff-incomplete.c" trying suff-incomplete.a...not there Wildcard expanding "suff-incomplete.c"...suffix is ".c"... : Making suff-incomplete.c from suff-incomplete.c all by default. Wildcard expanding "all"... SuffFindDeps ".END" No known suffix on .END. Using .NULL suffix adding suffix rules Wildcard expanding ".END"... exit status 0 diff --git a/contrib/bmake/unit-tests/suff-main-several.exp b/contrib/bmake/unit-tests/suff-main-several.exp index a494ddc68545..09fa6d63bffa 100644 --- a/contrib/bmake/unit-tests/suff-main-several.exp +++ b/contrib/bmake/unit-tests/suff-main-several.exp @@ -1,141 +1,141 @@ ParseReadLine (8): '.1.2 .1.3 .1.4:' -ParseDoDependency(.1.2 .1.3 .1.4:) +ParseDependency(.1.2 .1.3 .1.4:) Setting main node to ".1.2" ParseReadLine (9): ' : Making ${.TARGET} from ${.IMPSRC}.' ParseReadLine (14): 'next-main:' -ParseDoDependency(next-main:) +ParseDependency(next-main:) ParseReadLine (15): ' : Making ${.TARGET}' ParseReadLine (19): '.SUFFIXES: .1 .2 .3 .4' -ParseDoDependency(.SUFFIXES: .1 .2 .3 .4) +ParseDependency(.SUFFIXES: .1 .2 .3 .4) Adding suffix ".1" Adding suffix ".2" Setting main node from ".1.2" back to null defining transformation from `.1' to `.2' inserting ".1" (1) at end of list inserting ".2" (2) at end of list Setting main node to ".1.3" Adding suffix ".3" Setting main node from ".1.3" back to null defining transformation from `.1' to `.3' inserting ".1" (1) at end of list inserting ".3" (3) at end of list Setting main node to ".1.4" Adding suffix ".4" Setting main node from ".1.4" back to null defining transformation from `.1' to `.4' inserting ".1" (1) at end of list inserting ".4" (4) at end of list Setting main node to "next-main" ParseReadLine (24): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (32): '.SUFFIXES: .4 .3 .2 .1' -ParseDoDependency(.SUFFIXES: .4 .3 .2 .1) +ParseDependency(.SUFFIXES: .4 .3 .2 .1) Adding suffix ".4" Adding suffix ".3" Adding suffix ".2" Adding suffix ".1" ParseReadLine (33): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (34): '.SUFFIXES: .1 .2 .3 .4' -ParseDoDependency(.SUFFIXES: .1 .2 .3 .4) +ParseDependency(.SUFFIXES: .1 .2 .3 .4) Adding suffix ".1" Adding suffix ".2" Adding suffix ".3" Adding suffix ".4" ParseReadLine (35): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (36): '.SUFFIXES: .4 .3 .2 .1' -ParseDoDependency(.SUFFIXES: .4 .3 .2 .1) +ParseDependency(.SUFFIXES: .4 .3 .2 .1) Adding suffix ".4" Adding suffix ".3" Adding suffix ".2" Adding suffix ".1" ParseReadLine (38): 'suff-main-several.1:' -ParseDoDependency(suff-main-several.1:) +ParseDependency(suff-main-several.1:) ParseReadLine (39): ' : Making ${.TARGET} out of nothing.' ParseReadLine (40): 'next-main: suff-main-several.{2,3,4}' -ParseDoDependency(next-main: suff-main-several.{2,3,4}) +ParseDependency(next-main: suff-main-several.{2,3,4}) # LinkSource: added child next-main - suff-main-several.{2,3,4} # next-main, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # suff-main-several.{2,3,4}, unmade, type none, flags none ParseReadLine (42): '.MAKEFLAGS: -d0 -dg1' -ParseDoDependency(.MAKEFLAGS: -d0 -dg1) +ParseDependency(.MAKEFLAGS: -d0 -dg1) #*** Input graph: # .1.2, unmade, type OP_TRANSFORM, flags none # .1.3, unmade, type OP_TRANSFORM, flags none # .1.4, unmade, type OP_TRANSFORM, flags none # next-main, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # suff-main-several.1, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # suff-main-several.{2,3,4}, unmade, type none, flags none # # Files that are only sources: # .1.2 [.1.2] # .1.3 [.1.3] # .1.4 [.1.4] # suff-main-several.{2,3,4} [suff-main-several.{2,3,4}] #*** Global Variables: .ALLTARGETS = .1.2 .1.3 .1.4 next-main suff-main-several.1 suff-main-several.{2,3,4} .CURDIR = .INCLUDES = .LIBS = .MAKE =
.MAKE.DEPENDFILE =
.MAKE.GID =
.MAKE.LEVEL =
.MAKE.MAKEFILES =
.MAKE.MAKEFILE_PREFERENCE =
.MAKE.OS =
.MAKE.PID =
.MAKE.PPID =
.MAKE.UID =
.MAKEFLAGS = -r -k -d mps -d 0 -d g1 .MAKEOVERRIDES = .OBJDIR = .PATH = . .TARGETS = .newline = MACHINE =
MACHINE_ARCH =
MAKE =
MFLAGS = -r -k -d mps -d 0 -d g1 #*** Command-line Variables: .MAKE.LEVEL.ENV = MAKELEVEL #*** Directory Cache: # Stats: 0 hits 2 misses 0 near misses 0 losers (0%) # refs hits directory # 1 0 # 1 0 . #*** Suffixes: # ".4" (num 1, ref 1) # To: # From: # Search Path: # ".3" (num 2, ref 1) # To: # From: # Search Path: # ".2" (num 3, ref 1) # To: # From: # Search Path: # ".1" (num 4, ref 1) # To: # From: # Search Path: #*** Transformations: make: don't know how to make suff-main-several.2 (continuing) make: don't know how to make suff-main-several.3 (continuing) make: don't know how to make suff-main-several.4 (continuing) `next-main' not remade because of errors. Stop. make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/suff-rebuild.exp b/contrib/bmake/unit-tests/suff-rebuild.exp index ccb423a6086a..7ef53ae2e151 100644 --- a/contrib/bmake/unit-tests/suff-rebuild.exp +++ b/contrib/bmake/unit-tests/suff-rebuild.exp @@ -1,73 +1,73 @@ ParseReadLine (10): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (12): '.SUFFIXES: .a .b .c' -ParseDoDependency(.SUFFIXES: .a .b .c) +ParseDependency(.SUFFIXES: .a .b .c) Adding suffix ".a" Adding suffix ".b" Adding suffix ".c" ParseReadLine (14): 'suff-rebuild-example.a:' -ParseDoDependency(suff-rebuild-example.a:) +ParseDependency(suff-rebuild-example.a:) Adding "suff-rebuild-example.a" to all targets. ParseReadLine (15): ' : Making ${.TARGET} out of nothing.' ParseReadLine (17): '.a.b:' -ParseDoDependency(.a.b:) +ParseDependency(.a.b:) defining transformation from `.a' to `.b' inserting ".a" (1) at end of list inserting ".b" (2) at end of list ParseReadLine (18): ' : Making ${.TARGET} from ${.IMPSRC}.' ParseReadLine (19): '.b.c:' transformation .a.b complete -ParseDoDependency(.b.c:) +ParseDependency(.b.c:) defining transformation from `.b' to `.c' inserting ".b" (2) at end of list inserting ".c" (3) at end of list ParseReadLine (20): ' : Making ${.TARGET} from ${.IMPSRC}.' ParseReadLine (21): '.c:' transformation .b.c complete -ParseDoDependency(.c:) +ParseDependency(.c:) defining transformation from `.c' to `' inserting ".c" (3) at end of list inserting "" (0) at end of list ParseReadLine (22): ' : Making ${.TARGET} from ${.IMPSRC}.' ParseReadLine (44): '.SUFFIXES: .c .b .a' transformation .c complete -ParseDoDependency(.SUFFIXES: .c .b .a) +ParseDependency(.SUFFIXES: .c .b .a) Adding ".END" to all targets. Wildcard expanding "all"... SuffFindDeps "all" No known suffix on all. Using .NULL suffix adding suffix rules trying all.c...not there trying all.b...not there trying all.a...not there Wildcard expanding "suff-rebuild-example"... SuffFindDeps "suff-rebuild-example" No known suffix on suff-rebuild-example. Using .NULL suffix adding suffix rules trying suff-rebuild-example.c...not there trying suff-rebuild-example.b...not there trying suff-rebuild-example.a...got it Adding "suff-rebuild-example.b" to all targets. applying .a -> .b to "suff-rebuild-example.b" Adding "suff-rebuild-example.c" to all targets. applying .b -> .c to "suff-rebuild-example.c" applying .c -> to "suff-rebuild-example" suffix is ".c"... suffix is ".b"... suffix is ".a"... SuffFindDeps "suff-rebuild-example.a" suffix is ".a"... : Making suff-rebuild-example.a out of nothing. : Making suff-rebuild-example.b from suff-rebuild-example.a. : Making suff-rebuild-example.c from suff-rebuild-example.b. : Making suff-rebuild-example from suff-rebuild-example.c. Wildcard expanding "all"... SuffFindDeps ".END" No known suffix on .END. Using .NULL suffix adding suffix rules trying .END.c...not there trying .END.b...not there trying .END.a...not there Wildcard expanding ".END"... exit status 0 diff --git a/contrib/bmake/unit-tests/var-class-cmdline.exp b/contrib/bmake/unit-tests/var-class-cmdline.exp index 39a9383953dd..6df2155ca7eb 100644 --- a/contrib/bmake/unit-tests/var-class-cmdline.exp +++ b/contrib/bmake/unit-tests/var-class-cmdline.exp @@ -1 +1,4 @@ +make: "var-class-cmdline.mk" line 67: global +make: "var-class-cmdline.mk" line 76: makeflags +makeflags exit status 0 diff --git a/contrib/bmake/unit-tests/var-class-cmdline.mk b/contrib/bmake/unit-tests/var-class-cmdline.mk index c43b5351c329..679e051bb242 100644 --- a/contrib/bmake/unit-tests/var-class-cmdline.mk +++ b/contrib/bmake/unit-tests/var-class-cmdline.mk @@ -1,8 +1,80 @@ -# $NetBSD: var-class-cmdline.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-class-cmdline.mk,v 1.5 2021/02/23 21:59:31 rillig Exp $ # # Tests for variables specified on the command line. +# +# Variables that are specified on the command line override those from the +# global scope. +# +# For performance reasons, the actual implementation is more complex than the +# above single-sentence rule, in order to avoid unnecessary lookups in scopes, +# which before var.c 1.586 from 2020-10-25 calculated the hash value of the +# variable name once for each lookup. Instead, when looking up the value of +# a variable, the search often starts in the global scope since that is where +# most of the variables are stored. This conflicts with the statement that +# variables from the cmdline scope override global variables, since after the +# common case of finding a variable in the global scope, another lookup would +# be needed in the cmdline scope to ensure that there is no overriding +# variable there. +# +# Instead of this costly lookup scheme, make implements it in a different +# way: +# +# Whenever a global variable is created, this creation is ignored if +# there is a cmdline variable of the same name. +# +# Whenever a cmdline variable is created, any global variable of the +# same name is deleted. +# +# Whenever a global variable is deleted, nothing special happens. +# +# Deleting a cmdline variable is not possible. +# +# These 4 rules provide the guarantee that whenever a global variable exists, +# there cannot be a cmdline variable of the same name. Therefore, after +# finding a variable in the global scope, no additional lookup is needed in +# the cmdline scope. +# +# The above ruleset provides the same guarantees as the simple rule "cmdline +# overrides global". Due to an implementation mistake, the actual behavior +# was not entirely equivalent to the simple rule though. The mistake was +# that when a cmdline variable with '$$' in its name was added, a global +# variable was deleted, but not with the exact same name as the cmdline +# variable. Instead, the name of the global variable was expanded one more +# time than the name of the cmdline variable. For variable names that didn't +# have a '$$' in their name, it was implemented correctly all the time. +# +# The bug was added in var.c 1.183 on 2013-07-16, when Var_Set called +# Var_Delete to delete the global variable. Just two months earlier, in var.c +# 1.174 from 2013-05-18, Var_Delete had started to expand the variable name. +# Together, these two changes made the variable name be expanded twice in a +# row. This bug was fixed in var.c 1.835 from 2021-02-22. +# +# Another bug was the wrong assumption that "deleting a cmdline variable is +# not possible". Deleting such a variable has been possible since var.c 1.204 +# from 2016-02-19, when the variable modifier ':@' started to delete the +# temporary loop variable after finishing the loop. It was probably not +# intended back then that a side effect of this seemingly simple change was +# that both global and cmdline variables could now be undefined at will as a +# side effect of evaluating a variable expression. As of 2021-02-23, this is +# still possible. +# +# Most cmdline variables are set at the very beginning, when parsing the +# command line arguments. Using the special target '.MAKEFLAGS', it is +# possible to set cmdline variables at any later time. + +# A normal global variable, without any cmdline variable nearby. +VAR= global +.info ${VAR} -# TODO: Implementation +# The global variable is "overridden" by simply deleting it and then +# installing the cmdline variable instead. Since there is no obvious way to +# undefine a cmdline variable, there is no need to remember the old value +# of the global variable could become visible again. +# +# See varmod-loop.mk for a non-obvious way to undefine a cmdline variable. +.MAKEFLAGS: VAR=makeflags +.info ${VAR} -all: - @:; +# If Var_SetWithFlags should ever forget to delete the global variable, +# the below line would print "global" instead of the current "makeflags". +.MAKEFLAGS: -V VAR diff --git a/contrib/bmake/unit-tests/var-eval-short.exp b/contrib/bmake/unit-tests/var-eval-short.exp new file mode 100644 index 000000000000..ae0aff7d7c2c --- /dev/null +++ b/contrib/bmake/unit-tests/var-eval-short.exp @@ -0,0 +1,29 @@ +make: "var-eval-short.mk" line 41: In the :@ modifier of "", the variable name "${FAIL}" must not contain a dollar. +make: "var-eval-short.mk" line 41: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@}) +make: "var-eval-short.mk" line 79: Invalid time value: ${FAIL}} +make: "var-eval-short.mk" line 79: Malformed conditional (0 && ${:Uword:gmtime=${FAIL}}) +make: "var-eval-short.mk" line 93: Invalid time value: ${FAIL}} +make: "var-eval-short.mk" line 93: Malformed conditional (0 && ${:Uword:localtime=${FAIL}}) +CondParser_Eval: 0 && ${0:?${FAIL}then:${FAIL}else} +Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse-only) +Parsing modifier ${0:?...} +Modifier part: "${FAIL}then" +Modifier part: "${FAIL}else" +Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse-only, defined) +ParseReadLine (158): 'DEFINED= defined' +Global: DEFINED = defined +CondParser_Eval: 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} +Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse-only) +Parsing modifier ${DEFINED:L} +Result of ${DEFINED:L} is "defined" (parse-only, regular) +Parsing modifier ${DEFINED:?...} +Modifier part: "${FAIL}then" +Modifier part: "${FAIL}else" +Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse-only, regular) +ParseReadLine (161): '.MAKEFLAGS: -d0' +ParseDependency(.MAKEFLAGS: -d0) +Global: .MAKEFLAGS = -r -k -d cpv -d +Global: .MAKEFLAGS = -r -k -d cpv -d 0 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/contrib/bmake/unit-tests/var-eval-short.mk b/contrib/bmake/unit-tests/var-eval-short.mk new file mode 100644 index 000000000000..41782f0d7823 --- /dev/null +++ b/contrib/bmake/unit-tests/var-eval-short.mk @@ -0,0 +1,163 @@ +# $NetBSD: var-eval-short.mk,v 1.5 2021/04/04 13:35:26 rillig Exp $ +# +# Tests for each variable modifier to ensure that they only do the minimum +# necessary computations. If the result of the expression is not needed, they +# should only parse the modifier but not actually evaluate it. +# +# See also: +# var.c, the comment starting with 'The ApplyModifier functions' +# ApplyModifier, for the order of the modifiers +# ParseModifierPart, for evaluating nested expressions +# cond-short.mk + +FAIL= ${:!echo unexpected 1>&2!} + +# The following tests only ensure that nested expressions are not evaluated. +# They cannot ensure that any unexpanded text returned from ParseModifierPart +# is ignored as well. To do that, it is necessary to step through the code of +# each modifier. + +.if 0 && ${FAIL} +.endif + +.if 0 && ${VAR::=${FAIL}} +.elif defined(VAR) +. error +.endif + +.if 0 && ${${FAIL}:?then:else} +.endif + +.if 0 && ${1:?${FAIL}:${FAIL}} +.endif + +.if 0 && ${0:?${FAIL}:${FAIL}} +.endif + +# Before var.c 1.870 from 2021-03-14, the expression ${FAIL} was evaluated +# after the loop, when undefining the temporary global loop variable. +# Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the +# variable name. +.if 0 && ${:Uword:@${FAIL}@expr@} +.endif + +.if 0 && ${:Uword:@var@${FAIL}@} +.endif + +# Before var.c,v 1.877 from 2021-03-14, the modifier ':[...]' did not expand +# the nested expression ${FAIL} and then tried to parse the unexpanded text, +# which failed since '$' is not a valid range character. +.if 0 && ${:Uword:[${FAIL}]} +.endif + +# Before var.c,v 1.867 from 2021-03-14, the modifier ':_' defined the variable +# even though the whole expression should have only been parsed, not +# evaluated. +.if 0 && ${:Uword:_=VAR} +.elif defined(VAR) +. error +.endif + +# Before var.c,v 1.856 from 2021-03-14, the modifier ':C' did not expand the +# nested expression ${FAIL} and then tried to compile the unexpanded text as a +# regular expression, which failed both because of the '{FAIL}', which is not +# a valid repetition, and because of the '****', which are repeated +# repetitions as well. +# '${FAIL}' +.if 0 && ${:Uword:C,${FAIL}****,,} +.endif + +DEFINED= # defined +.if 0 && ${DEFINED:D${FAIL}} +.endif + +.if 0 && ${:Uword:E} +.endif + +# As of 2021-03-14, the error 'Invalid time value: ${FAIL}}' is ok since +# ':gmtime' does not expand its argument. +.if 0 && ${:Uword:gmtime=${FAIL}} +.endif + +.if 0 && ${:Uword:H} +.endif + +.if 0 && ${:Uword:hash} +.endif + +.if 0 && ${value:L} +.endif + +# As of 2021-03-14, the error 'Invalid time value: ${FAIL}}' is ok since +# ':localtime' does not expand its argument. +.if 0 && ${:Uword:localtime=${FAIL}} +.endif + +.if 0 && ${:Uword:M${FAIL}} +.endif + +.if 0 && ${:Uword:N${FAIL}} +.endif + +.if 0 && ${:Uword:O} +.endif + +.if 0 && ${:Uword:Ox} +.endif + +.if 0 && ${:Uword:P} +.endif + +.if 0 && ${:Uword:Q} +.endif + +.if 0 && ${:Uword:q} +.endif + +.if 0 && ${:Uword:R} +.endif + +.if 0 && ${:Uword:range} +.endif + +.if 0 && ${:Uword:S,${FAIL},${FAIL},} +.endif + +.if 0 && ${:Uword:sh} +.endif + +.if 0 && ${:Uword:T} +.endif + +.if 0 && ${:Uword:ts/} +.endif + +.if 0 && ${:U${FAIL}} +.endif + +.if 0 && ${:Uword:u} +.endif + +.if 0 && ${:Uword:word=replacement} +.endif + +# Before var.c 1.875 from 2021-03-14, Var_Parse returned "${FAIL}else" for the +# irrelevant right-hand side of the condition, even though this was not +# necessary. Since the return value from Var_Parse is supposed to be ignored +# anyway, and since it is actually ignored in an overly complicated way, +# an empty string suffices. +.MAKEFLAGS: -dcpv +.if 0 && ${0:?${FAIL}then:${FAIL}else} +.endif + +# The ':L' is applied before the ':?' modifier, giving the expression a name +# and a value, just to see whether this value gets passed through or whether +# the parse-only mode results in an empty string (only visible in the debug +# log). As of var.c 1.875 from 2021-03-14, the value of the variable gets +# through, even though an empty string would suffice. +DEFINED= defined +.if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} +.endif +.MAKEFLAGS: -d0 + +all: diff --git a/contrib/bmake/unit-tests/var-op-append.exp b/contrib/bmake/unit-tests/var-op-append.exp index 424ad37ccf63..32134be75a3d 100644 --- a/contrib/bmake/unit-tests/var-op-append.exp +++ b/contrib/bmake/unit-tests/var-op-append.exp @@ -1,7 +1,7 @@ -Var_Parse: ${:U\$\$\$\$\$\$\$\$} with VARE_WANTRES -Applying ${:U...} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U\$\$\$\$\$\$\$\$} is "$$$$$$$$" (VARE_WANTRES, none, VES_DEF) -Global:VAR.$$$$$$$$ = dollars -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +Var_Parse: ${:U\$\$\$\$\$\$\$\$} (eval) +Evaluating modifier ${:U...} on value "" (eval, undefined) +Result of ${:U\$\$\$\$\$\$\$\$} is "$$$$$$$$" (eval, defined) +Global: VAR.$$$$$$$$ = dollars +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/var-op-append.mk b/contrib/bmake/unit-tests/var-op-append.mk index deb4af6a7384..420ee376b75d 100644 --- a/contrib/bmake/unit-tests/var-op-append.mk +++ b/contrib/bmake/unit-tests/var-op-append.mk @@ -1,46 +1,46 @@ -# $NetBSD: var-op-append.mk,v 1.8 2021/02/03 08:40:47 rillig Exp $ +# $NetBSD: var-op-append.mk,v 1.9 2021/04/04 10:13:09 rillig Exp $ # # Tests for the += variable assignment operator, which appends to a variable, # creating it if necessary. # Appending to an undefined variable is possible. # The variable is created, and no extra space is added before the value. VAR+= one .if ${VAR} != "one" . error .endif # Appending to an existing variable adds a single space and the value. VAR+= two .if ${VAR} != "one two" . error .endif # Appending an empty string nevertheless adds a single space. VAR+= # empty .if ${VAR} != "one two " . error .endif # Variable names may contain '+', and this character is also part of the # '+=' assignment operator. As far as possible, the '+' is interpreted as # part of the assignment operator. # -# See Parse_DoVar +# See Parse_Var C++= value .if ${C+} != "value" || defined(C++) . error .endif # Before var.c 1.793 from 2021-02-03, the variable name of a newly created # variable was expanded two times in a row, which was unexpected but # irrelevant in practice since variable names containing dollars lead to # strange side effects in several other places as well. .MAKEFLAGS: -dv VAR.${:U\$\$\$\$\$\$\$\$}+= dollars .MAKEFLAGS: -d0 .if ${VAR.${:U\$\$\$\$\$\$\$\$}} != "dollars" . error .endif all: diff --git a/contrib/bmake/unit-tests/var-op-assign.mk b/contrib/bmake/unit-tests/var-op-assign.mk index 3bcc3de0ba0e..18ecf8d0d5ed 100644 --- a/contrib/bmake/unit-tests/var-op-assign.mk +++ b/contrib/bmake/unit-tests/var-op-assign.mk @@ -1,96 +1,96 @@ -# $NetBSD: var-op-assign.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: var-op-assign.mk,v 1.8 2021/03/15 19:15:04 rillig Exp $ # # Tests for the = variable assignment operator, which overwrites an existing # variable or creates it. # This is a simple variable assignment. # To the left of the assignment operator '=' there is the variable name, # and to the right is the variable value. The variable value is stored as-is, # it is not expanded in any way. # VAR= value # This condition demonstrates that whitespace around the assignment operator # is discarded. Otherwise the value would start with a single tab. # .if ${VAR} != "value" . error .endif # Whitespace to the left of the assignment operator is ignored as well. # The variable value can contain arbitrary characters. # # The '#' needs to be escaped with a backslash, this happens in a very # early stage of parsing and applies to all line types, except for the # commands, which are indented with a tab. # # The '$' needs to be escaped with another '$', otherwise it would refer to # another variable. # VAR= new value and \# some $$ special characters # comment # When a string literal appears in a condition, the escaping rules are # different. Run make with the -dc option to see the details. .if ${VAR} != "new value and \# some \$ special characters" . error ${VAR} .endif # The variable value may contain references to other variables. # In this example, the reference is to the variable with the empty name, # which is never defined. # # This alone would not produce any side-effects, therefore the variable has # a :!...! modifier that executes a shell command. The :!...! modifier turns # an undefined expression into a defined one, see ApplyModifier_ShellCommand, -# the call to ApplyModifiersState_Define. +# the call to Expr_Define. # # Since the right-hand side of a '=' assignment is not expanded at the time # when the variable is defined, the first command is not run at all. VAR= ${:! echo 'not yet evaluated' 1>&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: @:; diff --git a/contrib/bmake/unit-tests/var-op-sunsh.mk b/contrib/bmake/unit-tests/var-op-sunsh.mk index 0e16b2b42d34..0d15b8c88b92 100644 --- a/contrib/bmake/unit-tests/var-op-sunsh.mk +++ b/contrib/bmake/unit-tests/var-op-sunsh.mk @@ -1,124 +1,124 @@ -# $NetBSD: var-op-sunsh.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: var-op-sunsh.mk,v 1.8 2021/04/04 10:13:09 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. +# Parse_Var. 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). +# adventurous. This only works because the parser replaces each and every +# whitespace character that is not nested with '\0' (see Parse_Var). # 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. +# Parse_Var 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. +# counted by Parse_IsVar and ignored by Parse_Var. 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: @:; diff --git a/contrib/bmake/unit-tests/varcmd.mk b/contrib/bmake/unit-tests/varcmd.mk index 9ec4f4f9a21a..12739df30926 100644 --- a/contrib/bmake/unit-tests/varcmd.mk +++ b/contrib/bmake/unit-tests/varcmd.mk @@ -1,60 +1,70 @@ -# $NetBSD: varcmd.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: varcmd.mk,v 1.6 2021/02/16 19:43:09 rillig Exp $ # # Test behaviour of recursive make and vars set on command line. +# +# FIXME: The purpose of this test is unclear. The test uses six levels of +# sub-makes, which makes it incredibly hard to understand. There must be at +# least an introductory explanation about what _should_ happen here. +# The variable names are terrible, as well as their values. +# +# This test produces different results if the large block with the condition +# "scope == SCOPE_GLOBAL" in Var_SetWithFlags is removed. This test should +# be rewritten to make it clear why there is a difference and why this is +# actually intended. Removing that large block of code makes only this test +# and vardebug.mk fail, which is not enough. FU= fu FOO?= foo .if !empty(.TARGETS) TAG= ${.TARGETS} .endif TAG?= default all: one show: @echo "${TAG} FU=${FU} FOO=${FOO} VAR=${VAR}" one: show @${.MAKE} -f ${MAKEFILE} FU=bar FOO+=goo two two: show @${.MAKE} -f ${MAKEFILE} three three: show @${.MAKE} -f ${MAKEFILE} four .ifmake two # this should not work FU+= oops FOO+= oops _FU:= ${FU} _FOO:= ${FOO} two: immutable immutable: @echo "$@ FU='${_FU}'" @echo "$@ FOO='${_FOO}'" .endif .ifmake four VAR=Internal .MAKEOVERRIDES+= VAR .endif four: show @${.MAKE} -f ${MAKEFILE} five M= x V.y= is y V.x= is x V:= ${V.$M} K:= ${V} show-v: @echo '${TAG} v=${V} k=${K}' five: show show-v @${.MAKE} -f ${MAKEFILE} M=y six six: show-v @${.MAKE} -f ${MAKEFILE} V=override show-v - diff --git a/contrib/bmake/unit-tests/vardebug.exp b/contrib/bmake/unit-tests/vardebug.exp index a9a00a11f5dd..6d00acc977af 100644 --- a/contrib/bmake/unit-tests/vardebug.exp +++ b/contrib/bmake/unit-tests/vardebug.exp @@ -1,86 +1,69 @@ Global:delete FROM_CMDLINE (not found) -Command:FROM_CMDLINE = -Global:.MAKEOVERRIDES = FROM_CMDLINE -Global:VAR = added -Global:VAR = overwritten +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, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) -Var_Set("${:U}", "empty name", ...) name expands to empty string - ignored -Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_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]] +Var_SetExpand: variable name "${:U}" expands to empty string, with value "empty name" - ignored +Var_AppendExpand: variable name "${:U}" expands to empty string, with value "empty name" - ignored +Global: FROM_CMDLINE = overwritten ignored! +Global: VAR = 1 +Global: VAR = 1 2 +Global: VAR = 1 2 3 +Var_Parse: ${VAR:M[2]} (eval-defined) +Evaluating modifier ${VAR:M...} on value "1 2 3" +Pattern for ':M' 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]] +Result of ${VAR:M[2]} is "2" +Var_Parse: ${VAR:N[2]} (eval-defined) +Evaluating modifier ${VAR:N...} on value "1 2 3" +Pattern for ':N' 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) +Result of ${VAR:N[2]} is "1 3" +Var_Parse: ${VAR:S,2,two,} (eval-defined) +Evaluating modifier ${VAR:S...} on value "1 2 3" 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, VES_UNDEF) -Result of ${:Uvalue} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Var_Parse: ${:UM*e}:Mvalu[e]} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:UM*e} is "M*e" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) +Result of ${VAR:S,2,two,} is "1 two 3" +Var_Parse: ${VAR:Q} (eval-defined) +Evaluating modifier ${VAR:Q} on value "1 2 3" +Result of ${VAR:Q} is "1\ 2\ 3" +Var_Parse: ${VAR:tu:tl:Q} (eval-defined) +Evaluating modifier ${VAR:t...} on value "1 2 3" +Result of ${VAR:tu} is "1 2 3" +Evaluating modifier ${VAR:t...} on value "1 2 3" +Result of ${VAR:tl} is "1 2 3" +Evaluating modifier ${VAR:Q} on value "1 2 3" +Result of ${VAR:Q} is "1\ 2\ 3" +Var_Parse: ${:Uvalue:${:UM*e}:Mvalu[e]} (eval-defined) +Evaluating modifier ${:U...} on value "" (eval-defined, undefined) +Result of ${:Uvalue} is "value" (eval-defined, defined) Indirect modifier "M*e" from "${:UM*e}" -Applying ${:M...} to "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[] for [value] is [*e] +Evaluating modifier ${:M...} on value "value" (eval-defined, defined) +Pattern for ':M' is "*e" ModifyWords: split "value" into 1 words -VarMatch [value] [*e] -Result of ${:M*e} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Applying ${:M...} to "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[] for [value] is [valu[e]] +Result of ${:M*e} is "value" (eval-defined, defined) +Evaluating modifier ${:M...} on value "value" (eval-defined, defined) +Pattern for ':M' is "valu[e]" ModifyWords: split "value" into 1 words -VarMatch [value] [valu[e]] -Result of ${:Mvalu[e]} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Var_Parse: ${:UVAR} with VARE_WANTRES -Applying ${:U...} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:UVAR} is "VAR" (VARE_WANTRES, none, VES_DEF) +Result of ${:Mvalu[e]} is "value" (eval-defined, defined) Global:delete VAR -Var_Parse: ${:Uvariable:unknown} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:Uvariable} is "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Applying ${:u...} to "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -make: "vardebug.mk" line 44: Unknown modifier 'u' -Result of ${:unknown} is error (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) +Var_Parse: ${:Uvariable:unknown} (eval-defined) +Evaluating modifier ${:U...} on value "" (eval-defined, undefined) +Result of ${:Uvariable} is "variable" (eval-defined, defined) +Evaluating modifier ${:u...} on value "variable" (eval-defined, defined) +make: "vardebug.mk" line 44: Unknown modifier "unknown" +Result of ${:unknown} is error (eval-defined, defined) make: "vardebug.mk" line 44: Malformed conditional (${:Uvariable:unknown}) -Var_Parse: ${UNDEFINED} with VARE_UNDEFERR|VARE_WANTRES +Var_Parse: ${UNDEFINED} (eval-defined) make: "vardebug.mk" line 53: Malformed conditional (${UNDEFINED}) Global:delete .SHELL (not found) -Command:.SHELL = -Command:.SHELL = overwritten ignored (read-only) -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +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 diff --git a/contrib/bmake/unit-tests/varmisc.exp b/contrib/bmake/unit-tests/varmisc.exp index e8f88d9ca51f..f56f72d0ab9c 100644 --- a/contrib/bmake/unit-tests/varmisc.exp +++ b/contrib/bmake/unit-tests/varmisc.exp @@ -1,74 +1,74 @@ :D expanded when var set true TRUE :U expanded when var undef true TRUE :D skipped if var undef :U skipped when var set is set :? only lhs when value true true TRUE :? only rhs when value false false FALSE do not evaluate or expand :? if discarding is set year=2016 month=04 day=01 date=20160401 Version=123.456.789 == 123456789 Literal=3.4.5 == 3004005 We have target specific vars MAN= make.1 save-dollars: 0 = $ save-dollars: 1 = $$ save-dollars: 2 = $$ save-dollars: False = $ save-dollars: True = $$ save-dollars: false = $ save-dollars: true = $$ save-dollars: Yes = $$ save-dollars: No = $ save-dollars: yes = $$ save-dollars: no = $ save-dollars: On = $$ save-dollars: Off = $ save-dollars: ON = $$ save-dollars: OFF = $ save-dollars: on = $$ save-dollars: off = $ export-appended: env export-appended: env export-appended: env mk parse-dynamic: parse-dynamic parse-dynamic before parse-dynamic: parse-dynamic parse-dynamic after parse-dynamic: parse-dynamic parse-dynamic after varerror-unclosed:begin make: Unclosed variable "" make: Unclosed variable "UNCLOSED" make: Unclosed variable "UNCLOSED" make: Unclosed variable "PATTERN" -make: Unclosed variable specification (expecting '}') for "UNCLOSED" (value "") modifier M +make: Unclosed variable expression, expecting '}' for modifier "M${PATTERN" of variable "UNCLOSED" with value "" make: Unclosed variable "param" make: Unclosed variable "UNCLOSED." make: Unclosed variable "UNCLOSED.1" make: Unclosed variable "UNCLOSED.2" make: Unclosed variable "UNCLOSED.3" make: Unclosed variable "UNCLOSED_ORIG" varerror-unclosed:end target1-flags: we have: one two target2-flags: we have: one two three four exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-assign.exp b/contrib/bmake/unit-tests/varmod-assign.exp index 743ef2fb4082..1e43714d500b 100644 --- a/contrib/bmake/unit-tests/varmod-assign.exp +++ b/contrib/bmake/unit-tests/varmod-assign.exp @@ -1,26 +1,40 @@ +Global: param = twice +Global: VARNAME = VAR.$${param} +Var_Parse: ${VARNAME} (eval) +Global: VAR.${param} = initial-value +Var_Parse: ${${VARNAME}::=assigned-value} (eval-defined) +Var_Parse: ${VARNAME}::=assigned-value} (eval-defined) +Evaluating modifier ${VAR.${param}::...} on value "initial-value" +Modifier part: "assigned-value" +Global: VAR.${param} = assigned-value +Result of ${VAR.${param}::=assigned-value} is "" +Var_Parse: ${${VARNAME}} != "assigned-value" (eval-defined) +Var_Parse: ${VARNAME}} != "assigned-value" (eval-defined) +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 mod-assign: first=1. mod-assign: last=3. mod-assign: appended=1 2 3. 1 2 3 mod-assign: ran:3. mod-assign: global: 1, 3, 1 2 3, 3. mod-assign-nested: then1t1 mod-assign-nested: else2e2 mod-assign-nested: then3t3 mod-assign-nested: else4e4 -make: Bad modifier `:' for +make: Bad modifier ":" for variable "" mod-assign-empty: value} -make: Bad modifier `:' for +make: Bad modifier ":" for variable "" mod-assign-empty: overwritten} mod-assign-empty: VAR=overwritten -make: Unknown modifier ':' +make: Unknown modifier ":x" sysv:y -make: Unfinished modifier for ASSIGN ('}' missing) +make: Unfinished modifier for "ASSIGN" ('}' missing) ok=word make: " echo word; false " returned non-zero status err=previous exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-assign.mk b/contrib/bmake/unit-tests/varmod-assign.mk index e4cbc249df88..f50c654f5bcf 100644 --- a/contrib/bmake/unit-tests/varmod-assign.mk +++ b/contrib/bmake/unit-tests/varmod-assign.mk @@ -1,106 +1,141 @@ -# $NetBSD: varmod-assign.mk,v 1.9 2021/01/22 22:54:53 rillig Exp $ +# $NetBSD: varmod-assign.mk,v 1.12 2021/03/15 18:56:38 rillig Exp $ # # Tests for the obscure ::= variable modifiers, which perform variable # assignments during evaluation, just like the = operator in C. all: mod-assign all: mod-assign-nested all: mod-assign-empty all: mod-assign-parse all: mod-assign-shell-error mod-assign: # The ::?= modifier applies the ?= assignment operator 3 times. # The ?= operator only has an effect for the first time, therefore # the variable FIRST ends up with the value 1. @echo $@: ${1 2 3:L:@i@${FIRST::?=$i}@} first=${FIRST}. # The ::= modifier applies the = assignment operator 3 times. # The = operator overwrites the previous value, therefore the # variable LAST ends up with the value 3. @echo $@: ${1 2 3:L:@i@${LAST::=$i}@} last=${LAST}. # The ::+= modifier applies the += assignment operator 3 times. # The += operator appends 3 times to the variable, therefore # the variable APPENDED ends up with the value "1 2 3". @echo $@: ${1 2 3:L:@i@${APPENDED::+=$i}@} appended=${APPENDED}. # The ::!= modifier applies the != assignment operator 3 times. # The side effects of the shell commands are visible in the output. # Just as with the ::= modifier, the last value is stored in the # RAN variable. @echo $@: ${echo.1 echo.2 echo.3:L:@i@${RAN::!=${i:C,.*,&; & 1>\&2,:S,., ,g}}@} ran:${RAN}. # The assignments happen in the global scope and thus are # preserved even after the shell command has been run. @echo $@: global: ${FIRST:Q}, ${LAST:Q}, ${APPENDED:Q}, ${RAN:Q}. mod-assign-nested: # The condition "1" is true, therefore THEN1 gets assigned a value, # and IT1 as well. Nothing surprising here. @echo $@: ${1:?${THEN1::=then1${IT1::=t1}}:${ELSE1::=else1${IE1::=e1}}}${THEN1}${ELSE1}${IT1}${IE1} # The condition "0" is false, therefore ELSE1 gets assigned a value, # and IE1 as well. Nothing surprising here as well. @echo $@: ${0:?${THEN2::=then2${IT2::=t2}}:${ELSE2::=else2${IE2::=e2}}}${THEN2}${ELSE2}${IT2}${IE2} # The same effects happen when the variables are defined elsewhere. @echo $@: ${SINK3:Q} @echo $@: ${SINK4:Q} SINK3:= ${1:?${THEN3::=then3${IT3::=t3}}:${ELSE3::=else3${IE3::=e3}}}${THEN3}${ELSE3}${IT3}${IE3} SINK4:= ${0:?${THEN4::=then4${IT4::=t4}}:${ELSE4::=else4${IE4::=e4}}}${THEN4}${ELSE4}${IT4}${IE4} mod-assign-empty: # Assigning to the empty variable would obviously not work since that # variable is write-protected. Therefore it is rejected early with a # "Bad modifier" message. # # XXX: The error message is hard to read since the variable name is # empty. This leads to a trailing space in the error message. @echo $@: ${::=value} # In this variant, it is not as obvious that the name of the # expression is empty. Assigning to it is rejected as well, with the # same "Bad modifier" message. # # XXX: The error message is hard to read since the variable name is # empty. This leads to a trailing space in the error message. @echo $@: ${:Uvalue::=overwritten} # The :L modifier sets the value of the expression to its variable # name. The name of the expression is "VAR", therefore assigning to # that variable works. @echo $@: ${VAR:L::=overwritten} VAR=${VAR} mod-assign-parse: # The modifier for assignment operators starts with a ':'. # An 'x' after that is an invalid modifier. @echo ${ASSIGN::x} # 'x' is an unknown assignment operator # When parsing an assignment operator fails because the operator is # incomplete, make falls back to the SysV modifier. @echo ${SYSV::=sysv\:x}${SYSV::x=:y} @echo ${ASSIGN::=value # missing closing brace mod-assign-shell-error: # If the command succeeds, the variable is assigned. @${SH_OK::!= echo word; true } echo ok=${SH_OK} # If the command fails, the variable keeps its previous value. @${SH_ERR::=previous} @${SH_ERR::!= echo word; false } echo err=${SH_ERR} -# XXX: The ::= modifier expands its right-hand side, exactly once. +# XXX: The ::= modifier expands its right-hand side exactly once. # This differs subtly from normal assignments such as '+=' or '=', which copy # their right-hand side literally. APPEND.prev= previous APPEND.var= ${APPEND.prev} APPEND.indirect= indirect $${:Unot expanded} APPEND.dollar= $${APPEND.indirect} .if ${APPEND.var::+=${APPEND.dollar}} != "" . error .endif .if ${APPEND.var} != "previous indirect \${:Unot expanded}" . error .endif + + +# The assignment modifier can be used in a variable expression that is +# enclosed in parentheses. In such a case, parsing stops at the first ')', +# not at the first '}'. +VAR= previous +_:= $(VAR::=current}) +.if ${VAR} != "current}" +. error +.endif + + +# Before var.c 1.888 from 2021-03-15, an expression using the modifier '::=' +# expanded its variable name once too often during evaluation. This was only +# relevant for variable names containing a '$' sign in their actual name, not +# the usual VAR.${param}. +.MAKEFLAGS: -dv +param= twice +VARNAME= VAR.$${param} # Indirect variable name because of the '$', + # to avoid difficult escaping rules. + +${VARNAME}= initial-value # Sets 'VAR.${param}' to 'expanded'. +.if defined(VAR.twice) # At this point, the '$$' is not expanded. +. error +.endif +.if ${${VARNAME}::=assigned-value} # Here the variable name gets expanded once +. error # too often. +.endif +.if defined(VAR.twice) +. error The variable name in the '::=' modifier is expanded once too often. +.endif +.if ${${VARNAME}} != "assigned-value" +. error +.endif +.MAKEFLAGS: -d0 diff --git a/contrib/bmake/unit-tests/varmod-defined.exp b/contrib/bmake/unit-tests/varmod-defined.exp index 15f40226f1db..2f7d4dbf4baa 100644 --- a/contrib/bmake/unit-tests/varmod-defined.exp +++ b/contrib/bmake/unit-tests/varmod-defined.exp @@ -1,23 +1,23 @@ -Global:8_DOLLARS = $$$$$$$$ -Global:VAR = -Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Global:VAR = $$$$$$$$ -Var_Parse: ${VAR:D${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${VAR:D...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none) -Var_Parse: ${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Result of ${VAR:D${8_DOLLARS}} is "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none) -Global:VAR = $$$$$$$$ -Var_Parse: ${VAR:@var@${8_DOLLARS}@} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${VAR:@...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none) +Global: 8_DOLLARS = $$$$$$$$ +Global: VAR = +Var_Parse: ${8_DOLLARS} (eval-keep-dollar-and-undefined) +Global: VAR = $$$$$$$$ +Var_Parse: ${VAR:D${8_DOLLARS}} (eval-keep-dollar-and-undefined) +Evaluating modifier ${VAR:D...} on value "$$$$$$$$" (eval-keep-dollar-and-undefined, regular) +Var_Parse: ${8_DOLLARS}} (eval-keep-dollar-and-undefined) +Result of ${VAR:D${8_DOLLARS}} is "$$$$$$$$" (eval-keep-dollar-and-undefined, regular) +Global: VAR = $$$$$$$$ +Var_Parse: ${VAR:@var@${8_DOLLARS}@} (eval-keep-dollar-and-undefined) +Evaluating modifier ${VAR:@...} on value "$$$$$$$$" (eval-keep-dollar-and-undefined, regular) Modifier part: "var" Modifier part: "${8_DOLLARS}" ModifyWords: split "$$$$$$$$" into 1 words -Global:var = $$$$$$$$ -Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_UNDEF +Global: var = $$$$$$$$ +Var_Parse: ${8_DOLLARS} (eval-keep-undefined) ModifyWord_Loop: in "$$$$$$$$", replace "var" with "${8_DOLLARS}" to "$$$$" Global:delete var -Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none) -Global:VAR = $$$$ -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (eval-keep-dollar-and-undefined, regular) +Global: VAR = $$$$ +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-defined.mk b/contrib/bmake/unit-tests/varmod-defined.mk index 59b9d79d754b..a44b9f993146 100644 --- a/contrib/bmake/unit-tests/varmod-defined.mk +++ b/contrib/bmake/unit-tests/varmod-defined.mk @@ -1,105 +1,105 @@ -# $NetBSD: varmod-defined.mk,v 1.9 2020/11/12 00:40:55 rillig Exp $ +# $NetBSD: varmod-defined.mk,v 1.11 2021/04/11 13:35:56 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_Defined passes the emode unmodified to Var_Parse, unlike # ApplyModifier_Loop, which uses ParseModifierPart, which in turn removes -# VARE_KEEP_DOLLAR from eflags. +# the keepDollar flag from emode. # # 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: @:; diff --git a/contrib/bmake/unit-tests/varmod-edge.exp b/contrib/bmake/unit-tests/varmod-edge.exp index c90eef2756c6..d9db72b2e2ef 100644 --- a/contrib/bmake/unit-tests/varmod-edge.exp +++ b/contrib/bmake/unit-tests/varmod-edge.exp @@ -1,23 +1,27 @@ make: "varmod-edge.mk" line 166: ok M-paren make: "varmod-edge.mk" line 166: ok M-mixed make: "varmod-edge.mk" line 166: ok M-unescape -make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U +make: Unclosed variable expression, expecting '}' for modifier "U*)" of variable "" with value "*)" make: "varmod-edge.mk" line 166: ok M-nest-mix make: "varmod-edge.mk" line 166: ok M-nest-brk make: "varmod-edge.mk" line 166: ok M-pat-err make: "varmod-edge.mk" line 166: ok M-bsbs make: "varmod-edge.mk" line 166: ok M-bs1-par make: "varmod-edge.mk" line 166: ok M-bs2-par make: "varmod-edge.mk" line 166: ok M-128 make: "varmod-edge.mk" line 166: ok eq-ext make: "varmod-edge.mk" line 166: ok eq-q make: "varmod-edge.mk" line 166: ok eq-bs -make: Unfinished modifier for INP.eq-esc ('=' missing) +make: Unfinished modifier for "INP.eq-esc" ('=' missing) make: "varmod-edge.mk" line 166: ok eq-esc make: "varmod-edge.mk" line 166: ok colon -make: "varmod-edge.mk" line 165: Unknown modifier ':' -make: "varmod-edge.mk" line 165: Unknown modifier ':' +make: "varmod-edge.mk" line 165: Unknown modifier ":" +make: "varmod-edge.mk" line 165: Unknown modifier ":" make: "varmod-edge.mk" line 166: ok colons +make: "varmod-edge.mk" line 175: Unknown modifier "Z" +make: "varmod-edge.mk" line 175: Malformed conditional (${:Z}) +make: Unfinished modifier for "" (',' missing) +make: "varmod-edge.mk" line 188: Malformed conditional (${:S,}) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-edge.mk b/contrib/bmake/unit-tests/varmod-edge.mk index a0b6d9342ef6..762053d281a3 100644 --- a/contrib/bmake/unit-tests/varmod-edge.mk +++ b/contrib/bmake/unit-tests/varmod-edge.mk @@ -1,173 +1,195 @@ -# $NetBSD: varmod-edge.mk,v 1.13 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-edge.mk,v 1.16 2021/02/23 15:56:30 rillig Exp $ # # Tests for edge cases in variable modifiers. # # These tests demonstrate the current implementation in small examples. # They may contain surprising behavior. # # Each test consists of: # - INP, the input to the test # - MOD, the expression for testing the modifier # - EXP, the expected output TESTS+= M-paren INP.M-paren= (parentheses) {braces} (opening closing) () MOD.M-paren= ${INP.M-paren:M(*)} EXP.M-paren= (parentheses) () # The first closing brace matches the opening parenthesis. # The second closing brace actually ends the variable expression. # # XXX: This is unexpected but rarely occurs in practice. TESTS+= M-mixed INP.M-mixed= (paren-brace} ( MOD.M-mixed= ${INP.M-mixed:M(*}} EXP.M-mixed= (paren-brace} # After the :M modifier has parsed the pattern, only the closing brace # and the colon are unescaped. The other characters are left as-is. # To actually see this effect, the backslashes in the :M modifier need # to be doubled since single backslashes would simply be unescaped by # Str_Match. # # XXX: This is unexpected. The opening brace should also be unescaped. TESTS+= M-unescape INP.M-unescape= ({}): \(\{\}\)\: \(\{}\): MOD.M-unescape= ${INP.M-unescape:M\\(\\{\\}\\)\\:} EXP.M-unescape= \(\{}\): # When the :M and :N modifiers are parsed, the pattern finishes as soon # as open_parens + open_braces == closing_parens + closing_braces. This # means that ( and } form a matching pair. # # Nested variable expressions are not parsed as such. Instead, only the # parentheses and braces are counted. This leads to a parse error since # the nested expression is not "${:U*)}" but only "${:U*)", which is # missing the closing brace. The expression is evaluated anyway. # The final brace in the output comes from the end of M.nest-mix. # # XXX: This is unexpected but rarely occurs in practice. TESTS+= M-nest-mix INP.M-nest-mix= (parentheses) MOD.M-nest-mix= ${INP.M-nest-mix:M${:U*)}} EXP.M-nest-mix= (parentheses)} -# make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U +# make: Unclosed variable expression, expecting '}' for modifier "U*)" of variable "" with value "*)" # In contrast to parentheses and braces, the brackets are not counted # when the :M modifier is parsed since Makefile variables only take the # ${VAR} or $(VAR) forms, but not $[VAR]. # # The final ] in the pattern is needed to close the character class. TESTS+= M-nest-brk INP.M-nest-brk= [ [[ [[[ MOD.M-nest-brk= ${INP.M-nest-brk:M${:U[[[[[]}} EXP.M-nest-brk= [ # The pattern in the nested variable has an unclosed character class. # No error is reported though, and the pattern is closed implicitly. # # XXX: It is unexpected that no error is reported. # See str.c, function Str_Match. # # Before 2019-12-02, this test case triggered an out-of-bounds read # in Str_Match. TESTS+= M-pat-err INP.M-pat-err= [ [[ [[[ MOD.M-pat-err= ${INP.M-pat-err:M${:U[[}} EXP.M-pat-err= [ # The first backslash does not escape the second backslash. # Therefore, the second backslash escapes the parenthesis. # This means that the pattern ends there. # The final } in the output comes from the end of MOD.M-bsbs. # # If the first backslash were to escape the second backslash, the first # closing brace would match the opening parenthesis (see M-mixed), and # the second closing brace would be needed to close the variable. # After that, the remaining backslash would escape the parenthesis in # the pattern, therefore (} would match. TESTS+= M-bsbs INP.M-bsbs= (} \( \(} MOD.M-bsbs= ${INP.M-bsbs:M\\(}} EXP.M-bsbs= \(} #EXP.M-bsbs= (} # If the first backslash were to escape ... # The backslash in \( does not escape the parenthesis, therefore it # counts for the nesting level and matches with the first closing brace. # The second closing brace closes the variable, and the third is copied # literally. # # The second :M in the pattern is nested between ( and }, therefore it # does not start a new modifier. TESTS+= M-bs1-par INP.M-bs1-par= ( (:M (:M} \( \(:M \(:M} MOD.M-bs1-par= ${INP.M-bs1-par:M\(:M*}}} EXP.M-bs1-par= (:M}} # The double backslash is passed verbatim to the pattern matcher. # The Str_Match pattern is \\(:M*}, and there the backslash is unescaped. # Again, the ( takes place in the nesting level, and there is no way to # prevent this, no matter how many backslashes are used. TESTS+= M-bs2-par INP.M-bs2-par= ( (:M (:M} \( \(:M \(:M} MOD.M-bs2-par= ${INP.M-bs2-par:M\\(:M*}}} EXP.M-bs2-par= \(:M}} # Str_Match uses a recursive algorithm for matching the * patterns. # Make sure that it survives patterns with 128 asterisks. # That should be enough for all practical purposes. # To produce a stack overflow, just add more :Qs below. TESTS+= M-128 INP.M-128= ${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g} PAT.M-128= ${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g} MOD.M-128= ${INP.M-128:M${PAT.M-128}} EXP.M-128= ${INP.M-128} # This is the normal SysV substitution. Nothing surprising here. TESTS+= eq-ext INP.eq-ext= file.c file.cc MOD.eq-ext= ${INP.eq-ext:%.c=%.o} EXP.eq-ext= file.o file.cc # The SysV := modifier is greedy and consumes all the modifier text # up until the closing brace or parenthesis. The :Q may look like a # modifier, but it really isn't, that's why it appears in the output. TESTS+= eq-q INP.eq-q= file.c file.cc MOD.eq-q= ${INP.eq-q:%.c=%.o:Q} EXP.eq-q= file.o:Q file.cc # The = in the := modifier can be escaped. TESTS+= eq-bs INP.eq-bs= file.c file.c=%.o MOD.eq-bs= ${INP.eq-bs:%.c\=%.o=%.ext} EXP.eq-bs= file.c file.ext # Having only an escaped '=' results in a parse error. # The call to "pattern.lhs = ParseModifierPart" fails. TESTS+= eq-esc INP.eq-esc= file.c file... MOD.eq-esc= ${INP.eq-esc:a\=b} EXP.eq-esc= # empty # make: Unfinished modifier for INP.eq-esc ('=' missing) TESTS+= colon INP.colon= value MOD.colon= ${INP.colon:} EXP.colon= value TESTS+= colons INP.colons= value MOD.colons= ${INP.colons::::} EXP.colons= # empty .for test in ${TESTS} . if ${MOD.${test}} == ${EXP.${test}} . info ok ${test} . else . warning error in ${test}: expected "${EXP.${test}}", got "${MOD.${test}}" . endif .endfor +# Even in expressions based on an unnamed variable, there may be errors. +# XXX: The error message should mention the variable name of the expression, +# even though that name is empty in this case. +.if ${:Z} +. error +.else +. error +.endif + +# Even in expressions based on an unnamed variable, there may be errors. +# +# Before var.c 1.842 from 2021-02-23, the error message did not surround the +# variable name with quotes, leading to the rather confusing "Unfinished +# modifier for (',' missing)", having two spaces in a row. +# +# XXX: The error message should report the filename:lineno. +.if ${:S,} +. error +.else +. error +.endif + all: @echo ok diff --git a/contrib/bmake/unit-tests/varmod-hash.exp b/contrib/bmake/unit-tests/varmod-hash.exp index f16f30903539..1286b456c6c2 100644 --- a/contrib/bmake/unit-tests/varmod-hash.exp +++ b/contrib/bmake/unit-tests/varmod-hash.exp @@ -1,9 +1,9 @@ -make: Unknown modifier 'h' +make: Unknown modifier "has" 26bb0f5f 12345 -make: Unknown modifier 'h' +make: Unknown modifier "hasX" -make: Unknown modifier 'h' +make: Unknown modifier "hashed" exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-ifelse.exp b/contrib/bmake/unit-tests/varmod-ifelse.exp index 17d4d8afcbeb..e42e39525f1c 100644 --- a/contrib/bmake/unit-tests/varmod-ifelse.exp +++ b/contrib/bmake/unit-tests/varmod-ifelse.exp @@ -1,20 +1,32 @@ -make: Bad conditional expression `variable expression == "literal"' in variable expression == "literal"?bad:bad +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: 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: 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 +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. CondParser_Eval: ${ ${:U\$}{VAR} == value :?ok:bad} != "ok" CondParser_Eval: ${VAR} == value lhs = "value", rhs = "value", op = == lhs = "ok", rhs = "ok", op = != +make: "varmod-ifelse.mk" line 153: no. +make: "varmod-ifelse.mk" line 154: String comparison operator must be either == or != +make: Bad conditional expression 'string == "literal" || no >= 10' in 'string == "literal" || no >= 10?yes:no' +make: "varmod-ifelse.mk" line 154: . +make: Bad conditional expression 'string == "literal" && >= 10' in 'string == "literal" && >= 10?yes:no' +make: "varmod-ifelse.mk" line 159: . +make: Bad conditional expression 'string == "literal" || >= 10' in 'string == "literal" || >= 10?yes:no' +make: "varmod-ifelse.mk" line 160: . +make: "varmod-ifelse.mk" line 167: true +make: "varmod-ifelse.mk" line 169: false +make: Bad conditional expression ' ' in ' ?true:false' +make: "varmod-ifelse.mk" line 171: make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-ifelse.mk b/contrib/bmake/unit-tests/varmod-ifelse.mk index ec6acdb2ee2f..0e16032a6543 100644 --- a/contrib/bmake/unit-tests/varmod-ifelse.mk +++ b/contrib/bmake/unit-tests/varmod-ifelse.mk @@ -1,115 +1,171 @@ -# $NetBSD: varmod-ifelse.mk,v 1.9 2021/01/25 19:05:39 rillig Exp $ +# $NetBSD: varmod-ifelse.mk,v 1.17 2021/06/11 13:01:28 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 +# being called without VARE_UNDEFERR. 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 # As of 2020-12-10, the variable "name" is first expanded, and the result of # this expansion is then taken as the condition. To force the variable # expression in the condition to be evaluated at exactly the right point, # the '$' of the intended '${VAR}' escapes from the parser in form of the # expression ${:U\$}. Because of this escaping, the variable "name" and thus # the condition ends up as "${VAR} == value", just as intended. # # This hack does not work for variables from .for loops since these are # expanded at parse time to their corresponding ${:Uvalue} expressions. # Making the '$' of the '${VAR}' expression indirect hides this expression # from the parser of the .for loop body. See ForLoop_SubstVarLong. .MAKEFLAGS: -dc VAR= value .if ${ ${:U\$}{VAR} == value :?ok:bad} != "ok" . error .endif .MAKEFLAGS: -d0 -all: - @:; +# On 2021-04-19, when building external/bsd/tmux with HAVE_LLVM=yes and +# HAVE_GCC=no, the following conditional generated this error message: +# +# make: Bad conditional expression 'string == "literal" && no >= 10' +# in 'string == "literal" && no >= 10?yes:no' +# +# Despite the error message (which was not clearly marked with "error:"), +# the build continued, for historical reasons, see main_Exit. +# +# The tricky detail here is that the condition that looks so obvious in the +# form written in the makefile becomes tricky when it is actually evaluated. +# This is because the condition is written in the place of the variable name +# of the expression, and in an expression, the variable name is always +# expanded first, before even looking at the modifiers. This happens for the +# modifier ':?' as well, so when CondEvalExpression gets to see the +# expression, it already looks like this: +# +# string == "literal" && no >= 10 +# +# When parsing such an expression, the parser used to be strict. It first +# evaluated the left-hand side of the operator '&&' and then started parsing +# the right-hand side 'no >= 10'. The word 'no' is obviously a string +# literal, not enclosed in quotes, which is ok, even on the left-hand side of +# the comparison operator, but only because this is a condition in the +# modifier ':?'. In an ordinary directive '.if', this would be a parse error. +# For strings, only the comparison operators '==' and '!=' are defined, +# therefore parsing stopped at the '>', producing the 'Bad conditional +# expression'. +# +# Ideally, the conditional expression would not be expanded before parsing +# it. This would allow to write the conditions exactly as seen below. That +# change has a high chance of breaking _some_ existing code and would need +# to be thoroughly tested. +# +# Since cond.c 1.262 from 2021-04-20, make reports a more specific error +# message in situations like these, pointing directly to the specific problem +# instead of just saying that the whole condition is bad. +STRING= string +NUMBER= no # not really a number +.info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. +.info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. + +# The following situation occasionally occurs with MKINET6 or similar +# variables. +NUMBER= # empty, not really a number either +.info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. +.info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. + +# CondParser_LeafToken handles [0-9-+] specially, treating them as a number. +PLUS= + +ASTERISK= * +EMPTY= # empty +# "true" since "+" is not the empty string. +.info ${${PLUS} :?true:false} +# "false" since the variable named "*" is not defined. +.info ${${ASTERISK} :?true:false} +# syntax error since the condition is completely blank. +.info ${${EMPTY} :?true:false} diff --git a/contrib/bmake/unit-tests/varmod-indirect.exp b/contrib/bmake/unit-tests/varmod-indirect.exp index 860da7781979..63ed988d0c0e 100644 --- a/contrib/bmake/unit-tests/varmod-indirect.exp +++ b/contrib/bmake/unit-tests/varmod-indirect.exp @@ -1,59 +1,43 @@ -make: "varmod-indirect.mk" line 13: Unknown modifier '$' -make: "varmod-indirect.mk" line 108: before -make: "varmod-indirect.mk" line 108: after -make: "varmod-indirect.mk" line 114: before -make: "varmod-indirect.mk" line 114: after -make: "varmod-indirect.mk" line 120: before -make: "varmod-indirect.mk" line 120: after -make: "varmod-indirect.mk" line 124: Unknown modifier 'Z' -make: "varmod-indirect.mk" line 125: before -make: "varmod-indirect.mk" line 125: after -ParseReadLine (134): '_:= before ${UNDEF} after' -Global:_ = -Var_Parse: ${UNDEF} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Global:_ = before ${UNDEF} after -ParseReadLine (137): '_:= before ${UNDEF:${:US,a,a,}} after' -Var_Parse: ${UNDEF:${:US,a,a,}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Var_Parse: ${:US,a,a,}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:US,a,a,} is "S,a,a," (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) +make: "varmod-indirect.mk" line 19: Unknown modifier "${" +make: "varmod-indirect.mk" line 52: Unknown modifier "${" +make: "varmod-indirect.mk" line 55: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression. +make: "varmod-indirect.mk" line 140: before +make: "varmod-indirect.mk" line 140: after +make: "varmod-indirect.mk" line 146: before +make: "varmod-indirect.mk" line 146: after +make: "varmod-indirect.mk" line 152: before +make: "varmod-indirect.mk" line 152: after +make: "varmod-indirect.mk" line 156: Unknown modifier "Z" +make: "varmod-indirect.mk" line 157: before +make: "varmod-indirect.mk" line 157: after +ParseReadLine (166): '_:= before ${UNDEF} after' +Global: _ = +Var_Parse: ${UNDEF} after (eval-keep-dollar-and-undefined) +Global: _ = before ${UNDEF} after +ParseReadLine (169): '_:= before ${UNDEF:${:US,a,a,}} after' +Var_Parse: ${UNDEF:${:US,a,a,}} after (eval-keep-dollar-and-undefined) Indirect modifier "S,a,a," from "${:US,a,a,}" -Applying ${UNDEF:S...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) +Evaluating modifier ${UNDEF:S...} on value "" (eval-keep-dollar-and-undefined, undefined) Modifier part: "a" Modifier part: "a" ModifyWords: split "" into 1 words -Result of ${UNDEF:S,a,a,} is "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Var_Parse: ${:US,a,a,}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:US,a,a,} is "S,a,a," (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) -Global:_ = before ${UNDEF:S,a,a,} after -ParseReadLine (147): '_:= before ${UNDEF:${:U}} after' -Var_Parse: ${UNDEF:${:U}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Var_Parse: ${:U}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) +Result of ${UNDEF:S,a,a,} is "" (eval-keep-dollar-and-undefined, undefined) +Global: _ = before ${UNDEF:S,a,a,} after +ParseReadLine (179): '_:= before ${UNDEF:${:U}} after' +Var_Parse: ${UNDEF:${:U}} after (eval-keep-dollar-and-undefined) Indirect modifier "" from "${:U}" -Var_Parse: ${:U}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) -Global:_ = before ${UNDEF:} after -ParseReadLine (152): '_:= before ${UNDEF:${:UZ}} after' -Var_Parse: ${UNDEF:${:UZ}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Var_Parse: ${:UZ}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:UZ} is "Z" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) +Global: _ = before ${UNDEF:} after +ParseReadLine (184): '_:= before ${UNDEF:${:UZ}} after' +Var_Parse: ${UNDEF:${:UZ}} after (eval-keep-dollar-and-undefined) Indirect modifier "Z" from "${:UZ}" -Applying ${UNDEF:Z} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -make: "varmod-indirect.mk" line 152: Unknown modifier 'Z' -Result of ${UNDEF:Z} is error (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Var_Parse: ${:UZ}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:UZ} is "Z" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) -Global:_ = before ${UNDEF:Z} after -ParseReadLine (154): '.MAKEFLAGS: -d0' -ParseDoDependency(.MAKEFLAGS: -d0) -Global:.MAKEFLAGS = -r -k -d 0 -d pv -d -Global:.MAKEFLAGS = -r -k -d 0 -d pv -d 0 +Evaluating modifier ${UNDEF:Z} on value "" (eval-keep-dollar-and-undefined, undefined) +make: "varmod-indirect.mk" line 184: Unknown modifier "Z" +Result of ${UNDEF:Z} is error (eval-keep-dollar-and-undefined, undefined) +Global: _ = before ${UNDEF:Z} after +ParseReadLine (186): '.MAKEFLAGS: -d0' +ParseDependency(.MAKEFLAGS: -d0) +Global: .MAKEFLAGS = -r -k -d 0 -d pv -d +Global: .MAKEFLAGS = -r -k -d 0 -d pv -d 0 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-indirect.mk b/contrib/bmake/unit-tests/varmod-indirect.mk index d130c7cae76d..fa58997cc849 100644 --- a/contrib/bmake/unit-tests/varmod-indirect.mk +++ b/contrib/bmake/unit-tests/varmod-indirect.mk @@ -1,157 +1,247 @@ -# $NetBSD: varmod-indirect.mk,v 1.5 2020/12/27 17:32:25 rillig Exp $ +# $NetBSD: varmod-indirect.mk,v 1.9 2021/03/15 20:00:50 rillig Exp $ # # Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}. # These can be used for very basic purposes like converting a string to either # uppercase or lowercase, as well as for fairly advanced modifiers that first # look like line noise and are hard to decipher. # -# TODO: Since when are indirect modifiers supported? +# Initial support for indirect modifiers was added in var.c 1.101 from +# 2006-02-18. Since var.c 1.108 from 2006-05-11 it is possible to use +# indirect modifiers for all but the very first modifier as well. # To apply a modifier indirectly via another variable, the whole # modifier must be put into a single variable expression. +# The following expression generates a parse error since its indirect +# modifier contains more than a sole variable expression. +# +# expect+1: Unknown modifier '$' .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. +# An indirect variable that evaluates to the empty string is allowed. +# It is even allowed to write another modifier directly afterwards. +# There is no practical use case for this feature though, as demonstrated +# in the test case directly below. +.if ${value:L:${:Dempty}S,value,replaced,} != "replaced" +. warning unexpected +.endif + +# If an expression for an indirect modifier evaluates to anything else than an +# empty string and is neither followed by a ':' nor '}', this produces a parse +# error. Because of this parse error, this feature cannot be used reasonably +# in practice. +# +# expect+1: Unknown modifier '$' +#.MAKEFLAGS: -dvc +.if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}" +. warning FIXME: this expression should have resulted in a parse $\ + error rather than returning the unparsed portion of the $\ + expression. +.else +. error +.endif +#.MAKEFLAGS: -d0 + +# An indirect modifier can be followed by other modifiers, no matter if the +# indirect modifier evaluates to an empty string or not. +# # 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 +.if ${value:L:${:D empty }:S,value,replaced,} != "replaced" +. error .endif # The nested variable expression expands to "tu", and this is interpreted as # a variable modifier for the value "Upper", resulting in "UPPER". .if ${Upper:L:${:Utu}} != "UPPER" . error .endif # The nested variable expression expands to "tl", and this is interpreted as # a variable modifier for the value "Lower", resulting in "lower". .if ${Lower:L:${:Utl}} != "lower" . error .endif # The nested variable expression is ${1 != 1:?Z:tl}, consisting of the # condition "1 != 1", the then-branch "Z" and the else-branch "tl". Since # the condition evaluates to false, the then-branch is ignored (it would # have been an unknown modifier anyway) and the ":tl" modifier is applied. .if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed" . error .endif # The indirect modifier can also replace an ':L' modifier, which allows for # brain twisters since by reading the expression alone, it is not possible # to say whether the variable name will be evaluated as a variable name or # as the immediate value of the expression. VAR= value M_ExpandVar= # an empty modifier M_VarAsValue= L # .if ${VAR:${M_ExpandVar}} != "value" . error .endif .if ${VAR:${M_VarAsValue}} != "VAR" . error .endif # The indirect modifier M_ListToSkip, when applied to a list of patterns, # expands to a sequence of ':N' modifiers, each of which filters one of the # patterns. This list of patterns can then be applied to another variable # to actually filter that variable. # M_ListToSkip= @pat@N$${pat}@:ts: # # The dollar signs need to be doubled in the above modifier expression, # otherwise they would be expanded too early, that is, when parsing the # modifier itself. # # In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'. # The 'N' comes from the expression 'N${pat}', the separating colons come # from the modifier ':ts:'. # #.MAKEFLAGS: -dcv # Uncomment this line to see the details # PRIMES= 2 3 5 7 1[1379] M_NoPrimes= ${PRIMES:${M_ListToSkip}} .if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20" . error .endif .MAKEFLAGS: -d0 # In contrast to the .if conditions, the .for loop allows undefined variable # expressions. These expressions expand to empty strings. # An undefined expression without any modifiers expands to an empty string. .for var in before ${UNDEF} after . info ${var} .endfor # An undefined expression with only modifiers that keep the expression # undefined expands to an empty string. .for var in before ${UNDEF:${:US,a,a,}} after . info ${var} .endfor # Even in an indirect modifier based on an undefined variable, the value of # the expression in Var_Parse is a simple empty string. .for var in before ${UNDEF:${:U}} after . info ${var} .endfor # An error in an indirect modifier. .for var in before ${UNDEF:${:UZ}} after . info ${var} .endfor # Another slightly different evaluation context is the right-hand side of # a variable assignment using ':='. .MAKEFLAGS: -dpv # The undefined variable expression is kept as-is. _:= before ${UNDEF} after # The undefined variable expression is kept as-is. _:= before ${UNDEF:${:US,a,a,}} after # XXX: The subexpression ${:U} is fully defined, therefore it is expanded. # This results in ${UNDEF:}, which can lead to tricky parse errors later, # when the variable '_' is expanded further. # # XXX: What should be the correct strategy here? One possibility is to # expand the defined subexpression and replace it with ${:U...}, just like # in .for loops. This would preserve the structure of the expression while # at the same time expanding the expression as far as possible. _:= before ${UNDEF:${:U}} after # XXX: This expands to ${UNDEF:Z}, which will behave differently if the # variable '_' is used in a context where the variable expression ${_} is # parsed but not evaluated. _:= before ${UNDEF:${:UZ}} after .MAKEFLAGS: -d0 .undef _ + +# When evaluating indirect modifiers, these modifiers may expand to ':tW', +# which modifies the interpretation of the expression value. This modified +# interpretation only lasts until the end of the indirect modifier, it does +# not influence the outer variable expression. +.if ${1 2 3:L:tW:[#]} != 1 # direct :tW applies to the :[#] +. error +.endif +.if ${1 2 3:L:${:UtW}:[#]} != 3 # indirect :tW does not apply to :[#] +. error +.endif + + +# When evaluating indirect modifiers, these modifiers may expand to ':ts*', +# which modifies the interpretation of the expression value. This modified +# interpretation only lasts until the end of the indirect modifier, it does +# not influence the outer variable expression. +# +# In this first expression, the direct ':ts*' has no effect since ':U' does not +# treat the expression value as a list of words but as a single word. It has +# to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no +# variable of that name. +#.MAKEFLAGS: -dcpv +.if ${1 2 3:L:ts*:Ua b c} != "a b c" +. error +.endif +# In this expression, the direct ':ts*' affects the ':M' at the end. +.if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c" +. error +.endif +# In this expression, the ':ts*' is indirect, therefore the changed separator +# only applies to the modifiers from the indirect text. It does not affect +# the ':M' since that is not part of the text from the indirect modifier. +# +# Implementation detail: when ApplyModifiersIndirect calls ApplyModifiers +# (which creates a new ModChain containing a fresh separator), +# the outer separator character is not passed by reference to the inner +# evaluation, therefore the scope of the inner separator ends after applying +# the modifier ':ts*'. +.if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c" +. error +.endif + +# A direct modifier ':U' turns the expression from undefined to defined. +# An indirect modifier ':U' has the same effect, unlike the separator from +# ':ts*' or the single-word marker from ':tW'. +# +# This is because when ApplyModifiersIndirect calls ApplyModifiers, it passes +# the definedness of the outer expression by reference. If that weren't the +# case, the first condition below would result in a parse error because its +# left-hand side would be undefined. +.if ${UNDEF:${:UUindirect-fallback}} != "indirect-fallback" +. error +.endif +.if ${UNDEF:${:UUindirect-fallback}:Uouter-fallback} != "outer-fallback" +. error +.endif + all: diff --git a/contrib/bmake/unit-tests/varmod-loop-varname.exp b/contrib/bmake/unit-tests/varmod-loop-varname.exp new file mode 100644 index 000000000000..9170307bd2a0 --- /dev/null +++ b/contrib/bmake/unit-tests/varmod-loop-varname.exp @@ -0,0 +1,11 @@ +make: "varmod-loop-varname.mk" line 13: In the :@ modifier of "", the variable name "${:Ubar:S,b,v,}" must not contain a dollar. +make: "varmod-loop-varname.mk" line 13: Malformed conditional (${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+") +make: "varmod-loop-varname.mk" line 80: In the :@ modifier of "1 2 3", the variable name "v$" must not contain a dollar. +make: "varmod-loop-varname.mk" line 80: Malformed conditional (${1 2 3:L:@v$@($v)@} != "(1) (2) (3)") +make: "varmod-loop-varname.mk" line 85: In the :@ modifier of "1 2 3", the variable name "v$$" must not contain a dollar. +make: "varmod-loop-varname.mk" line 85: Malformed conditional (${1 2 3:L:@v$$@($v)@} != "() () ()") +make: "varmod-loop-varname.mk" line 90: In the :@ modifier of "1 2 3", the variable name "v$$$" must not contain a dollar. +make: "varmod-loop-varname.mk" line 90: Malformed conditional (${1 2 3:L:@v$$$@($v)@} != "() () ()") +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-loop-varname.mk b/contrib/bmake/unit-tests/varmod-loop-varname.mk new file mode 100644 index 000000000000..d51e2ba76a42 --- /dev/null +++ b/contrib/bmake/unit-tests/varmod-loop-varname.mk @@ -0,0 +1,127 @@ +# $NetBSD: varmod-loop-varname.mk,v 1.2 2021/04/04 13:35:26 rillig Exp $ +# +# Tests for the first part of the variable modifier ':@var@...@', which +# contains the variable name to use during the loop. + +.MAKE.SAVE_DOLLARS= yes + + +# Before 2021-04-04, the name of the loop variable could be generated +# dynamically. There was no practical use-case for this. +# Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the +# variable name. +.if ${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+" +. error +.endif + + +# ":::" is a very creative variable name, unlikely to occur in practice. +# The expression ${\:\:\:} would not work since backslashes can only +# be escaped in the modifiers, but not in the variable name, therefore +# the extra indirection via the modifier ':U'. +.if ${:U1 2 3:@:::@x${${:U\:\:\:}}y@} != "x1y x2y x3y" +. error +.endif + + +# "@@" is another creative variable name. +.if ${:U1 2 3:@\@\@@x${@@}y@} != "x1y x2y x3y" +. error +.endif + + +# In extreme cases, even the backslash can be used as variable name. +# It needs to be doubled though. +.if ${:U1 2 3:@\\@x${${:Ux:S,x,\\,}}y@} != "x1y x2y x3y" +. error +.endif + + +# 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. +.if ${:U1 2 3:@@x${}y@} != "xy xy xy" +. error +.endif + + +# The :@ modifier resolves the variables from the replacement text once more +# than expected. In particular, it resolves _all_ variables from the scope, +# and not only the loop variable (in this case v). +SRCS= source +CFLAGS.source= before +ALL_CFLAGS:= ${SRCS:@src@${CFLAGS.${src}}@} # note the ':=' +CFLAGS.source+= after +.if ${ALL_CFLAGS} != "before" +. error +.endif + + +# In the following example, the modifier ':@' expands the '$$' to '$'. This +# means that when the resulting expression is evaluated, these resulting '$' +# will be interpreted as starting a subexpression. +# +# 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 + +.if ${RESOLVE:@v@w${v}w@} != "w1d2d3w w2i3w w1i2d3 2i\${RES3}w w1d2d3 2i\${RES3} 1i\${RES2}w" +. error +.endif + + +# 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. +# Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the +# variable name. +.if ${1 2 3:L:@v$@($v)@} != "(1) (2) (3)" +. error +.else +. error +.endif +.if ${1 2 3:L:@v$$@($v)@} != "() () ()" +. error +.else +. error +.endif +.if ${1 2 3:L:@v$$$@($v)@} != "() () ()" +. error +.else +. error +.endif + + +# 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 scope 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 + +all: .PHONY diff --git a/contrib/bmake/unit-tests/varmod-loop.exp b/contrib/bmake/unit-tests/varmod-loop.exp index 66cfd6f51e16..a4704973f6e2 100644 --- a/contrib/bmake/unit-tests/varmod-loop.exp +++ b/contrib/bmake/unit-tests/varmod-loop.exp @@ -1,25 +1,16 @@ -ParseReadLine (117): 'USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$' +ParseReadLine (75): '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}' +ParseReadLine (80): '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:() () (). +ParseReadLine (105): '.MAKEFLAGS: -d0' +ParseDependency(.MAKEFLAGS: -d0) +:varname-overwriting-target: :x1y x2y x3y: :: 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 diff --git a/contrib/bmake/unit-tests/varmod-loop.mk b/contrib/bmake/unit-tests/varmod-loop.mk index c109c775a492..4fdaa3ff4e61 100644 --- a/contrib/bmake/unit-tests/varmod-loop.mk +++ b/contrib/bmake/unit-tests/varmod-loop.mk @@ -1,147 +1,189 @@ -# $NetBSD: varmod-loop.mk,v 1.9 2021/02/04 21:42:47 rillig Exp $ +# $NetBSD: varmod-loop.mk,v 1.15 2021/04/11 13:35:56 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: varname-overwriting-target 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@}: - +varname-overwriting-target: # 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. + # target. Because of this, after the loop has finished, '$@' is + # undefined. This is something that make doesn't expect, this may + # even trigger an assertion failure somewhere. @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 scope, 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 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 scope 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. +# prevented by VARE_EVAL_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 ':=' assignment operator evaluates the variable value using the mode +# VARE_KEEP_DOLLAR_UNDEF, 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 +# in ApplyModifier_Loop, the flag keepDollar 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. +# modifier and are thus expanded with the flag keepDollar 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 + +# After looping over the words of the expression, the loop variable gets +# undefined. The modifier ':@' uses an ordinary global variable for this, +# which is different from the '.for' loop, which replaces ${var} with +# ${:Uvalue} in the body of the loop. This choice of implementation detail +# can be used for a nasty side effect. The expression ${:U:@VAR@@} evaluates +# to an empty string, plus it undefines the variable 'VAR'. This is the only +# possibility to undefine a global variable during evaluation. +GLOBAL= before-global +RESULT:= ${:U${GLOBAL} ${:U:@GLOBAL@@} ${GLOBAL:Uundefined}} +.if ${RESULT} != "before-global undefined" +. error +.endif + +# The above side effect of undefining a variable from a certain scope can be +# further combined with the otherwise undocumented implementation detail that +# the argument of an '.if' directive is evaluated in cmdline scope. Putting +# these together makes it possible to undefine variables from the cmdline +# scope, something that is not possible in a straight-forward way. +.MAKEFLAGS: CMDLINE=cmdline +.if ${:U${CMDLINE}${:U:@CMDLINE@@}} != "cmdline" +. error +.endif +# Now the cmdline variable got undefined. +.if ${CMDLINE} != "cmdline" +. error +.endif +# At this point, it still looks as if the cmdline variable were defined, +# since the value of CMDLINE is still "cmdline". That impression is only +# superficial though, the cmdline variable is actually deleted. To +# demonstrate this, it is now possible to override its value using a global +# variable, something that was not possible before: +CMDLINE= global +.if ${CMDLINE} != "global" +. error +.endif +# Now undefine that global variable again, to get back to the original value. +.undef CMDLINE +.if ${CMDLINE} != "cmdline" +. error +.endif +# What actually happened is that when CMDLINE was set by the '.MAKEFLAGS' +# target in the cmdline scope, that same variable was exported to the +# environment, see Var_SetWithFlags. +.unexport CMDLINE +.if ${CMDLINE} != "cmdline" +. error +.endif +# The above '.unexport' has no effect since UnexportVar requires a global +# variable of the same name to be defined, otherwise nothing is unexported. +CMDLINE= global +.unexport CMDLINE +.undef CMDLINE +.if ${CMDLINE} != "cmdline" +. error +.endif +# This still didn't work since there must not only be a global variable, the +# variable must be marked as exported as well, which it wasn't before. +CMDLINE= global +.export CMDLINE +.unexport CMDLINE +.undef CMDLINE +.if ${CMDLINE:Uundefined} != "undefined" +. error +.endif +# Finally the variable 'CMDLINE' from the cmdline scope is gone, and all its +# traces from the environment are gone as well. To do that, a global variable +# had to be defined and exported, something that is far from obvious. To +# recap, here is the essence of the above story: +.MAKEFLAGS: CMDLINE=cmdline # have a cmdline + environment variable +.if ${:U:@CMDLINE@@}} # undefine cmdline, keep environment +.endif +CMDLINE= global # needed for deleting the environment +.export CMDLINE # needed for deleting the environment +.unexport CMDLINE # delete the environment +.undef CMDLINE # delete the global helper variable +.if ${CMDLINE:Uundefined} != "undefined" +. error # 'CMDLINE' is gone now from all scopes +.endif + + +# TODO: Actually trigger the undefined behavior (use after free) that was +# already suspected in Var_Parse, in the comment 'the value of the variable +# must not change'. diff --git a/contrib/bmake/unit-tests/varmod-match-escape.exp b/contrib/bmake/unit-tests/varmod-match-escape.exp index 30c148075524..42cdd7a87ac9 100755 --- a/contrib/bmake/unit-tests/varmod-match-escape.exp +++ b/contrib/bmake/unit-tests/varmod-match-escape.exp @@ -1,61 +1,39 @@ -Global:SPECIALS = \: : \\ * \* +Global: SPECIALS = \: : \\ * \* CondParser_Eval: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} -Var_Parse: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} with VARE_UNDEFERR|VARE_WANTRES -Applying ${SPECIALS:M...} to "\: : \\ * \*" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${:U}\: with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[SPECIALS] for [\: : \\ * \*] is [\:] +Var_Parse: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} (eval-defined) +Evaluating modifier ${SPECIALS:M...} on value "\: : \\ * \*" +Pattern for ':M' is "\:" ModifyWords: split "\: : \\ * \*" into 5 words -VarMatch [\:] [\:] -VarMatch [:] [\:] -VarMatch [\\] [\:] -VarMatch [*] [\:] -VarMatch [\*] [\:] -Result of ${SPECIALS:M${:U}\:} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${SPECIALS:M\:${:U}} with VARE_UNDEFERR|VARE_WANTRES -Applying ${SPECIALS:M...} to "\: : \\ * \*" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${:U} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[SPECIALS] for [\: : \\ * \*] is [:] +Result of ${SPECIALS:M${:U}\:} is ":" +Var_Parse: ${SPECIALS:M\:${:U}} (eval-defined) +Evaluating modifier ${SPECIALS:M...} on value "\: : \\ * \*" +Pattern for ':M' is ":" ModifyWords: split "\: : \\ * \*" into 5 words -VarMatch [\:] [:] -VarMatch [:] [:] -VarMatch [\\] [:] -VarMatch [*] [:] -VarMatch [\*] [:] -Result of ${SPECIALS:M\:${:U}} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, none) +Result of ${SPECIALS:M\:${:U}} is ":" lhs = ":", rhs = ":", op = != -Global:VALUES = : :: :\: +Global: VALUES = : :: :\: CondParser_Eval: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} -Var_Parse: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VALUES:M...} to ": :: :\:" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${:U:} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[VALUES] for [: :: :\:] is [:] +Var_Parse: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} (eval-defined) +Evaluating modifier ${VALUES:M...} on value ": :: :\:" +Var_Parse: ${:U:} (eval-defined) +Evaluating modifier ${:U} on value "" (eval-defined, undefined) +Result of ${:U} is "" (eval-defined, defined) +Pattern for ':M' is ":" ModifyWords: split ": :: :\:" into 3 words -VarMatch [:] [:] -VarMatch [::] [:] -VarMatch [:\:] [:] -Result of ${VALUES:M\:${:U\:}} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${VALUES:M${:U\:}\:} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VALUES:M...} to ": :: :\:" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${:U\:}\: with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:U\:} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[VALUES] for [: :: :\:] is [:\:] +Result of ${VALUES:M\:${:U\:}} is ":" +Var_Parse: ${VALUES:M${:U\:}\:} (eval-defined) +Evaluating modifier ${VALUES:M...} on value ": :: :\:" +Var_Parse: ${:U\:}\: (eval-defined) +Evaluating modifier ${:U...} on value "" (eval-defined, undefined) +Result of ${:U\:} is ":" (eval-defined, defined) +Pattern for ':M' is ":\:" ModifyWords: split ": :: :\:" into 3 words -VarMatch [:] [:\:] -VarMatch [::] [:\:] -VarMatch [:\:] [:\:] -Result of ${VALUES:M${:U\:}\:} is "::" (VARE_UNDEFERR|VARE_WANTRES, none, none) +Result of ${VALUES:M${:U\:}\:} is "::" lhs = ":", rhs = "::", op = != make: "varmod-match-escape.mk" line 42: warning: XXX: Oops -Global:.MAKEFLAGS = -r -k -d cv -d -Global:.MAKEFLAGS = -r -k -d cv -d 0 +Global: .MAKEFLAGS = -r -k -d cv -d +Global: .MAKEFLAGS = -r -k -d cv -d 0 make: "varmod-match-escape.mk" line 67: Dollar followed by nothing make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-match-escape.mk b/contrib/bmake/unit-tests/varmod-match-escape.mk index e62fbe8352b7..5ac69f964a68 100755 --- a/contrib/bmake/unit-tests/varmod-match-escape.mk +++ b/contrib/bmake/unit-tests/varmod-match-escape.mk @@ -1,90 +1,90 @@ -# $NetBSD: varmod-match-escape.mk,v 1.6 2021/02/01 22:36:28 rillig Exp $ +# $NetBSD: varmod-match-escape.mk,v 1.7 2021/04/03 11:08:40 rillig Exp $ # # As of 2020-08-01, the :M and :N modifiers interpret backslashes differently, # depending on whether there was a variable expression somewhere before the -# first backslash or not. See ApplyModifier_Match, "copy = TRUE". +# first backslash or not. See ApplyModifier_Match, "copy = true". # # Apart from the different and possibly confusing debug output, there is no # difference in behavior. When parsing the modifier text, only \{, \} and \: # are unescaped, and in the pattern matching these have the same meaning as # their plain variants '{', '}' and ':'. In the pattern matching from # Str_Match, only \*, \? or \[ would make a noticeable difference. .MAKEFLAGS: -dcv SPECIALS= \: : \\ * \* .if ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} . warning unexpected .endif # And now both cases combined: A single modifier with both an escaped ':' # as well as a variable expression that expands to a ':'. # # XXX: As of 2020-11-01, when an escaped ':' occurs before the variable # expression, the whole modifier text is subject to unescaping '\:' to ':', # before the variable expression is expanded. This means that the '\:' in # the variable expression is expanded as well, turning ${:U\:} into a simple # ${:U:}, which silently expands to an empty string, instead of generating # an error message. # # XXX: As of 2020-11-01, the modifier on the right-hand side of the # comparison is parsed differently though. First, the variable expression -# is parsed, resulting in ':' and needSubst=TRUE. After that, the escaped -# ':' is seen, and this time, copy=TRUE is not executed but stays copy=FALSE. +# is parsed, resulting in ':' and needSubst=true. After that, the escaped +# ':' is seen, and this time, copy=true is not executed but stays copy=false. # Therefore the escaped ':' is kept as-is, and the final pattern becomes # ':\:'. # # If ApplyModifier_Match had used the same parsing algorithm as Var_Subst, # both patterns would end up as '::'. # VALUES= : :: :\: .if ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} . warning XXX: Oops .endif .MAKEFLAGS: -d0 # XXX: As of 2020-11-01, unlike all other variable modifiers, a '$' in the # :M and :N modifiers is written as '$$', not as '\$'. This is confusing, # undocumented and hopefully not used in practice. .if ${:U\$:M$$} != "\$" . error .endif # XXX: As of 2020-11-01, unlike all other variable modifiers, '\$' is not # parsed as an escaped '$'. Instead, ApplyModifier_Match first scans for # the ':' at the end of the modifier, which results in the pattern '\$'. # No unescaping takes place since the pattern neither contained '\:' nor # '\{' nor '\}'. But the text is expanded, and a lonely '$' at the end # is silently discarded. The resulting expanded pattern is thus '\', that # is a single backslash. .if ${:U\$:M\$} != "" . error .endif # In lint mode, the case of a lonely '$' is covered with an error message. .MAKEFLAGS: -dL .if ${:U\$:M\$} != "" . error .endif # The control flow of the pattern parser depends on the actual string that # is being matched. There needs to be either a test that shows a difference # in behavior, or a proof that the behavior does not depend on the actual # string. # # TODO: Str_Match("a-z]", "[a-z]") # TODO: Str_Match("012", "[0-]]") # TODO: Str_Match("0]", "[0-]]") # TODO: Str_Match("1]", "[0-]]") # TODO: Str_Match("[", "[[]") # TODO: Str_Match("]", "[]") # TODO: Str_Match("]", "[[-]]") # In brackets, the backslash is just an ordinary character. # Outside brackets, it is an escape character for a few special characters. # TODO: Str_Match("\\", "[\\-]]") # TODO: Str_Match("-]", "[\\-]]") all: @:; diff --git a/contrib/bmake/unit-tests/varmod-order.exp b/contrib/bmake/unit-tests/varmod-order.exp index 99d1d6ef164c..94c3cb694886 100644 --- a/contrib/bmake/unit-tests/varmod-order.exp +++ b/contrib/bmake/unit-tests/varmod-order.exp @@ -1,7 +1,7 @@ -make: Bad modifier `:OX' for NUMBERS +make: Bad modifier ":OX" for variable "NUMBERS" make: "varmod-order.mk" line 13: Undefined variable "${NUMBERS:OX" -make: Bad modifier `:OxXX' for NUMBERS +make: Bad modifier ":OxXX" for variable "NUMBERS" make: "varmod-order.mk" line 16: Undefined variable "${NUMBERS:Ox" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-range.exp b/contrib/bmake/unit-tests/varmod-range.exp index 3a9d4d032c3a..f4ada11ebde6 100644 --- a/contrib/bmake/unit-tests/varmod-range.exp +++ b/contrib/bmake/unit-tests/varmod-range.exp @@ -1,13 +1,13 @@ -make: "varmod-range.mk" line 53: Invalid number: x}Rest" != "Rest" +make: "varmod-range.mk" line 53: Invalid number "x}Rest" != "Rest"" for ':range' modifier make: "varmod-range.mk" line 53: Malformed conditional ("${:U:range=x}Rest" != "Rest") -make: "varmod-range.mk" line 62: Unknown modifier 'x' +make: "varmod-range.mk" line 62: Unknown modifier "x0" make: "varmod-range.mk" line 62: Malformed conditional ("${:U:range=0x0}Rest" != "Rest") -make: "varmod-range.mk" line 78: Unknown modifier 'r' +make: "varmod-range.mk" line 78: Unknown modifier "rang" make: "varmod-range.mk" line 78: Malformed conditional ("${a b c:L:rang}Rest" != "Rest") -make: "varmod-range.mk" line 85: Unknown modifier 'r' +make: "varmod-range.mk" line 85: Unknown modifier "rango" make: "varmod-range.mk" line 85: Malformed conditional ("${a b c:L:rango}Rest" != "Rest") -make: "varmod-range.mk" line 92: Unknown modifier 'r' +make: "varmod-range.mk" line 92: Unknown modifier "ranger" make: "varmod-range.mk" line 92: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest") make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-remember.exp b/contrib/bmake/unit-tests/varmod-remember.exp index 448f817d8969..39a9383953dd 100644 --- a/contrib/bmake/unit-tests/varmod-remember.exp +++ b/contrib/bmake/unit-tests/varmod-remember.exp @@ -1,3 +1 @@ -1 2 3 1 2 3 1 2 3 -1 2 3, SAVED=3 exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-remember.mk b/contrib/bmake/unit-tests/varmod-remember.mk index 68eb96a122c4..403811759672 100644 --- a/contrib/bmake/unit-tests/varmod-remember.mk +++ b/contrib/bmake/unit-tests/varmod-remember.mk @@ -1,12 +1,35 @@ -# $NetBSD: varmod-remember.mk,v 1.3 2020/08/23 15:18:43 rillig Exp $ +# $NetBSD: varmod-remember.mk,v 1.6 2021/03/14 17:27:27 rillig Exp $ # # Tests for the :_ modifier, which saves the current variable value # in the _ variable or another, to be used later again. +.if ${1 2 3:L:_:@var@${_}@} != "1 2 3 1 2 3 1 2 3" +. error +.endif + # In the parameterized form, having the variable name on the right side of # the = assignment operator is confusing. In almost all other situations # the variable name is on the left-hand side of the = operator. Luckily # this modifier is only rarely needed. +.if ${1 2 3:L:@var@${var:_=SAVED:}@} != "1 2 3" +. error +.elif ${SAVED} != "3" +. error +.endif + +# The ':_' modifier takes a variable name as optional argument. This variable +# name can refer to other variables, though this was rather an implementation +# oversight than an intended feature. The variable name stops at the first +# '}' or ')' and thus cannot use the usual form ${VARNAME} of long variable +# names. +# +# Because of all these edge-casey conditions, this "feature" has been removed +# in var.c 1.867 from 2021-03-14. +S= INDIRECT_VARNAME +.if ${value:L:@var@${var:_=$S}@} != "value" +. error +.elif defined(INDIRECT_VARNAME) +. error +.endif + all: - @echo ${1 2 3:L:_:@var@${_}@} - @echo ${1 2 3:L:@var@${var:_=SAVED:}@}, SAVED=${SAVED} diff --git a/contrib/bmake/unit-tests/varmod-shell.mk b/contrib/bmake/unit-tests/varmod-shell.mk index db82e302f2a8..c736042f80a0 100644 --- a/contrib/bmake/unit-tests/varmod-shell.mk +++ b/contrib/bmake/unit-tests/varmod-shell.mk @@ -1,35 +1,32 @@ -# $NetBSD: varmod-shell.mk,v 1.5 2020/11/17 20:11:02 rillig Exp $ +# $NetBSD: varmod-shell.mk,v 1.6 2021/02/14 20:16:17 rillig Exp $ # -# Tests for the :sh variable modifier, which runs the shell command -# given by the variable value and returns its output. +# Tests for the ':!cmd!' variable modifier, which runs the shell command +# given by the variable modifier 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: - @:; diff --git a/contrib/bmake/unit-tests/varmod-subst-regex.exp b/contrib/bmake/unit-tests/varmod-subst-regex.exp index 207a97fc25e8..a09046ef764c 100644 --- a/contrib/bmake/unit-tests/varmod-subst-regex.exp +++ b/contrib/bmake/unit-tests/varmod-subst-regex.exp @@ -1,25 +1,46 @@ make: Regex compilation error: (details omitted) mod-regex-compile-error: C,word,____,:Q}. make: No subexpression \1 make: No subexpression \1 make: No subexpression \1 make: No subexpression \1 mod-regex-limits:11-missing:1 6 mod-regex-limits:11-ok:1 22 446 make: No subexpression \2 make: No subexpression \2 make: No subexpression \2 make: No subexpression \2 mod-regex-limits:22-missing:1 6 make: No subexpression \2 make: No subexpression \2 make: No subexpression \2 make: No subexpression \2 mod-regex-limits:22-missing:1 6 mod-regex-limits:22-ok:1 33 556 mod-regex-limits:capture:ihgfedcbaabcdefghijABCDEFGHIJa0a1a2rest make: Regex compilation error: (details omitted) mod-regex-errors: -make: Unknown modifier 'Z' +make: Unknown modifier "Z" mod-regex-errors: xy -exit status 0 +unmatched-subexpression.ok: one one 2 3 5 8 one3 2one 34 +make: No match for subexpression \2 +unmatched-subexpression.1: ()() +make: No match for subexpression \2 +unmatched-subexpression.1: ()() +make: No match for subexpression \1 +unmatched-subexpression.2: ()() +unmatched-subexpression.3: 3 +unmatched-subexpression.5: 5 +unmatched-subexpression.8: 8 +make: No match for subexpression \2 +unmatched-subexpression.13: (3)() +make: No match for subexpression \1 +unmatched-subexpression.21: ()(1) +unmatched-subexpression.34: 34 +make: No match for subexpression \2 +make: No match for subexpression \2 +make: No match for subexpression \1 +make: No match for subexpression \2 +make: No match for subexpression \1 +unmatched-subexpression.all: ()() ()() ()() 3 5 8 (3)() ()(1) 34 +exit status 2 diff --git a/contrib/bmake/unit-tests/varmod-subst-regex.mk b/contrib/bmake/unit-tests/varmod-subst-regex.mk index 91b2f0e6a2f9..197691d73aad 100644 --- a/contrib/bmake/unit-tests/varmod-subst-regex.mk +++ b/contrib/bmake/unit-tests/varmod-subst-regex.mk @@ -1,109 +1,161 @@ -# $NetBSD: varmod-subst-regex.mk,v 1.6 2020/12/05 18:13:44 rillig Exp $ +# $NetBSD: varmod-subst-regex.mk,v 1.7 2021/06/21 08:17:39 rillig Exp $ # # Tests for the :C,from,to, variable modifier. +# report unmatched subexpressions +.MAKEFLAGS: -dL + all: mod-regex-compile-error all: mod-regex-limits all: mod-regex-errors +all: unmatched-subexpression # The variable expression expands to 4 words. Of these words, none matches # the regular expression "a b" since these words don't contain any # whitespace. .if ${:Ua b b c:C,a b,,} != "a b b c" . error .endif # Using the '1' modifier does not change anything. The '1' modifier just # means to apply at most 1 replacement in the whole variable expression. .if ${:Ua b b c:C,a b,,1} != "a b b c" . error .endif # The 'W' modifier treats the whole variable value as a single big word, # containing whitespace. This big word matches the regular expression, # therefore it gets replaced. Whitespace is preserved after replacing. .if ${:Ua b b c:C,a b,,W} != " b c" . error .endif # The 'g' modifier does not have any effect here since each of the words # contains the character 'b' a single time. .if ${:Ua b b c:C,b,,g} != "a c" . error .endif # The first :C modifier has the 'W' modifier, which makes the whole # expression a single word. The 'g' modifier then replaces all occurrences # of "1 2" with "___". The 'W' modifier only applies to this single :C # modifier. This is demonstrated by the :C modifier that follows. If the # 'W' modifier would be preserved, only a single underscore would have been # replaced with an 'x'. .if ${:U1 2 3 1 2 3:C,1 2,___,Wg:C,_,x,} != "x__ 3 x__ 3" . error .endif # The regular expression does not match in the first word. # It matches once in the second word, and the \0\0 doubles that word. # In the third word, the regular expression matches as early as possible, # and since the matches must not overlap, the next possible match would # start at the 6, but at that point, there is only one character left, # and that cannot match the regular expression "..". Therefore only the # "45" is doubled in the third word. .if ${:U1 23 456:C,..,\0\0,} != "1 2323 45456" . error .endif # The modifier '1' applies the replacement at most once, across the whole # expression value, no matter whether it is a single big word or many small # words. # # Up to 2020-08-28, the manual page said that the modifiers '1' and 'g' # were orthogonal, which was wrong. It doesn't make sense to specify both # 'g' and '1' at the same time. .if ${:U12345 12345:C,.,\0\0,1} != "112345 12345" . error .endif # A regular expression that matches the empty string applies before every # single character of the word. # XXX: Most other places where regular expression are used match at the end # of the string as well. .if ${:U1a2b3c:C,a*,*,g} != "*1**2*b*3*c" . error .endif # A dot in the regular expression matches any character, even a newline. # In most other contexts where regular expressions are used, a dot matches # any character except newline. In make, regcomp is called without # REG_NEWLINE, thus newline is an ordinary character. .if ${:U"${.newline}":C,.,.,g} != "..." . error .endif # Multiple asterisks form an invalid regular expression. This produces an # error message and (as of 2020-08-28) stops parsing in the middle of the # variable expression. The unparsed part of the expression is then copied # verbatim to the output, which is unexpected and can lead to strange shell # commands being run. mod-regex-compile-error: @echo $@: ${:Uword1 word2:C,****,____,g:C,word,____,:Q}. # These tests generate error messages but as of 2020-08-28 just continue # parsing and execution as if nothing bad had happened. mod-regex-limits: @echo $@:11-missing:${:U1 23 456:C,..,\1\1,:Q} @echo $@:11-ok:${:U1 23 456:C,(.).,\1\1,:Q} @echo $@:22-missing:${:U1 23 456:C,..,\2\2,:Q} @echo $@:22-missing:${:U1 23 456:C,(.).,\2\2,:Q} @echo $@:22-ok:${:U1 23 456:C,(.)(.),\2\2,:Q} # The :C modifier only handles single-digit capturing groups, # which is more than enough for daily use. @echo $@:capture:${:UabcdefghijABCDEFGHIJrest:C,(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.),\9\8\7\6\5\4\3\2\1\0\10\11\12,} mod-regex-errors: @echo $@: ${UNDEF:Uvalue:C,[,,} # If the replacement pattern produces a parse error because of an # unknown modifier, the parse error is ignored in ParseModifierPart # and the faulty variable expression expands to "". @echo $@: ${word:L:C,.*,x${:U:Z}y,W} + +# In regular expressions with alternatives, not all capturing groups are +# always set; some may be missing. Make calls these "unmatched +# subexpressions". +# +# Between var.c 1.16 from 1996-12-24 until before var.c 1.933 from 2021-06-21, +# unmatched subexpressions produced an "error message" but did not have any +# further effect since the "error handling" didn't influence the exit status. +# +# Before 2021-06-21 there was no way to turn off this warning, thus the +# combination of alternative matches and capturing groups was seldom used, if +# at all. +# +# Since var.c 1.933 from 2021-06-21, the error message is only printed in lint +# mode (-dL), but not in default mode. +# +# As an alternative to the change from var.c 1.933 from 2021-06-21, a possible +# mitigation would have been to add a new modifier 'U' to the already existing +# '1Wg' modifiers of the ':C' modifier. That modifier could have been used in +# the modifier ':C,(a.)|(b.),\1\2,U' to treat unmatched subexpressions as +# empty. This approach would have created a syntactical ambiguity since the +# modifiers ':S' and ':C' are open-ended (see mod-subst-chain), that is, they +# do not need to be followed by a ':' to separate them from the next modifier. +# Luckily the modifier :U does not make sense after :C, therefore this case +# does not happen in practice. +unmatched-subexpression: + # In each of the following cases, if the regular expression matches at + # all, the subexpression \1 matches as well. + @echo $@.ok: ${:U1 1 2 3 5 8 13 21 34:C,1(.*),one\1,} + + # In the following cases: + # * The subexpression \1 is only defined for 1 and 13. + # * The subexpression \2 is only defined for 2 and 21. + # * If the regular expression does not match at all, the + # replacement string is not analyzed, thus no error messages. + # In total, there are 5 error messages about unmatched subexpressions. + @echo $@.1: ${:U 1:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \2 + @echo $@.1: ${:U 1:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \2 + @echo $@.2: ${:U 2:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \1 + @echo $@.3: ${:U 3:C,1(.*)|2(.*),(\1)(\2),:Q} + @echo $@.5: ${:U 5:C,1(.*)|2(.*),(\1)(\2),:Q} + @echo $@.8: ${:U 8:C,1(.*)|2(.*),(\1)(\2),:Q} + @echo $@.13: ${:U 13:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \2 + @echo $@.21: ${:U 21:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \1 + @echo $@.34: ${:U 34:C,1(.*)|2(.*),(\1)(\2),:Q} + + # And now all together: 5 error messages for 1, 1, 2, 13, 21. + @echo $@.all: ${:U1 1 2 3 5 8 13 21 34:C,1(.*)|2(.*),(\1)(\2),:Q} diff --git a/contrib/bmake/unit-tests/varmod-subst.exp b/contrib/bmake/unit-tests/varmod-subst.exp index 3122c17b1ed3..97fa2e4f1491 100644 --- a/contrib/bmake/unit-tests/varmod-subst.exp +++ b/contrib/bmake/unit-tests/varmod-subst.exp @@ -1,62 +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 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 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 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 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 right curly bracket 1 two 3 tilde mod-subst-chain: A B c. -make: Unknown modifier 'i' +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 diff --git a/contrib/bmake/unit-tests/varmod-subst.mk b/contrib/bmake/unit-tests/varmod-subst.mk index 3c3ee673c07a..85f41e499ab7 100644 --- a/contrib/bmake/unit-tests/varmod-subst.mk +++ b/contrib/bmake/unit-tests/varmod-subst.mk @@ -1,181 +1,189 @@ -# $NetBSD: varmod-subst.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: varmod-subst.mk,v 1.8 2021/05/14 19:37:16 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 +.if ${WORDS:S,*,replacement,} != ${WORDS} +. error The '*' seems to be interpreted as a wildcard of some kind. +.endif + +.if ${WORDS:S,.,replacement,} != ${WORDS} +. error The '.' seems to be interpreted as a wildcard of some kind. +.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} 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} 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} 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;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} 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} 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 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 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} diff --git a/contrib/bmake/unit-tests/varmod-sun-shell.exp b/contrib/bmake/unit-tests/varmod-sun-shell.exp new file mode 100644 index 000000000000..5087bc66d943 --- /dev/null +++ b/contrib/bmake/unit-tests/varmod-sun-shell.exp @@ -0,0 +1,2 @@ +make: "echo word; false" returned non-zero status +exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-sun-shell.mk b/contrib/bmake/unit-tests/varmod-sun-shell.mk new file mode 100644 index 000000000000..712b36bc7030 --- /dev/null +++ b/contrib/bmake/unit-tests/varmod-sun-shell.mk @@ -0,0 +1,21 @@ +# $NetBSD: varmod-sun-shell.mk,v 1.1 2021/02/14 20:16:17 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 1996-05-29. +# +# See also: +# ApplyModifier_SunShell + +.if ${echo word:L:sh} != "word" +. error +.endif + +# If the command exits with non-zero, an error message is printed. +# XXX: Processing continues as usual though. +.if ${echo word; false:L:sh} != "word" +. error +.endif + +all: diff --git a/contrib/bmake/unit-tests/varmod-sysv.exp b/contrib/bmake/unit-tests/varmod-sysv.exp index 57e69a667281..59275857f98a 100644 --- a/contrib/bmake/unit-tests/varmod-sysv.exp +++ b/contrib/bmake/unit-tests/varmod-sysv.exp @@ -1,5 +1,150 @@ -make: Unfinished modifier for word214 ('=' missing) +make: Unfinished modifier for "word214" ('=' missing) make: "varmod-sysv.mk" line 214: Malformed conditional (${word214:L:from${:D=}to}) +word modifier result +'' = "" +suffix = "suffix" +prefix = "prefix" +pre-middle-suffix = "pre-middle-suffix" +'' =NS "" +suffix =NS "suffixNS" +prefix =NS "prefixNS" +pre-middle-suffix =NS "pre-middle-suffixNS" +'' =% "" +suffix =% "suffix%" +prefix =% "prefix%" +pre-middle-suffix =% "pre-middle-suffix%" +'' =%NS "" +suffix =%NS "suffix%NS" +prefix =%NS "prefix%NS" +pre-middle-suffix =%NS "pre-middle-suffix%NS" +'' =NPre% "" +suffix =NPre% "suffixNPre%" +prefix =NPre% "prefixNPre%" +pre-middle-suffix =NPre% "pre-middle-suffixNPre%" +'' =NPre%NS "" +suffix =NPre%NS "suffixNPre%NS" +prefix =NPre%NS "prefixNPre%NS" +pre-middle-suffix =NPre%NS "pre-middle-suffixNPre%NS" +'' ffix= "" +suffix ffix= "su" +prefix ffix= "prefix" +pre-middle-suffix ffix= "pre-middle-su" +'' ffix=NS "" +suffix ffix=NS "suNS" +prefix ffix=NS "prefix" +pre-middle-suffix ffix=NS "pre-middle-suNS" +'' ffix=% "" +suffix ffix=% "su%" +prefix ffix=% "prefix" +pre-middle-suffix ffix=% "pre-middle-su%" +'' ffix=%NS "" +suffix ffix=%NS "su%NS" +prefix ffix=%NS "prefix" +pre-middle-suffix ffix=%NS "pre-middle-su%NS" +'' ffix=NPre% "" +suffix ffix=NPre% "suNPre%" +prefix ffix=NPre% "prefix" +pre-middle-suffix ffix=NPre% "pre-middle-suNPre%" +'' ffix=NPre%NS "" +suffix ffix=NPre%NS "suNPre%NS" +prefix ffix=NPre%NS "prefix" +pre-middle-suffix ffix=NPre%NS "pre-middle-suNPre%NS" +'' %= "" +suffix %= "" +prefix %= "" +pre-middle-suffix %= "" +'' %=NS "" +suffix %=NS "NS" +prefix %=NS "NS" +pre-middle-suffix %=NS "NS" +'' %=% "" +suffix %=% "suffix" +prefix %=% "prefix" +pre-middle-suffix %=% "pre-middle-suffix" +'' %=%NS "" +suffix %=%NS "suffixNS" +prefix %=%NS "prefixNS" +pre-middle-suffix %=%NS "pre-middle-suffixNS" +'' %=NPre% "" +suffix %=NPre% "NPresuffix" +prefix %=NPre% "NPreprefix" +pre-middle-suffix %=NPre% "NPrepre-middle-suffix" +'' %=NPre%NS "" +suffix %=NPre%NS "NPresuffixNS" +prefix %=NPre%NS "NPreprefixNS" +pre-middle-suffix %=NPre%NS "NPrepre-middle-suffixNS" +'' pre%= "" +suffix pre%= "suffix" +prefix pre%= "" +pre-middle-suffix pre%= "" +'' pre%=NS "" +suffix pre%=NS "suffix" +prefix pre%=NS "NS" +pre-middle-suffix pre%=NS "NS" +'' pre%=% "" +suffix pre%=% "suffix" +prefix pre%=% "fix" +pre-middle-suffix pre%=% "-middle-suffix" +'' pre%=%NS "" +suffix pre%=%NS "suffix" +prefix pre%=%NS "fixNS" +pre-middle-suffix pre%=%NS "-middle-suffixNS" +'' pre%=NPre% "" +suffix pre%=NPre% "suffix" +prefix pre%=NPre% "NPrefix" +pre-middle-suffix pre%=NPre% "NPre-middle-suffix" +'' pre%=NPre%NS "" +suffix pre%=NPre%NS "suffix" +prefix pre%=NPre%NS "NPrefixNS" +pre-middle-suffix pre%=NPre%NS "NPre-middle-suffixNS" +'' %ffix= "" +suffix %ffix= "" +prefix %ffix= "prefix" +pre-middle-suffix %ffix= "" +'' %ffix=NS "" +suffix %ffix=NS "NS" +prefix %ffix=NS "prefix" +pre-middle-suffix %ffix=NS "NS" +'' %ffix=% "" +suffix %ffix=% "su" +prefix %ffix=% "prefix" +pre-middle-suffix %ffix=% "pre-middle-su" +'' %ffix=%NS "" +suffix %ffix=%NS "suNS" +prefix %ffix=%NS "prefix" +pre-middle-suffix %ffix=%NS "pre-middle-suNS" +'' %ffix=NPre% "" +suffix %ffix=NPre% "NPresu" +prefix %ffix=NPre% "prefix" +pre-middle-suffix %ffix=NPre% "NPrepre-middle-su" +'' %ffix=NPre%NS "" +suffix %ffix=NPre%NS "NPresuNS" +prefix %ffix=NPre%NS "prefix" +pre-middle-suffix %ffix=NPre%NS "NPrepre-middle-suNS" +'' pre%ffix= "" +suffix pre%ffix= "suffix" +prefix pre%ffix= "prefix" +pre-middle-suffix pre%ffix= "" +'' pre%ffix=NS "" +suffix pre%ffix=NS "suffix" +prefix pre%ffix=NS "prefix" +pre-middle-suffix pre%ffix=NS "NS" +'' pre%ffix=% "" +suffix pre%ffix=% "suffix" +prefix pre%ffix=% "prefix" +pre-middle-suffix pre%ffix=% "-middle-su" +'' pre%ffix=%NS "" +suffix pre%ffix=%NS "suffix" +prefix pre%ffix=%NS "prefix" +pre-middle-suffix pre%ffix=%NS "-middle-suNS" +'' pre%ffix=NPre% "" +suffix pre%ffix=NPre% "suffix" +prefix pre%ffix=NPre% "prefix" +pre-middle-suffix pre%ffix=NPre% "NPre-middle-su" +'' pre%ffix=NPre%NS "" +suffix pre%ffix=NPre%NS "suffix" +prefix pre%ffix=NPre%NS "prefix" +pre-middle-suffix pre%ffix=NPre%NS "NPre-middle-suNS" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-sysv.mk b/contrib/bmake/unit-tests/varmod-sysv.mk index 751736ceaf74..712c1731717b 100644 --- a/contrib/bmake/unit-tests/varmod-sysv.mk +++ b/contrib/bmake/unit-tests/varmod-sysv.mk @@ -1,241 +1,254 @@ -# $NetBSD: varmod-sysv.mk,v 1.12 2020/12/05 13:01:33 rillig Exp $ +# $NetBSD: varmod-sysv.mk,v 1.14 2021/04/12 16:09:57 rillig Exp $ # -# Tests for the ${VAR:from=to} variable modifier, which replaces the suffix +# Tests for the variable modifier ':from=to', which replaces the suffix # "from" with "to". It can also use '%' as a wildcard. # # This modifier is applied when the other modifiers don't match exactly. # # See ApplyModifier_SysV. -# A typical use case for the :from=to modifier is conversion of filename +# A typical use case for the modifier ':from=to' is conversion of filename # extensions. .if ${src.c:L:.c=.o} != "src.o" . error .endif # The modifier applies to each word on its own. .if ${one.c two.c three.c:L:.c=.o} != "one.o two.o three.o" . error .endif # Words that don't match the pattern are passed unmodified. .if ${src.c src.h:L:.c=.o} != "src.o src.h" . error .endif -# The :from=to modifier is therefore often combined with the :M modifier. +# The modifier ':from=to' is therefore often combined with the modifier ':M'. .if ${src.c src.h:L:M*.c:.c=.o} != "src.o" . error .endif -# Another use case for the :from=to modifier is to append a suffix to each +# Another use case for the modifier ':from=to' is to append a suffix to each # word. In this case, the "from" string is empty, therefore it always -# matches. The same effect can be achieved with the :S,$,teen, modifier. +# matches. The same effect can be achieved with the modifier ':S,$,teen,'. .if ${four six seven nine:L:=teen} != "fourteen sixteen seventeen nineteen" . error .endif -# The :from=to modifier can also be used to surround each word by strings. +# The modifier ':from=to' can also be used to surround each word by strings. # It might be tempting to use this for enclosing a string in quotes for the -# shell, but that's the job of the :Q modifier. +# shell, but that's the job of the modifier ':Q'. .if ${one two three:L:%=(%)} != "(one) (two) (three)" . error .endif -# When the :from=to modifier is parsed, it lasts until the closing brace -# or parenthesis. The :Q in the below expression may look like a modifier -# but isn't. It is part of the replacement string. +# When the modifier ':from=to' is parsed, it lasts until the closing brace +# or parenthesis. The ':Q' in the below expression may look like a modifier +# but it isn't. It is part of the replacement string. .if ${a b c d e:L:%a=x:Q} != "x:Q b c d e" . error .endif -# In the :from=to modifier, both parts can contain variable expressions. +# In the modifier ':from=to', both parts can contain variable expressions. .if ${one two:L:${:Uone}=${:U1}} != "1 two" . error .endif -# In the :from=to modifier, the "from" part is expanded exactly once. +# In the modifier ':from=to', the "from" part is expanded exactly once. .if ${:U\$ \$\$ \$\$\$\$:${:U\$\$\$\$}=4} != "\$ \$\$ 4" . error .endif -# In the :from=to modifier, the "to" part is expanded exactly twice. +# In the modifier ':from=to', the "to" part is expanded exactly twice. # XXX: The right-hand side should be expanded only once. # XXX: It's hard to get the escaping correct here, and to read that. # XXX: It's not intuitive why the closing brace must be escaped but not # the opening brace. .if ${:U1 2 4:4=${:Uonce\${\:Utwice\}}} != "1 2 oncetwice" . error .endif # The replacement string can contain spaces, thereby changing the number # of words in the variable expression. .if ${In:L:%=% ${:Uthe Sun}} != "In the Sun" . error .endif # If the variable value is empty, it is debatable whether it consists of a -# single empty word, or no word at all. The :from=to modifier treats it as +# single empty word, or no word at all. The modifier ':from=to' treats it as # no word at all. # # See SysVMatch, which doesn't handle w_len == p_len specially. .if ${:L:=suffix} != "" . error .endif # If the variable value is empty, it is debatable whether it consists of a # single empty word (before 2020-05-06), or no word at all (since 2020-05-06). # # See SysVMatch, percent != NULL && w[0] == '\0'. .if ${:L:%=suffix} != "" . error .endif # Before 2020-07-19, an ampersand could be used in the replacement part # of a SysV substitution modifier, and it was replaced with the whole match, -# just like in the :S modifier. +# just like in the modifier ':S'. # # This was probably a copy-and-paste mistake since the code for the SysV -# modifier looked a lot like the code for the :S and :C modifiers. +# modifier looked a lot like the code for the modifiers ':S' and ':C'. # The ampersand is not mentioned in the manual page. .if ${a.bcd.e:L:a.%=%} != "bcd.e" . error .endif # Before 2020-07-19, the result of the expression was "a.bcd.e". .if ${a.bcd.e:L:a.%=&} != "&" . error .endif # Before 2020-07-20, when a SysV modifier was parsed, a single dollar # before the '=' was parsed (but not interpreted) as an anchor. # Parsing something without then evaluating it accordingly doesn't make -# sense. +# sense, so this has been fixed. .if ${value:L:e$=x} != "value" . error .endif -# Before 2020-07-20, the modifier ":e$=x" was parsed as having a left-hand -# side "e" and a right-hand side "x". The dollar was parsed (but not +# Before 2020-07-20, the modifier ':e$=x' was parsed as having a left-hand +# side 'e' and a right-hand side 'x'. The dollar was parsed (but not # interpreted) as 'anchor at the end'. Therefore the modifier was equivalent -# to ":e=x", which doesn't match the string "value$". Therefore the whole +# to ':e=x', which doesn't match the string "value$". Therefore the whole # expression evaluated to "value$". .if ${${:Uvalue\$}:L:e$=x} != "valux" . error .endif .if ${value:L:e=x} != "valux" . error .endif # Words that don't match are copied unmodified. .if ${:Ufile.c file.h:%.c=%.cpp} != "file.cpp file.h" . error .endif # The % placeholder can be anywhere in the string, it doesn't have to be at # the beginning of the pattern. .if ${:Ufile.c other.c:file.%=renamed.%} != "renamed.c other.c" . error .endif # It's also possible to modify each word by replacing the prefix and adding # a suffix. .if ${one two:L:o%=a%w} != "anew two" . error .endif # Each word gets the suffix "X" appended. .if ${one two:L:=X} != "oneX twoX" . error .endif # The suffix "o" is replaced with "X". .if ${one two:L:o=X} != "one twX" . error .endif # The suffix "o" is replaced with nothing. .if ${one two:L:o=} != "one tw" . error .endif # The suffix "o" is replaced with a literal percent. The percent is only # a wildcard when it appears on the left-hand side. .if ${one two:L:o=%} != "one tw%" . error .endif # Each word with the suffix "o" is replaced with "X". The percent is a # wildcard even though the right-hand side does not contain another percent. .if ${one two:L:%o=X} != "one X" . error .endif # Each word with the prefix "o" is replaced with "X". The percent is a # wildcard even though the right-hand side does not contain another percent. .if ${one two:L:o%=X} != "X two" . error .endif # For each word with the prefix "o" and the suffix "e", the whole word is # replaced with "X". .if ${one two oe oxen:L:o%e=X} != "X two X oxen" . error .endif # Only the first '%' is the wildcard. .if ${one two o%e other%e:L:o%%e=X} != "one two X X" . error .endif # In the replacement, only the first '%' is the placeholder, all others # are literal percent characters. .if ${one two:L:%=%%} != "one% two%" . error .endif # In the word "one", only a prefix of the pattern suffix "nes" matches, # the whole word is too short. Therefore it doesn't match. .if ${one two:L:%nes=%xxx} != "one two" . error .endif -# The :from=to modifier can be used to replace both the prefix and a suffix +# The modifier ':from=to' can be used to replace both the prefix and a suffix # of a word with other strings. This is not possible with a single :S # modifier, and using a :C modifier for the same task looks more complicated # in many cases. .if ${prefix-middle-suffix:L:prefix-%-suffix=p-%-s} != "p-middle-s" . error .endif # This is not a SysV modifier since the nested variable expression expands # to an empty string. The '=' in it should be irrelevant during parsing. # XXX: As of 2020-12-05, this expression generates an "Unfinished modifier" # error, while the correct error message would be "Unknown modifier" since # there is no modifier named "fromto". .if ${word214:L:from${:D=}to} . error .endif # XXX: This specially constructed case demonstrates that the SysV modifier # lasts longer than expected. The whole expression initially has the value # "fromto}...". The next modifier is a SysV modifier. ApplyModifier_SysV # parses the modifier as "from${:D=}to", ending at the '}'. Next, the two # parts of the modifier are parsed using ParseModifierPart, which scans # differently, properly handling nested variable expressions. The two parts # are now "fromto}..." and "replaced". .if "${:Ufromto\}...:from${:D=}to}...=replaced}" != "replaced" . error .endif # As of 2020-10-06, the right-hand side of the SysV modifier is expanded # twice. The first expansion happens in ApplyModifier_SysV, where the # modifier is split into its two parts. The second expansion happens # when each word is replaced in ModifyWord_SYSVSubst. # XXX: This is unexpected. Add more test case to demonstrate the effects # of removing one of the expansions. VALUE= value INDIRECT= 1:${VALUE} 2:$${VALUE} 4:$$$${VALUE} .if ${x:L:x=${INDIRECT}} != "1:value 2:value 4:\${VALUE}" . error .endif +# Test all relevant combinations of prefix, '%' and suffix in both the pattern +# and the replacement. +!=1>&2 printf '%-24s %-24s %-24s\n' 'word' 'modifier' 'result' +.for from in '' ffix % pre% %ffix pre%ffix +. for to in '' NS % %NS NPre% NPre%NS +. for word in '' suffix prefix pre-middle-suffix +. for mod in ${from:N''}=${to:N''} +!=1>&2 printf '%-24s %-24s "%s"\n' ''${word:Q} ''${mod:Q} ''${word:N'':${mod}:Q} +. endfor +. endfor +. endfor +.endfor + all: diff --git a/contrib/bmake/unit-tests/varmod-to-separator.exp b/contrib/bmake/unit-tests/varmod-to-separator.exp index 44c9f0973ed9..c6e8ce98a21a 100644 --- a/contrib/bmake/unit-tests/varmod-to-separator.exp +++ b/contrib/bmake/unit-tests/varmod-to-separator.exp @@ -1,19 +1,19 @@ make: "varmod-to-separator.mk" line 107: Invalid character number: 400:tu} make: "varmod-to-separator.mk" line 107: Malformed conditional (${WORDS:[1..3]:ts\400:tu}) make: "varmod-to-separator.mk" line 121: Invalid character number: 100:tu} make: "varmod-to-separator.mk" line 121: Malformed conditional (${WORDS:[1..3]:ts\x100:tu}) -make: Bad modifier `:ts\-300' for WORDS +make: Bad modifier ":ts\-300" for variable "WORDS" make: "varmod-to-separator.mk" line 128: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) -make: Bad modifier `:ts\8' for 1 2 3 +make: Bad modifier ":ts\8" for variable "1 2 3" make: "varmod-to-separator.mk" line 136: Malformed conditional (${1 2 3:L:ts\8:tu}) -make: Bad modifier `:ts\100L' for 1 2 3 +make: Bad modifier ":ts\100L" for variable "1 2 3" make: "varmod-to-separator.mk" line 143: Malformed conditional (${1 2 3:L:ts\100L}) -make: Bad modifier `:ts\x40g' for 1 2 3 +make: Bad modifier ":ts\x40g" for variable "1 2 3" make: "varmod-to-separator.mk" line 150: Malformed conditional (${1 2 3:L:ts\x40g}) -make: Bad modifier `:tx' for WORDS +make: Bad modifier ":tx" for variable "WORDS" make: "varmod-to-separator.mk" line 158: Malformed conditional (${WORDS:tx} != "anything") -make: Bad modifier `:t\X' for WORDS +make: Bad modifier ":t\X" for variable "WORDS" make: "varmod-to-separator.mk" line 165: Malformed conditional (${WORDS:t\X} != "anything") make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-unique.mk b/contrib/bmake/unit-tests/varmod-unique.mk index ea4698764947..04d04a575af1 100644 --- a/contrib/bmake/unit-tests/varmod-unique.mk +++ b/contrib/bmake/unit-tests/varmod-unique.mk @@ -1,39 +1,47 @@ -# $NetBSD: varmod-unique.mk,v 1.4 2020/08/31 17:41:38 rillig Exp $ +# $NetBSD: varmod-unique.mk,v 1.5 2021/05/30 20:26:41 rillig Exp $ # # Tests for the :u variable modifier, which discards adjacent duplicate # words. .if ${:U1 2 1:u} != "1 2 1" . warning The :u modifier only merges _adjacent_ duplicate words. .endif .if ${:U1 2 2 3:u} != "1 2 3" . warning The :u modifier must merge adjacent duplicate words. .endif .if ${:U:u} != "" . warning The :u modifier must do nothing with an empty word list. .endif -.if ${:U1:u} != "1" +.if ${:U :u} != "" +. warning The modifier ':u' must normalize the whitespace. +.endif + +.if ${:Uword:u} != "word" . warning The :u modifier must do nothing with a single-element word list. .endif +.if ${:U word :u} != "word" +. warning The modifier ':u' must normalize the whitespace. +.endif + .if ${:U1 1 1 1 1 1 1 1:u} != "1" . warning The :u modifier must merge _all_ adjacent duplicate words. .endif .if ${:U 1 2 1 1 :u} != "1 2 1" . warning The :u modifier must normalize whitespace between the words. .endif .if ${:U1 1 1 1 2:u} != "1 2" . warning Duplicate words at the beginning must be merged. .endif .if ${:U1 2 2 2 2:u} != "1 2" . warning Duplicate words at the end must be merged. .endif all: @:; diff --git a/contrib/bmake/unit-tests/varname-dot-shell.exp b/contrib/bmake/unit-tests/varname-dot-shell.exp index 46a1b2127c98..bfbcfc960182 100755 --- a/contrib/bmake/unit-tests/varname-dot-shell.exp +++ b/contrib/bmake/unit-tests/varname-dot-shell.exp @@ -1,32 +1,32 @@ ParseReadLine (10): 'ORIG_SHELL:= ${.SHELL}' -Global:ORIG_SHELL = -Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF +Global: ORIG_SHELL = +Var_Parse: ${.SHELL} (eval-keep-dollar-and-undefined) Global:delete .SHELL (not found) -Command:.SHELL = (details omitted) -Global:ORIG_SHELL = (details omitted) +Command: .SHELL = (details omitted) +Global: ORIG_SHELL = (details omitted) ParseReadLine (12): '.SHELL= overwritten' -Global:.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 +Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) +Var_Parse: ${ORIG_SHELL} (eval-defined) lhs = "(details omitted)", rhs = "(details omitted)", op = != ParseReadLine (19): '.MAKEFLAGS: .SHELL+=appended' -ParseDoDependency(.MAKEFLAGS: .SHELL+=appended) +ParseDependency(.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 +Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) +Var_Parse: ${ORIG_SHELL} (eval-defined) lhs = "(details omitted)", rhs = "(details omitted)", op = != ParseReadLine (27): '.undef .SHELL' Global:delete .SHELL ParseReadLine (28): '.SHELL= newly overwritten' -Global:.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 +Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) +Var_Parse: ${ORIG_SHELL} (eval-defined) 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 +ParseDependency(.MAKEFLAGS: -d0) +Global: .MAKEFLAGS = -r -k -d cpv -d +Global: .MAKEFLAGS = -r -k -d cpv -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/varname-empty.exp b/contrib/bmake/unit-tests/varname-empty.exp index 28f55368fd19..ec225c6973c8 100644 --- a/contrib/bmake/unit-tests/varname-empty.exp +++ b/contrib/bmake/unit-tests/varname-empty.exp @@ -1,47 +1,27 @@ -Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) -Var_Set("${:U}", "cmdline-u", ...) name expands to empty string - ignored -Var_Set("", "cmdline-plain", ...) name expands to empty string - ignored -Global:.CURDIR = -Var_Parse: ${MAKE_OBJDIR_CHECK_WRITABLE:U} with VARE_WANTRES -Applying ${MAKE_OBJDIR_CHECK_WRITABLE:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${MAKE_OBJDIR_CHECK_WRITABLE:U} is "" (VARE_WANTRES, none, VES_DEF) -Global:.OBJDIR = +Var_SetExpand: variable name "${:U}" expands to empty string, with value "cmdline-u" - ignored +Var_SetExpand: variable name "" expands to empty string, with value "cmdline-plain" - ignored +Global: .CURDIR = +Var_Parse: ${MAKE_OBJDIR_CHECK_WRITABLE} (eval) +Global: .OBJDIR = Global:delete .PATH (not found) -Global:.PATH = . -Global:.PATH = . -Global:.TARGETS = -Internal:MAKEFILE = varname-empty.mk -Global:.MAKE.MAKEFILES = varname-empty.mk -Global:.PARSEFILE = varname-empty.mk +Global: .PATH = . +Global: .PATH = . +Global: .TARGETS = +Internal: MAKEFILE = varname-empty.mk +Global: .MAKE.MAKEFILES = varname-empty.mk +Global: .PARSEFILE = varname-empty.mk Global:delete .INCLUDEDFROMDIR (not found) Global:delete .INCLUDEDFROMFILE (not found) -Var_Set("", "default", ...) name expands to empty string - ignored -Var_Set("", "assigned", ...) name expands to empty string - ignored +Var_SetExpand: variable name "" expands to empty string, with value "default" - ignored +Var_SetExpand: variable name "" expands to empty string, with value "assigned" - ignored SetVar: variable name is empty - 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_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) -Var_Set("${:U}", "assigned indirectly", ...) name expands to empty string - ignored -Var_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) -Var_Append("${:U}", "appended indirectly", ...) name expands to empty string - ignored -Var_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Global:.MAKEFLAGS = -r -d v -d -Global:.MAKEFLAGS = -r -d v -d 0 +Var_SetExpand: variable name "" expands to empty string, with value "" - ignored +Var_SetExpand: variable name "" expands to empty string, with value "subst" - ignored +Var_SetExpand: variable name "" expands to empty string, with value "shell-output" - ignored +Var_SetExpand: variable name "${:U}" expands to empty string, with value "assigned indirectly" - ignored +Var_AppendExpand: variable name "${:U}" expands to empty string, with value "appended indirectly" - ignored +Global: .MAKEFLAGS = -r -d v -d +Global: .MAKEFLAGS = -r -d v -d 0 out: fallback out: 1 2 3 exit status 0 diff --git a/contrib/bmake/unit-tests/varname-empty.mk b/contrib/bmake/unit-tests/varname-empty.mk index 492f9f2618ba..f077d2ec07b4 100755 --- a/contrib/bmake/unit-tests/varname-empty.mk +++ b/contrib/bmake/unit-tests/varname-empty.mk @@ -1,69 +1,69 @@ -# $NetBSD: varname-empty.mk,v 1.8 2021/02/03 08:34:15 rillig Exp $ +# $NetBSD: varname-empty.mk,v 1.9 2021/04/04 10:13:09 rillig Exp $ # # Tests for the special variable with the empty name. # # There is no variable named "" at all, and this fact is used a lot in # variable expressions of the form ${:Ufallback}. These expressions are # based on the variable named "" and use the :U modifier to assign a # fallback value to the expression (but not to the variable). # # This form of expressions is used to implement value substitution in the # .for loops. Another use case is in a variable assignment of the form # ${:Uvarname}=value, which allows for characters in the variable name that # would otherwise be interpreted by the parser, such as whitespace, ':', # '=', '$', backslash. # # The only places where a variable is assigned a value are Var_Set and # Var_Append, and these places protect the variable named "" from being # defined. This is different from read-only variables, as that flag can # only apply to variables that are defined. The variable named "" must # never be defined though. # # See also: # The special variables @F or ^D, in var-class-local.mk # Until 2020-08-22 it was possible to assign a value to the variable with # the empty name, leading to all kinds of unexpected effects in .for loops # and other places that assume that ${:Ufallback} expands to "fallback". # The bug in Var_Set was that only expanded variables had been checked for # the empty name, but not the direct assignments with an empty name. ?= default = assigned # undefined behavior until 2020-08-22 += appended := subst != echo 'shell-output' .if ${:Ufallback} != "fallback" . error .endif ${:U}= assigned indirectly .if ${:Ufallback} != "fallback" . error .endif ${:U}+= appended indirectly .if ${:Ufallback} != "fallback" . error .endif .MAKEFLAGS: -d0 # Before 2020-08-22, the simple assignment operator '=' after an empty -# variable name had an off-by-one bug in Parse_DoVar. The code that was +# variable name had an off-by-one bug in Parse_Var. The code that was # supposed to "skip to operator character" started its search _after_ the # assignment operator, assuming that the variable name would be at least # one character long. It then looked for the next occurrence of a '=', which # could be several lines away or not occur at all. While looking for the # '=', some whitespace was nulled out, leading to out-of-bounds write. = assigned # undefined behavior until 2020-08-22 # The .for loop expands the expression ${i} to ${:U1}, ${:U2} and so on. # This only works if the variable with the empty name is guaranteed to # be undefined. .for i in 1 2 3 NUMBERS+= ${i} .endfor all: @echo out: ${:Ufallback} @echo out: ${NUMBERS} diff --git a/contrib/bmake/unit-tests/varname.exp b/contrib/bmake/unit-tests/varname.exp index 84f878a9f742..942532b654d5 100644 --- a/contrib/bmake/unit-tests/varname.exp +++ b/contrib/bmake/unit-tests/varname.exp @@ -1,24 +1,21 @@ -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, VES_UNDEF) -Result of ${:UVAR(((} is "VAR(((" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Global:.ALLTARGETS = VAR(((=) +Global: VAR{{{}}} = 3 braces +Var_Parse: ${VAR{{{}}}}" != "3 braces" (eval) +Global: VARNAME = VAR((( +Var_Parse: ${VARNAME} (eval) +Global: VAR((( = 3 open parentheses +Var_Parse: ${VAR(((}}}}" != "3 open parentheses}}}" (eval) +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, VES_UNDEF) -Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Global:.ALLTARGETS = VAR(((=) VAR\(\(\(= +Var_Parse: ${:UVAR\(\(\(}= try2 (eval-defined) +Evaluating modifier ${:U...} on value "" (eval-defined, undefined) +Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (eval-defined, defined) +Global: .ALLTARGETS = VAR(((=) VAR\(\(\(= make: "varname.mk" line 35: Invalid line type -Var_Parse: ${VARNAME} with VARE_WANTRES -Global:VAR((( = try3 -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +Var_Parse: ${VARNAME} (eval) +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 diff --git a/contrib/bmake/unit-tests/varparse-dynamic.mk b/contrib/bmake/unit-tests/varparse-dynamic.mk index c65ba12e6149..d4d165017a7f 100644 --- a/contrib/bmake/unit-tests/varparse-dynamic.mk +++ b/contrib/bmake/unit-tests/varparse-dynamic.mk @@ -1,35 +1,35 @@ -# $NetBSD: varparse-dynamic.mk,v 1.4 2021/02/04 21:42:47 rillig Exp $ +# $NetBSD: varparse-dynamic.mk,v 1.5 2021/02/22 20:38:55 rillig Exp $ # Before 2020-07-27, there was an off-by-one error in Var_Parse that skipped # the last character in the variable name. # To trigger the bug, the variable must not be defined. .if ${.TARGET} # exact match, may be undefined .endif .if ${.TARGEX} # 1 character difference, must be defined .endif .if ${.TARGXX} # 2 characters difference, must be defined .endif # When a dynamic variable (such as .TARGET) is evaluated in the global # scope, it is not yet ready to be expanded. Therefore the complete # expression is returned as the variable value, hoping that it can be # resolved at a later point. # -# This test covers the code in Var_Parse that deals with VAR_JUNK but not -# VAR_KEEP for dynamic variables. +# This test covers the code in Var_Parse that deals with DEF_UNDEF but not +# DEF_DEFINED for dynamic variables. .if ${.TARGET:S,^,,} != "\${.TARGET:S,^,,}" . error .endif # If a dynamic variable is expanded in a non-local scope, the expression # based on this variable is not expanded. But there may be nested variable # expressions in the modifiers, and these are kept unexpanded as well. .if ${.TARGET:M${:Ufallback}} != "\${.TARGET:M\${:Ufallback}}" . error .endif .if ${.TARGET:M${UNDEF}} != "\${.TARGET:M\${UNDEF}}" . error .endif all: @: diff --git a/contrib/bmake/unit-tests/varparse-errors.exp b/contrib/bmake/unit-tests/varparse-errors.exp index 50a0766c7d70..27589e0b21af 100644 --- a/contrib/bmake/unit-tests/varparse-errors.exp +++ b/contrib/bmake/unit-tests/varparse-errors.exp @@ -1,5 +1,5 @@ -make: "varparse-errors.mk" line 38: Unknown modifier 'Z' -make: "varparse-errors.mk" line 46: Unknown modifier 'Z' +make: "varparse-errors.mk" line 38: Unknown modifier "Z" +make: "varparse-errors.mk" line 46: Unknown modifier "Z" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varparse-errors.mk b/contrib/bmake/unit-tests/varparse-errors.mk index 113c7a292a79..f0947bb9410a 100644 --- a/contrib/bmake/unit-tests/varparse-errors.mk +++ b/contrib/bmake/unit-tests/varparse-errors.mk @@ -1,51 +1,51 @@ -# $NetBSD: varparse-errors.mk,v 1.3 2020/12/20 19:47:34 rillig Exp $ +# $NetBSD: varparse-errors.mk,v 1.4 2021/03/15 12:15:03 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. +# expanded using the mode VARE_UNDEFERR. # The variable itself must be defined. # It may refer to undefined variables though. .if ${REF_UNDEF} != "A reference to an undefined variable." . error .endif # As of 2020-12-01, errors in the variable name are silently ignored. # Since var.c 1.754 from 2020-12-20, unknown modifiers at parse time result # in an error message and a non-zero exit status. VAR.${:U:Z}= unknown modifier in the variable name .if ${VAR.} != "unknown modifier in the variable name" . error .endif # As of 2020-12-01, errors in the variable name are silently ignored. # Since var.c 1.754 from 2020-12-20, unknown modifiers at parse time result # in an error message and a non-zero exit status. VAR.${:U:Z}post= unknown modifier with text in the variable name .if ${VAR.post} != "unknown modifier with text in the variable name" . error .endif all: diff --git a/contrib/bmake/var.c b/contrib/bmake/var.c index eddca9ef6daa..6e5148bba968 100644 --- a/contrib/bmake/var.c +++ b/contrib/bmake/var.c @@ -1,4509 +1,4760 @@ -/* $NetBSD: var.c,v 1.807 2021/02/05 05:42:39 rillig Exp $ */ +/* $NetBSD: var.c,v 1.934 2021/06/21 08:40: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. */ /* * 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 * Var_SetExpand * Set the value of the variable, creating it if * necessary. * * Var_Append * Var_AppendExpand * Append more characters to the variable, creating it if * necessary. A space is placed between the old value and * the new one. * * Var_Exists * Var_ExistsExpand * 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 * Var_DeleteExpand * Delete a variable. * * Var_ReexportVars * 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 scope. * - * XXX: There's a lot of duplication in these functions. + * XXX: There's a lot of almost duplicate code in these functions that only + * differs in subtle details that are not mentioned in the manual page. */ #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.807 2021/02/05 05:42:39 rillig Exp $"); - -typedef enum VarFlags { - VAR_NONE = 0, - - /* - * 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 silently ignored, - * they are logged with -dv though. - */ - VAR_READONLY = 0x80 -} VarFlags; +MAKE_RCSID("$NetBSD: var.c,v 1.934 2021/06/21 08:40:44 rillig Exp $"); /* * 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: scope variables, environment variables, * undefined variables. * * Scope variables are stored in a GNode.scope. The only way to undefine * a scope 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 scope variables, it aliases the corresponding HashEntry name. * For environment and undefined variables, it is allocated. */ FStr name; /* The unexpanded value of the variable. */ Buffer val; - /* Miscellaneous status flags. */ - VarFlags flags; + + /* The variable came from the command line. */ + bool fromCmd: 1; + + /* + * 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. + */ + bool fromEnv: 1; + + /* + * The variable value cannot be changed anymore, and the variable + * cannot be deleted. Any attempts to do so are silently ignored, + * they are logged with -dv though. + * + * See VAR_SET_READONLY. + */ + bool readOnly: 1; + + /* + * The variable's value is currently being used by Var_Parse or + * Var_Subst. This marker is used to avoid endless recursion. + */ + bool inUse: 1; + + /* + * The variable is exported to the environment, to be used by child + * processes. + */ + bool exported: 1; + + /* + * 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. + */ + bool reexport: 1; } Var; /* - * Exporting vars is expensive so skip it if we can + * Exporting variables is expensive and may leak memory, so skip it if we + * can. + * + * To avoid this, it might be worth encapsulating the environment variables + * in a separate data structure called EnvVars. */ typedef enum VarExportedMode { VAR_EXPORTED_NONE, VAR_EXPORTED_SOME, VAR_EXPORTED_ALL } VarExportedMode; typedef enum UnexportWhat { + /* Unexport the variables given by name. */ UNEXPORT_NAMED, + /* + * Unexport all globals previously exported, but keep the environment + * inherited from the parent. + */ UNEXPORT_ALL, + /* + * Unexport all globals previously exported and clear the environment + * inherited from the parent. + */ UNEXPORT_ENV } UnexportWhat; /* Flags for pattern matching in the :S and :C modifiers */ -typedef struct VarPatternFlags { - - /* Replace as often as possible ('g') */ - Boolean subGlobal: 1; - /* Replace only once ('1') */ - Boolean subOnce: 1; - /* Match at start of word ('^') */ - Boolean anchorStart: 1; - /* Match at end of word ('$') */ - Boolean anchorEnd: 1; -} VarPatternFlags; - -/* SepBuf is a string being built from words, interleaved with separators. */ +typedef struct PatternFlags { + bool subGlobal: 1; /* 'g': replace as often as possible */ + bool subOnce: 1; /* '1': replace only once */ + bool anchorStart: 1; /* '^': match only at start of word */ + bool anchorEnd: 1; /* '$': match only at end of word */ +} PatternFlags; + +/* SepBuf builds a string from words interleaved with separators. */ typedef struct SepBuf { Buffer buf; - Boolean needSep; + bool needSep; /* Usually ' ', but see the ':ts' modifier. */ char sep; } SepBuf; -ENUM_FLAGS_RTTI_4(VarEvalFlags, - VARE_UNDEFERR, VARE_WANTRES, VARE_KEEP_DOLLAR, - VARE_KEEP_UNDEF); - /* * 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. + * + * See VARE_EVAL_KEEP_UNDEF. */ static char varUndefined[] = ""; /* * 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. + * 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; +static bool save_dollars = false; /* * A scope collects variable names and their values. * * The main scope is SCOPE_GLOBAL, which contains the variables that are set * in the makefiles. SCOPE_INTERNAL acts as a fallback for SCOPE_GLOBAL and * contains some internal make variables. These internal variables can thus * be overridden, they can also be restored by undefining the overriding * variable. * * SCOPE_CMDLINE contains variables from the command line arguments. These * override variables from SCOPE_GLOBAL. * * There is no scope for environment variables, these are generated on-the-fly * whenever they are referenced. If there were such a scope, each change to * environment variables would have to be reflected in that scope, which may * be simpler or more complex than the current implementation. * * Each target has its own scope, containing the 7 target-local variables * .TARGET, .ALLSRC, etc. No other variables are in these scopes. */ GNode *SCOPE_CMDLINE; GNode *SCOPE_GLOBAL; GNode *SCOPE_INTERNAL; -ENUM_FLAGS_RTTI_6(VarFlags, - VAR_IN_USE, VAR_FROM_ENV, - VAR_EXPORTED, VAR_REEXPORT, VAR_FROM_CMD, VAR_READONLY); - static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE; +static const char *VarEvalMode_Name[] = { + "parse-only", + "eval", + "eval-defined", + "eval-keep-dollar", + "eval-keep-undefined", + "eval-keep-dollar-and-undefined", +}; + static Var * -VarNew(FStr name, const char *value, VarFlags flags) +VarNew(FStr name, const char *value, bool fromEnv, bool readOnly) { size_t value_len = strlen(value); Var *var = bmake_malloc(sizeof *var); var->name = name; Buf_InitSize(&var->val, value_len + 1); Buf_AddBytes(&var->val, value, value_len); - var->flags = flags; + var->fromCmd = false; + var->fromEnv = fromEnv; + var->readOnly = readOnly; + var->inUse = false; + var->exported = false; + var->reexport = false; 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 == NULL) - Shell_Init(); - } - break; - case 'T': - if (strcmp(name, ".TARGET") == 0) - name = TARGET; - break; - } - } +static Substring +CanonicalVarname(Substring name) +{ + + if (!(Substring_Length(name) > 0 && name.start[0] == '.')) + return name; + + if (Substring_Equals(name, ".ALLSRC")) + return Substring_InitStr(ALLSRC); + if (Substring_Equals(name, ".ARCHIVE")) + return Substring_InitStr(ARCHIVE); + if (Substring_Equals(name, ".IMPSRC")) + return Substring_InitStr(IMPSRC); + if (Substring_Equals(name, ".MEMBER")) + return Substring_InitStr(MEMBER); + if (Substring_Equals(name, ".OODATE")) + return Substring_InitStr(OODATE); + if (Substring_Equals(name, ".PREFIX")) + return Substring_InitStr(PREFIX); + if (Substring_Equals(name, ".TARGET")) + return Substring_InitStr(TARGET); + + if (Substring_Equals(name, ".SHELL") && shellPath == NULL) + Shell_Init(); /* GNU make has an additional alias $^ == ${.ALLSRC}. */ return name; } static Var * -GNode_FindVar(GNode *scope, const char *varname, unsigned int hash) +GNode_FindVar(GNode *scope, Substring varname, unsigned int hash) { - return HashTable_FindValueHash(&scope->vars, varname, hash); + return HashTable_FindValueBySubstringHash(&scope->vars, varname, hash); } /* * Find the variable in the scope, and maybe in other scopes as well. * * Input: * name name to find, is not expanded any further * scope scope in which to look first - * elsewhere TRUE to look in other scopes as well + * elsewhere true to look in other scopes 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 *scope, Boolean elsewhere) +VarFindSubstring(Substring name, GNode *scope, bool 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. - */ + /* Replace '.TARGET' with '@', likewise for other local variables. */ name = CanonicalVarname(name); - nameHash = Hash_Hash(name); + nameHash = Hash_Substring(name); - /* First look for the variable in the given scope. */ var = GNode_FindVar(scope, name, nameHash); if (!elsewhere) return var; - /* - * The variable was not found in the given scope. - * Now look for it in the other scopes as well. - */ if (var == NULL && scope != SCOPE_CMDLINE) var = GNode_FindVar(SCOPE_CMDLINE, name, nameHash); if (!opts.checkEnvFirst && var == NULL && scope != SCOPE_GLOBAL) { var = GNode_FindVar(SCOPE_GLOBAL, name, nameHash); if (var == NULL && scope != SCOPE_INTERNAL) { /* SCOPE_INTERNAL is subordinate to SCOPE_GLOBAL */ var = GNode_FindVar(SCOPE_INTERNAL, name, nameHash); } } if (var == NULL) { - char *env; + FStr envName; + const char *envValue; - if ((env = getenv(name)) != NULL) { - char *varname = bmake_strdup(name); - return VarNew(FStr_InitOwn(varname), env, VAR_FROM_ENV); - } + /* + * TODO: try setting an environment variable with the empty + * name, which should be technically possible, just to see + * how make reacts. All .for loops should be broken then. + */ + envName = Substring_Str(name); + envValue = getenv(envName.str); + if (envValue != NULL) + return VarNew(envName, envValue, true, false); + FStr_Done(&envName); if (opts.checkEnvFirst && scope != SCOPE_GLOBAL) { var = GNode_FindVar(SCOPE_GLOBAL, name, nameHash); if (var == NULL && scope != SCOPE_INTERNAL) var = GNode_FindVar(SCOPE_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) +/* TODO: Replace these calls with VarFindSubstring, as far as possible. */ +static Var * +VarFind(const char *name, GNode *scope, bool elsewhere) +{ + return VarFindSubstring(Substring_InitStr(name), scope, elsewhere); +} + +/* If the variable is an environment variable, free it, including its value. */ +static void +VarFreeEnv(Var *v) { - if (!(v->flags & VAR_FROM_ENV)) - return FALSE; + if (!v->fromEnv) + return; FStr_Done(&v->name); - if (freeValue) - Buf_Done(&v->val); - else - Buf_DoneData(&v->val); + Buf_Done(&v->val); free(v); - return TRUE; } -/* - * Add a new variable of the given name and value to the given scope. - * The name and val arguments are duplicated so they may safely be freed. - */ -static void -VarAdd(const char *name, const char *val, GNode *scope, VarSetFlags flags) +/* Add a new variable of the given name and value to the given scope. */ +static Var * +VarAdd(const char *name, const char *value, GNode *scope, VarSetFlags flags) { HashEntry *he = HashTable_CreateEntry(&scope->vars, name, NULL); - Var *v = VarNew(FStr_InitRefer(/* aliased to */ he->key), val, - flags & VAR_SET_READONLY ? VAR_READONLY : VAR_NONE); + Var *v = VarNew(FStr_InitRefer(/* aliased to */ he->key), value, + false, (flags & VAR_SET_READONLY) != 0); HashEntry_Set(he, v); - DEBUG3(VAR, "%s:%s = %s\n", scope->name, name, val); + DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, value); + return v; } /* * Remove a variable from a scope, freeing all related memory as well. * The variable name is kept as-is, it is not expanded. */ void Var_Delete(GNode *scope, const char *varname) { HashEntry *he = HashTable_FindEntry(&scope->vars, varname); Var *v; if (he == NULL) { DEBUG2(VAR, "%s:delete %s (not found)\n", scope->name, varname); return; } DEBUG2(VAR, "%s:delete %s\n", scope->name, varname); - v = HashEntry_Get(he); - if (v->flags & VAR_EXPORTED) + v = he->value; + if (v->exported) unsetenv(v->name.str); if (strcmp(v->name.str, MAKE_EXPORTED) == 0) var_exportedVars = VAR_EXPORTED_NONE; assert(v->name.freeIt == NULL); HashTable_DeleteEntry(&scope->vars, he); Buf_Done(&v->val); free(v); } /* * Remove a variable from a scope, freeing all related memory as well. * The variable name is expanded once. */ void Var_DeleteExpand(GNode *scope, const char *name) { FStr varname = FStr_InitRefer(name); if (strchr(varname.str, '$') != NULL) { char *expanded; (void)Var_Subst(varname.str, SCOPE_GLOBAL, VARE_WANTRES, &expanded); /* TODO: handle errors */ varname = FStr_InitOwn(expanded); } Var_Delete(scope, varname.str); FStr_Done(&varname); } /* * Undefine one or more variables from the global scope. * The argument is expanded exactly once and then split into words. */ void Var_Undef(const char *arg) { VarParseResult vpr; char *expanded; Words varnames; size_t i; if (arg[0] == '\0') { Parse_Error(PARSE_FATAL, "The .undef directive requires an argument"); return; } vpr = Var_Subst(arg, SCOPE_GLOBAL, VARE_WANTRES, &expanded); if (vpr != VPR_OK) { Parse_Error(PARSE_FATAL, "Error in variable names to be undefined"); return; } - varnames = Str_Words(expanded, FALSE); + varnames = Str_Words(expanded, false); if (varnames.len == 1 && varnames.words[0][0] == '\0') varnames.len = 0; for (i = 0; i < varnames.len; i++) { const char *varname = varnames.words[i]; Global_Delete(varname); } Words_Free(varnames); free(expanded); } -static Boolean +static bool MayExport(const char *name) { if (name[0] == '.') - return FALSE; /* skip internals */ + return false; /* skip internals */ if (name[0] == '-') - return FALSE; /* skip misnamed variables */ + return false; /* skip misnamed variables */ if (name[1] == '\0') { /* * A single char. * If it is one of the variables that should only appear in * local scope, skip it, else we can get Var_Subst * into a loop. */ switch (name[0]) { case '@': case '%': case '*': case '!': - return FALSE; + return false; } } - return TRUE; + return true; } -static Boolean +static bool ExportVarEnv(Var *v) { const char *name = v->name.str; char *val = v->val.data; char *expr; - if ((v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT)) - return FALSE; /* nothing to do */ + if (v->exported && !v->reexport) + return false; /* nothing to do */ if (strchr(val, '$') == NULL) { - if (!(v->flags & VAR_EXPORTED)) + if (!v->exported) setenv(name, val, 1); - return TRUE; + return true; } - if (v->flags & VAR_IN_USE) { + if (v->inUse) { /* * We recursed while exporting in a child. * This isn't going to end well, just skip it. */ - return FALSE; + return false; } /* XXX: name is injected without escaping it */ expr = str_concat3("${", name, "}"); (void)Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES, &val); /* TODO: handle errors */ setenv(name, val, 1); free(val); free(expr); - return TRUE; + return true; } -static Boolean +static bool ExportVarPlain(Var *v) { if (strchr(v->val.data, '$') == NULL) { setenv(v->name.str, v->val.data, 1); - v->flags |= VAR_EXPORTED; - v->flags &= ~(unsigned)VAR_REEXPORT; - return TRUE; + v->exported = true; + v->reexport = false; + return true; } /* * 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. + * Avoid calling setenv more often than necessary since it can leak. */ - v->flags |= VAR_EXPORTED | VAR_REEXPORT; - return TRUE; + v->exported = true; + v->reexport = true; + return true; } -static Boolean +static bool ExportVarLiteral(Var *v) { - if ((v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT)) - return FALSE; + if (v->exported && !v->reexport) + return false; - if (!(v->flags & VAR_EXPORTED)) + if (!v->exported) setenv(v->name.str, v->val.data, 1); - return TRUE; + return true; } /* - * Export a single variable. + * Mark a single variable to be exported later for subprocesses. * - * 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. + * Internal variables (those starting with '.') are not exported. */ -static Boolean +static bool ExportVar(const char *name, VarExportMode mode) { Var *v; if (!MayExport(name)) - return FALSE; + return false; - v = VarFind(name, SCOPE_GLOBAL, FALSE); + v = VarFind(name, SCOPE_GLOBAL, false); if (v == NULL) - return FALSE; + return false; if (mode == VEM_ENV) return ExportVarEnv(v); else if (mode == VEM_PLAIN) return ExportVarPlain(v); else return ExportVarLiteral(v); } /* * Actually export the variables that have been marked as needing to be * re-exported. */ void Var_ReexportVars(void) { char *xvarnames; /* * Several make implementations 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]; + char tmp[21]; 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... */ + /* Ouch! Exporting all variables at once is crazy. */ HashIter_Init(&hi, &SCOPE_GLOBAL->vars); while (HashIter_Next(&hi) != NULL) { Var *var = hi.entry->value; ExportVar(var->name.str, VEM_ENV); } return; } (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", SCOPE_GLOBAL, VARE_WANTRES, &xvarnames); /* TODO: handle errors */ if (xvarnames[0] != '\0') { - Words varnames = Str_Words(xvarnames, FALSE); + Words varnames = Str_Words(xvarnames, false); size_t i; for (i = 0; i < varnames.len; i++) ExportVar(varnames.words[i], VEM_ENV); Words_Free(varnames); } free(xvarnames); } static void -ExportVars(const char *varnames, Boolean isExport, VarExportMode mode) +ExportVars(const char *varnames, bool isExport, VarExportMode mode) +/* TODO: try to combine the parameters 'isExport' and 'mode'. */ { - Words words = Str_Words(varnames, FALSE); + Words words = Str_Words(varnames, false); size_t i; if (words.len == 1 && words.words[0][0] == '\0') words.len = 0; for (i = 0; i < words.len; i++) { const char *varname = words.words[i]; if (!ExportVar(varname, mode)) continue; if (var_exportedVars == VAR_EXPORTED_NONE) var_exportedVars = VAR_EXPORTED_SOME; if (isExport && mode == VEM_PLAIN) Global_Append(MAKE_EXPORTED, varname); } Words_Free(words); } static void -ExportVarsExpand(const char *uvarnames, Boolean isExport, VarExportMode mode) +ExportVarsExpand(const char *uvarnames, bool isExport, VarExportMode mode) { char *xvarnames; (void)Var_Subst(uvarnames, SCOPE_GLOBAL, VARE_WANTRES, &xvarnames); /* TODO: handle errors */ ExportVars(xvarnames, isExport, mode); free(xvarnames); } /* Export the named variables, or all variables. */ void Var_Export(VarExportMode mode, const char *varnames) { if (mode == VEM_PLAIN && varnames[0] == '\0') { var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */ return; } - ExportVarsExpand(varnames, TRUE, mode); + ExportVarsExpand(varnames, true, mode); } void Var_ExportVars(const char *varnames) { - ExportVarsExpand(varnames, FALSE, VEM_PLAIN); + ExportVarsExpand(varnames, false, VEM_PLAIN); } extern char **environ; static void ClearEnv(void) { 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 != NULL) { 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 != NULL && *cp != '\0') setenv(MAKE_LEVEL_ENV, cp, 1); } static void -GetVarnamesToUnexport(Boolean isEnv, const char *arg, +GetVarnamesToUnexport(bool isEnv, const char *arg, FStr *out_varnames, UnexportWhat *out_what) { UnexportWhat what; FStr varnames = FStr_InitRefer(""); if (isEnv) { if (arg[0] != '\0') { Parse_Error(PARSE_FATAL, "The directive .unexport-env does not take " "arguments"); + /* continue anyway */ } what = UNEXPORT_ENV; } else { what = arg[0] != '\0' ? UNEXPORT_NAMED : UNEXPORT_ALL; if (what == UNEXPORT_NAMED) varnames = FStr_InitRefer(arg); } if (what != UNEXPORT_NAMED) { char *expanded; /* Using .MAKE.EXPORTED */ (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", SCOPE_GLOBAL, VARE_WANTRES, &expanded); /* TODO: handle errors */ varnames = FStr_InitOwn(expanded); } *out_varnames = varnames; *out_what = what; } static void UnexportVar(const char *varname, UnexportWhat what) { - Var *v = VarFind(varname, SCOPE_GLOBAL, FALSE); + Var *v = VarFind(varname, SCOPE_GLOBAL, false); if (v == NULL) { DEBUG1(VAR, "Not unexporting \"%s\" (not found)\n", varname); return; } DEBUG1(VAR, "Unexporting \"%s\"\n", varname); - if (what != UNEXPORT_ENV && - (v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT)) + if (what != UNEXPORT_ENV && v->exported && !v->reexport) unsetenv(v->name.str); - v->flags &= ~(unsigned)(VAR_EXPORTED | VAR_REEXPORT); + v->exported = false; + v->reexport = false; if (what == UNEXPORT_NAMED) { /* Remove the variable names from .MAKE.EXPORTED. */ /* XXX: v->name is injected without escaping it */ char *expr = str_concat3("${" MAKE_EXPORTED ":N", v->name.str, "}"); char *cp; (void)Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES, &cp); /* TODO: handle errors */ Global_Set(MAKE_EXPORTED, cp); free(cp); free(expr); } } static void UnexportVars(FStr *varnames, UnexportWhat what) { size_t i; Words words; if (what == UNEXPORT_ENV) ClearEnv(); - words = Str_Words(varnames->str, FALSE); + words = Str_Words(varnames->str, false); for (i = 0; i < words.len; i++) { const char *varname = words.words[i]; UnexportVar(varname, what); } Words_Free(words); if (what != UNEXPORT_NAMED) Global_Delete(MAKE_EXPORTED); } /* * This is called when .unexport[-env] is seen. * * str must have the form "unexport[-env] varname...". */ void -Var_UnExport(Boolean isEnv, const char *arg) +Var_UnExport(bool isEnv, const char *arg) { UnexportWhat what; FStr varnames; GetVarnamesToUnexport(isEnv, arg, &varnames, &what); UnexportVars(&varnames, what); FStr_Done(&varnames); } +/* + * When there is a variable of the same name in the command line scope, the + * global variable would not be visible anywhere. Therefore there is no + * point in setting it at all. + * + * See 'scope == SCOPE_CMDLINE' in Var_SetWithFlags. + */ +static bool +ExistsInCmdline(const char *name, const char *val) +{ + Var *v; + + v = VarFind(name, SCOPE_CMDLINE, false); + if (v == NULL) + return false; + + if (v->fromCmd) { + DEBUG3(VAR, "%s: %s = %s ignored!\n", + SCOPE_GLOBAL->name, name, val); + return true; + } + + VarFreeEnv(v); + return false; +} + /* Set the variable to the value; the name is not expanded. */ void Var_SetWithFlags(GNode *scope, const char *name, const char *val, VarSetFlags flags) { Var *v; assert(val != NULL); if (name[0] == '\0') { DEBUG0(VAR, "SetVar: variable name is empty - ignored\n"); return; } - if (scope == SCOPE_GLOBAL) { - v = VarFind(name, SCOPE_CMDLINE, FALSE); - if (v != NULL) { - if (v->flags & VAR_FROM_CMD) { - DEBUG3(VAR, "%s:%s = %s ignored!\n", - scope->name, name, val); - return; - } - VarFreeEnv(v, TRUE); - } - } + if (scope == SCOPE_GLOBAL && ExistsInCmdline(name, val)) + return; /* * Only look for a variable in the given scope since anything set * here will override anything in a lower scope, so there's not much - * point in searching them all just to save a bit of memory... + * point in searching them all. */ - v = VarFind(name, scope, FALSE); + v = VarFind(name, scope, false); if (v == NULL) { if (scope == SCOPE_CMDLINE && !(flags & VAR_SET_NO_EXPORT)) { /* * This var would normally prevent the same name being * added to SCOPE_GLOBAL, so delete it from there if * needed. Otherwise -V name may show the wrong value. + * + * See ExistsInCmdline. */ - /* XXX: name is expanded for the second time */ - Var_DeleteExpand(SCOPE_GLOBAL, name); + Var_Delete(SCOPE_GLOBAL, name); } - VarAdd(name, val, scope, flags); + v = VarAdd(name, val, scope, flags); } else { - if ((v->flags & VAR_READONLY) && !(flags & VAR_SET_READONLY)) { - DEBUG3(VAR, "%s:%s = %s ignored (read-only)\n", + if (v->readOnly && !(flags & VAR_SET_READONLY)) { + DEBUG3(VAR, "%s: %s = %s ignored (read-only)\n", scope->name, name, val); return; } Buf_Empty(&v->val); Buf_AddStr(&v->val, val); - DEBUG3(VAR, "%s:%s = %s\n", scope->name, name, val); - if (v->flags & VAR_EXPORTED) + DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, val); + if (v->exported) ExportVar(name, VEM_PLAIN); } + /* * Any variables given on the command line are automatically exported - * to the environment (as per POSIX standard) - * Other than internals. + * to the environment (as per POSIX standard), except for internals. */ if (scope == SCOPE_CMDLINE && !(flags & VAR_SET_NO_EXPORT) && name[0] != '.') { - if (v == NULL) - v = VarFind(name, scope, FALSE); /* we just added it */ - v->flags |= VAR_FROM_CMD; + v->fromCmd = true; /* * 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); + /* XXX: What about .MAKE.EXPORTED? */ + /* XXX: Why not just mark the variable for needing export, + * as in ExportVarPlain? */ Global_Append(MAKEOVERRIDES, name); } + if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0) save_dollars = ParseBoolean(val, save_dollars); if (v != NULL) - VarFreeEnv(v, TRUE); + VarFreeEnv(v); } /* See Var_Set for documentation. */ void Var_SetExpandWithFlags(GNode *scope, const char *name, const char *val, VarSetFlags flags) { const char *unexpanded_name = name; FStr varname = FStr_InitRefer(name); assert(val != NULL); if (strchr(varname.str, '$') != NULL) { char *expanded; (void)Var_Subst(varname.str, scope, VARE_WANTRES, &expanded); /* TODO: handle errors */ varname = FStr_InitOwn(expanded); } if (varname.str[0] == '\0') { - DEBUG2(VAR, "Var_Set(\"%s\", \"%s\", ...) " - "name expands to empty string - ignored\n", + DEBUG2(VAR, + "Var_SetExpand: variable name \"%s\" expands " + "to empty string, with value \"%s\" - ignored\n", unexpanded_name, val); } else Var_SetWithFlags(scope, varname.str, val, flags); FStr_Done(&varname); } void Var_Set(GNode *scope, const char *name, const char *val) { Var_SetWithFlags(scope, name, val, VAR_SET_NONE); } /* * Set the variable name to the value val in the given scope. * * 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 * scope scope in which to set it */ void Var_SetExpand(GNode *scope, const char *name, const char *val) { Var_SetExpandWithFlags(scope, name, val, VAR_SET_NONE); } void Global_Set(const char *name, const char *value) { Var_Set(SCOPE_GLOBAL, name, value); } void Global_SetExpand(const char *name, const char *value) { Var_SetExpand(SCOPE_GLOBAL, name, value); } void Global_Delete(const char *name) { Var_Delete(SCOPE_GLOBAL, name); } /* * Append the value to the named variable. * * If the variable doesn't exist, it is created. Otherwise a single space * and the given value are appended. */ void Var_Append(GNode *scope, const char *name, const char *val) { Var *v; v = VarFind(name, scope, scope == SCOPE_GLOBAL); if (v == NULL) { Var_SetWithFlags(scope, name, val, VAR_SET_NONE); - } else if (v->flags & VAR_READONLY) { + } else if (v->readOnly) { DEBUG1(VAR, "Ignoring append to %s since it is read-only\n", name); - } else if (scope == SCOPE_CMDLINE || !(v->flags & VAR_FROM_CMD)) { + } else if (scope == SCOPE_CMDLINE || !v->fromCmd) { Buf_AddByte(&v->val, ' '); Buf_AddStr(&v->val, val); - DEBUG3(VAR, "%s:%s = %s\n", scope->name, name, v->val.data); + DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, v->val.data); - if (v->flags & VAR_FROM_ENV) { + if (v->fromEnv) { /* * If the original variable came from the environment, * we have to install it in the global scope (we * could place it in the environment, but then we * should provide a way to export other variables...) */ - v->flags &= ~(unsigned)VAR_FROM_ENV; + v->fromEnv = false; /* * This is the only place where a variable is * created whose v->name is not the same as * scope->vars->key. */ HashTable_Set(&scope->vars, name, v); } } } /* * The variable of the given name has the given value appended to it in the * given scope. * * 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 * scope scope in which this should occur * * Notes: * Only if the variable is being sought in the global scope is the * environment searched. * XXX: Knows its calling circumstances in that if called with scope * an actual target, it will only search that scope since only * a local variable could be being appended to. This is actually * a big win and must be tolerated. */ void Var_AppendExpand(GNode *scope, const char *name, const char *val) { - char *name_freeIt = NULL; + FStr xname = FStr_InitRefer(name); assert(val != NULL); if (strchr(name, '$') != NULL) { - const char *unexpanded_name = name; - (void)Var_Subst(name, scope, VARE_WANTRES, &name_freeIt); + char *expanded; + (void)Var_Subst(name, scope, VARE_WANTRES, &expanded); /* TODO: handle errors */ - name = name_freeIt; - if (name[0] == '\0') { - /* TODO: update function name in the debug message */ - DEBUG2(VAR, "Var_Append(\"%s\", \"%s\", ...) " - "name expands to empty string - ignored\n", - unexpanded_name, val); - free(name_freeIt); + xname = FStr_InitOwn(expanded); + if (expanded[0] == '\0') { + DEBUG2(VAR, + "Var_AppendExpand: variable name \"%s\" expands " + "to empty string, with value \"%s\" - ignored\n", + name, val); + FStr_Done(&xname); return; } } - Var_Append(scope, name, val); + Var_Append(scope, xname.str, val); - free(name_freeIt); + FStr_Done(&xname); } void Global_Append(const char *name, const char *value) { Var_Append(SCOPE_GLOBAL, name, value); } -Boolean +bool Var_Exists(GNode *scope, const char *name) { - Var *v = VarFind(name, scope, TRUE); + Var *v = VarFind(name, scope, true); if (v == NULL) - return FALSE; + return false; - (void)VarFreeEnv(v, TRUE); - return TRUE; + VarFreeEnv(v); + return true; } /* * See if the given variable exists, in the given scope or in other * fallback scopes. * * Input: * name Variable to find, is expanded once * scope Scope in which to start search */ -Boolean +bool Var_ExistsExpand(GNode *scope, const char *name) { FStr varname = FStr_InitRefer(name); - Boolean exists; + bool exists; if (strchr(varname.str, '$') != NULL) { char *expanded; (void)Var_Subst(varname.str, scope, VARE_WANTRES, &expanded); /* TODO: handle errors */ varname = FStr_InitOwn(expanded); } exists = Var_Exists(scope, varname.str); FStr_Done(&varname); return exists; } /* * Return the unexpanded value of the given variable in the given scope, * or the usual scopes. * * Input: * name name to find, is not expanded any further * scope scope 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. + * The value is valid until the next modification to any variable. */ FStr Var_Value(GNode *scope, const char *name) { - Var *v = VarFind(name, scope, TRUE); + Var *v = VarFind(name, scope, true); char *value; if (v == NULL) return FStr_InitRefer(NULL); - value = v->val.data; - return VarFreeEnv(v, FALSE) - ? FStr_InitOwn(value) - : FStr_InitRefer(value); + if (!v->fromEnv) + return FStr_InitRefer(v->val.data); + + /* Since environment variables are short-lived, free it now. */ + FStr_Done(&v->name); + value = Buf_DoneData(&v->val); + free(v); + return FStr_InitOwn(value); } /* * Return the unexpanded variable value from this node, without trying to look * up the variable in any other scope. */ const char * GNode_ValueDirect(GNode *gn, const char *name) { - Var *v = VarFind(name, gn, FALSE); + Var *v = VarFind(name, gn, false); return v != NULL ? v->val.data : NULL; } +static VarEvalMode +VarEvalMode_WithoutKeepDollar(VarEvalMode emode) +{ + if (emode == VARE_KEEP_DOLLAR_UNDEF) + return VARE_EVAL_KEEP_UNDEF; + if (emode == VARE_EVAL_KEEP_DOLLAR) + return VARE_WANTRES; + return emode; +} + +static VarEvalMode +VarEvalMode_UndefOk(VarEvalMode emode) +{ + return emode == VARE_UNDEFERR ? VARE_WANTRES : emode; +} + +static bool +VarEvalMode_ShouldEval(VarEvalMode emode) +{ + return emode != VARE_PARSE_ONLY; +} + +static bool +VarEvalMode_ShouldKeepUndef(VarEvalMode emode) +{ + return emode == VARE_EVAL_KEEP_UNDEF || + emode == VARE_KEEP_DOLLAR_UNDEF; +} + +static bool +VarEvalMode_ShouldKeepDollar(VarEvalMode emode) +{ + return emode == VARE_EVAL_KEEP_DOLLAR || + emode == VARE_KEEP_DOLLAR_UNDEF; +} + static void SepBuf_Init(SepBuf *buf, char sep) { Buf_InitSize(&buf->buf, 32); - buf->needSep = FALSE; + buf->needSep = false; buf->sep = sep; } static void SepBuf_Sep(SepBuf *buf) { - buf->needSep = TRUE; + 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->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 void +SepBuf_AddSubstring(SepBuf *buf, Substring sub) +{ + SepBuf_AddBytesBetween(buf, sub.start, sub.end); +} + static char * SepBuf_DoneData(SepBuf *buf) { return Buf_DoneData(&buf->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". + * For example, when evaluating the modifier ':M*b' in ${:Ua b c:M*b}, the + * callback is called 3 times, once for "a", "b" and "c". + * + * Some ModifyWord functions assume that they are always passed a + * null-terminated substring, which is currently guaranteed but may change in + * the future. */ -typedef void (*ModifyWordsCallback)(const char *word, SepBuf *buf, void *data); +typedef void (*ModifyWordProc)(Substring word, SepBuf *buf, void *data); /* * Callback for ModifyWords to implement the :H modifier. * Add the dirname of the given word to the buffer. */ /*ARGSUSED*/ static void -ModifyWord_Head(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) +ModifyWord_Head(Substring 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, "."); + SepBuf_AddSubstring(buf, Substring_Dirname(word)); } /* * Callback for ModifyWords to implement the :T modifier. * Add the basename of the given word to the buffer. */ /*ARGSUSED*/ static void -ModifyWord_Tail(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) +ModifyWord_Tail(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { - SepBuf_AddStr(buf, str_basename(word)); + SepBuf_AddSubstring(buf, Substring_Basename(word)); } /* * Callback for ModifyWords to implement the :E modifier. * Add the filename suffix of the given word to the buffer, if it exists. */ /*ARGSUSED*/ static void -ModifyWord_Suffix(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) +ModifyWord_Suffix(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { - const char *lastDot = strrchr(word, '.'); + const char *lastDot = Substring_LastIndex(word, '.'); if (lastDot != NULL) - SepBuf_AddStr(buf, lastDot + 1); + SepBuf_AddBytesBetween(buf, lastDot + 1, word.end); } /* * Callback for ModifyWords to implement the :R modifier. - * Add the basename of the given word to the buffer. + * Add the filename without extension of the given word to the buffer. */ /*ARGSUSED*/ static void -ModifyWord_Root(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) +ModifyWord_Root(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { - const char *lastDot = strrchr(word, '.'); - size_t len = lastDot != NULL ? (size_t)(lastDot - word) : strlen(word); - SepBuf_AddBytes(buf, word, len); + const char *lastDot, *end; + + lastDot = Substring_LastIndex(word, '.'); + end = lastDot != NULL ? lastDot : word.end; + SepBuf_AddBytesBetween(buf, word.start, end); } /* * 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) +ModifyWord_Match(Substring word, SepBuf *buf, void *data) { const char *pattern = data; - DEBUG2(VAR, "VarMatch [%s] [%s]\n", word, pattern); - if (Str_Match(word, pattern)) - SepBuf_AddStr(buf, word); + + assert(word.end[0] == '\0'); /* assume null-terminated word */ + if (Str_Match(word.start, pattern)) + SepBuf_AddSubstring(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) +ModifyWord_NoMatch(Substring 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] == '\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; + assert(word.end[0] == '\0'); /* assume null-terminated word */ + if (!Str_Match(word.start, pattern)) + SepBuf_AddSubstring(buf, word); } -struct ModifyWord_SYSVSubstArgs { +#ifdef SYSVVARSUB +struct ModifyWord_SysVSubstArgs { GNode *scope; - const char *lhs; + Substring lhsPrefix; + bool lhsPercent; + Substring lhsSuffix; const char *rhs; }; /* Callback for ModifyWords to implement the :%.from=%.to modifier. */ static void -ModifyWord_SYSVSubst(const char *word, SepBuf *buf, void *data) +ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data) { - const struct ModifyWord_SYSVSubstArgs *args = data; - char *rhs_expanded; - const char *rhs; + const struct ModifyWord_SysVSubstArgs *args = data; + FStr rhs; + char *rhsExp; 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); + if (Substring_IsEmpty(word)) return; - } - /* - * Append rhs to the buffer, substituting the first '%' with the - * match, but only if the lhs had a '%' as well. - */ + if (!Substring_HasPrefix(word, args->lhsPrefix)) + goto no_match; + if (!Substring_HasSuffix(word, args->lhsSuffix)) + goto no_match; - (void)Var_Subst(args->rhs, args->scope, VARE_WANTRES, &rhs_expanded); - /* TODO: handle errors */ + rhs = FStr_InitRefer(args->rhs); + if (strchr(rhs.str, '$') != NULL) { + (void)Var_Subst(args->rhs, args->scope, VARE_WANTRES, &rhsExp); + /* TODO: handle errors */ + rhs = FStr_InitOwn(rhsExp); + } - rhs = rhs_expanded; - percent = strchr(rhs, '%'); + percent = args->lhsPercent ? strchr(rhs.str, '%') : NULL; - 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); + if (percent != NULL) + SepBuf_AddBytesBetween(buf, rhs.str, percent); + if (percent != NULL || !args->lhsPercent) + SepBuf_AddBytesBetween(buf, + word.start + Substring_Length(args->lhsPrefix), + word.end - Substring_Length(args->lhsSuffix)); + SepBuf_AddStr(buf, percent != NULL ? percent + 1 : rhs.str); - /* Append the suffix of the replacement pattern */ - SepBuf_AddStr(buf, rhs); + FStr_Done(&rhs); + return; - free(rhs_expanded); +no_match: + SepBuf_AddSubstring(buf, word); } #endif struct ModifyWord_SubstArgs { - const char *lhs; - size_t lhsLen; - const char *rhs; - size_t rhsLen; - VarPatternFlags pflags; - Boolean matched; + Substring lhs; + Substring rhs; + PatternFlags pflags; + bool matched; }; +static const char * +Substring_Find(Substring haystack, Substring needle) +{ + size_t len, needleLen, i; + + len = Substring_Length(haystack); + needleLen = Substring_Length(needle); + for (i = 0; i + needleLen <= len; i++) + if (memcmp(haystack.start + i, needle.start, needleLen) == 0) + return haystack.start + i; + return NULL; +} + /* * 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) +ModifyWord_Subst(Substring word, SepBuf *buf, void *data) { - size_t wordLen = strlen(word); struct ModifyWord_SubstArgs *args = data; - const char *match; + size_t wordLen, lhsLen; + const char *wordEnd, *match; + wordLen = Substring_Length(word); + wordEnd = word.end; if (args->pflags.subOnce && args->matched) goto nosub; + lhsLen = Substring_Length(args->lhs); if (args->pflags.anchorStart) { - if (wordLen < args->lhsLen || - memcmp(word, args->lhs, args->lhsLen) != 0) + if (wordLen < lhsLen || + memcmp(word.start, args->lhs.start, lhsLen) != 0) goto nosub; - if ((args->pflags.anchorEnd) && wordLen != args->lhsLen) + if (args->pflags.anchorEnd && wordLen != 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; + SepBuf_AddSubstring(buf, args->rhs); + SepBuf_AddBytesBetween(buf, word.start + lhsLen, wordEnd); + args->matched = true; return; } if (args->pflags.anchorEnd) { - const char *start; - - if (wordLen < args->lhsLen) + if (wordLen < lhsLen) goto nosub; - - start = word + (wordLen - args->lhsLen); - if (memcmp(start, args->lhs, args->lhsLen) != 0) + if (memcmp(wordEnd - lhsLen, args->lhs.start, lhsLen) != 0) goto nosub; /* :S,suffix$,replacement, */ - SepBuf_AddBytesBetween(buf, word, start); - SepBuf_AddBytes(buf, args->rhs, args->rhsLen); - args->matched = TRUE; + SepBuf_AddBytesBetween(buf, word.start, wordEnd - lhsLen); + SepBuf_AddSubstring(buf, args->rhs); + args->matched = true; return; } - if (args->lhs[0] == '\0') + if (Substring_IsEmpty(args->lhs)) 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.subGlobal) + while ((match = Substring_Find(word, args->lhs)) != NULL) { + SepBuf_AddBytesBetween(buf, word.start, match); + SepBuf_AddSubstring(buf, args->rhs); + args->matched = true; + word.start = match + lhsLen; + if (Substring_IsEmpty(word) || !args->pflags.subGlobal) break; } nosub: - SepBuf_AddBytes(buf, word, wordLen); + SepBuf_AddSubstring(buf, word); } #ifndef NO_REGEX /* Print the error caused by a regcomp or regexec call. */ static void VarREError(int reerr, const regex_t *pat, const char *str) { 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; + const char *replace; + PatternFlags pflags; + bool 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) +ModifyWord_SubstRegex(Substring word, SepBuf *buf, void *data) { struct ModifyWord_SubstRegexArgs *args = data; int xrv; - const char *wp = word; - char *rp; + const char *wp; + const char *rp; int flags = 0; regmatch_t m[10]; + assert(word.end[0] == '\0'); /* assume null-terminated word */ + wp = word.start; if (args->pflags.subOnce && args->matched) goto nosub; tryagain: xrv = regexec(&args->re, wp, args->nsub, m, flags); switch (xrv) { case 0: - args->matched = TRUE; + args->matched = true; SepBuf_AddBytes(buf, wp, (size_t)m[0].rm_so); + /* + * Replacement of regular expressions is not specified by + * POSIX, therefore implement it here. + */ + for (rp = args->replace; *rp != '\0'; 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 \\%u", (unsigned)n); } else if (m[n].rm_so == -1) { - Error( - "No match for subexpression \\%u", - (unsigned)n); + if (opts.strict) { + Error( + "No match for subexpression \\%u", + (unsigned)n); + } } else { SepBuf_AddBytesBetween(buf, wp + m[n].rm_so, wp + m[n].rm_eo); } } } wp += m[0].rm_eo; if (args->pflags.subGlobal) { flags |= REG_NOTBOL; if (m[0].rm_so == 0 && m[0].rm_eo == 0) { SepBuf_AddBytes(buf, wp, 1); wp++; } if (*wp != '\0') goto tryagain; } if (*wp != '\0') 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 *scope; - char *tvar; /* name of temporary variable */ - char *str; /* string to expand */ - VarEvalFlags eflags; + const char *var; /* name of the temporary variable */ + const char *body; /* string to expand */ + VarEvalMode emode; }; /* Callback for ModifyWords to implement the :@var@...@ modifier of ODE make. */ static void -ModifyWord_Loop(const char *word, SepBuf *buf, void *data) +ModifyWord_Loop(Substring word, SepBuf *buf, void *data) { const struct ModifyWord_LoopArgs *args; char *s; - if (word[0] == '\0') + if (Substring_IsEmpty(word)) return; args = data; - /* XXX: The variable name should not be expanded here. */ - Var_SetExpandWithFlags(args->scope, args->tvar, word, + assert(word.end[0] == '\0'); /* assume null-terminated word */ + Var_SetWithFlags(args->scope, args->var, word.start, VAR_SET_NO_EXPORT); - (void)Var_Subst(args->str, args->scope, args->eflags, &s); + (void)Var_Subst(args->body, args->scope, args->emode, &s); /* TODO: handle errors */ + assert(word.end[0] == '\0'); /* assume null-terminated word */ DEBUG4(VAR, "ModifyWord_Loop: " "in \"%s\", replace \"%s\" with \"%s\" to \"%s\"\n", - word, args->tvar, args->str, s); + word.start, args->var, args->body, s); if (s[0] == '\n' || Buf_EndsWith(&buf->buf, '\n')) - buf->needSep = FALSE; + 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) +VarSelectWords(const char *str, int first, int last, + char sep, bool oneBigWord) { 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(words.words[0])); words.freeIt = bmake_strdup(str); words.words[0] = words.freeIt; words.words[1] = NULL; } else { - words = Str_Words(str, FALSE); + 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 len, * -2 gets converted to (len - 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_DoneData(&buf); } /* * Callback for ModifyWords to implement the :tA modifier. * Replace each word with the result of realpath() if successful. */ /*ARGSUSED*/ static void -ModifyWord_Realpath(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) +ModifyWord_Realpath(Substring word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) { struct stat st; char rbuf[MAXPATHLEN]; + const char *rp; - const char *rp = cached_realpath(word, rbuf); + assert(word.end[0] == '\0'); /* assume null-terminated word */ + rp = cached_realpath(word.start, 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_DoneData(&result); - } - - SepBuf_Init(&result, sep); - - words = Str_Words(str, FALSE); - - DEBUG2(VAR, "ModifyWords: split \"%s\" into %u words\n", - str, (unsigned)words.len); - - for (i = 0; i < words.len; i++) { - modifyWord(words.words[i], &result, modifyWord_args); - if (result.buf.len > 0) - SepBuf_Sep(&result); - } - - Words_Free(words); - - return SepBuf_DoneData(&result); + SepBuf_AddStr(buf, rp); + else + SepBuf_AddSubstring(buf, word); } static char * Words_JoinFree(Words words) { Buffer buf; size_t i; Buf_Init(&buf); for (i = 0; i < words.len; i++) { if (i != 0) { - /* XXX: Use st->sep instead of ' ', for consistency. */ + /* XXX: Use ch->sep instead of ' ', for consistency. */ Buf_AddByte(&buf, ' '); } Buf_AddStr(&buf, words.words[i]); } Words_Free(words); return Buf_DoneData(&buf); } -/* 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) +static void +VarQuote(const char *str, bool quoteDollar, LazyBuf *buf) { - Buffer buf; - Buf_Init(&buf); + const char *p; - for (; *str != '\0'; str++) { - if (*str == '\n') { + LazyBuf_Init(buf, str); + for (p = str; *p != '\0'; p++) { + if (*p == '\n') { const char *newline = Shell_GetNewline(); if (newline == NULL) newline = "\\\n"; - Buf_AddStr(&buf, newline); + LazyBuf_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, "\\$"); + if (ch_isspace(*p) || is_shell_metachar((unsigned char)*p)) + LazyBuf_Add(buf, '\\'); + LazyBuf_Add(buf, *p); + if (quoteDollar && *p == '$') + LazyBuf_AddStr(buf, "\\$"); } - - return Buf_DoneData(&buf); } /* * 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 != 0;) { 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) +VarStrftime(const char *fmt, bool zulu, time_t tim) { char buf[BUFSIZ]; if (tim == 0) time(&tim); if (*fmt == '\0') fmt = "%c"; strftime(buf, sizeof buf, fmt, zulu ? gmtime(&tim) : localtime(&tim)); buf[sizeof buf - 1] = '\0'; return bmake_strdup(buf); } /* * 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 + * Their task is to apply a single modifier to the expression. This involves + * parsing the modifier, evaluating it and finally updating the value of the + * expression. * * 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. The modifier doesn't have to check for this delimiter character, + * ch->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 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.) + * undefined behavior. Not in the sense of C, but still the resulting string + * is garbage.) * * 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. + * during parsing though. For most modifiers this doesn't matter since their + * only noticeable effect is that the update the value of the expression. + * Some modifiers such as ':sh' or '::=' have noticeable side effects though. * * Evaluating the modifier usually takes the current value of the variable - * expression from st->val, or the variable name from st->var->name and stores - * the result in st->newVal. + * expression from ch->expr->value, or the variable name from ch->var->name + * and stores the result back in expr->value via Expr_SetValueOwn or + * Expr_SetValueRefer. * * 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). + * expressions (see Expr_Define). * * Some modifiers need to free some memory. */ -typedef enum VarExprStatus { - /* The variable expression is based in a regular, defined variable. */ - VES_NONE, +typedef enum ExprDefined { + /* The variable expression is based on a regular, defined variable. */ + DEF_REGULAR, /* The variable expression is based on an undefined variable. */ - VES_UNDEF, + DEF_UNDEF, /* * 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. + * of the modifiers (such as ':D' or ':U') has turned the expression + * from undefined to defined. */ - VES_DEF -} VarExprStatus; + DEF_DEFINED +} ExprDefined; -static const char * const VarExprStatus_Name[] = { - "none", - "VES_UNDEF", - "VES_DEF" +static const char *const ExprDefined_Name[] = { + "regular", + "undefined", + "defined" }; -typedef struct ApplyModifiersState { - /* '\0' or '{' or '(' */ - const char startc; - /* '\0' or '}' or ')' */ - const char endc; - Var *const var; - GNode *const scope; - const VarEvalFlags eflags; - /* - * The new value of the expression, after applying the modifier, - * never NULL. - */ - FStr newVal; - /* Word separator in expansions (see the :ts modifier). */ - char sep; - /* - * 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. - */ - Boolean oneBigWord; - VarExprStatus exprStatus; -} ApplyModifiersState; +#if __STDC_VERSION__ >= 199901L +#define const_member const +#else +#define const_member /* no const possible */ +#endif + +/* A variable expression such as $@ or ${VAR:Mpattern:Q}. */ +typedef struct Expr { + const char *name; + FStr value; + VarEvalMode const_member emode; + GNode *const_member scope; + ExprDefined defined; +} Expr; + +/* + * The status of applying a chain of modifiers to an expression. + * + * The modifiers of an expression are broken into chains of modifiers, + * starting a new nested chain whenever an indirect modifier starts. There + * are at most 2 nesting levels: the outer one for the direct modifiers, and + * the inner one for the indirect modifiers. + * + * For example, the expression ${VAR:M*:${IND1}:${IND2}:O:u} has 3 chains of + * modifiers: + * + * Chain 1 starts with the single modifier ':M*'. + * Chain 2 starts with all modifiers from ${IND1}. + * Chain 2 ends at the ':' between ${IND1} and ${IND2}. + * Chain 3 starts with all modifiers from ${IND2}. + * Chain 3 ends at the ':' after ${IND2}. + * Chain 1 continues with the the 2 modifiers ':O' and ':u'. + * Chain 1 ends at the final '}' of the expression. + * + * After such a chain ends, its properties no longer have any effect. + * + * It may or may not have been intended that 'defined' has scope Expr while + * 'sep' and 'oneBigWord' have smaller scope. + * + * See varmod-indirect.mk. + */ +typedef struct ModChain { + Expr *expr; + /* '\0' or '{' or '(' */ + char const_member startc; + /* '\0' or '}' or ')' */ + char const_member endc; + /* Word separator in expansions (see the :ts modifier). */ + char sep; + /* + * 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. + */ + bool oneBigWord; +} ModChain; + +static void +Expr_Define(Expr *expr) +{ + if (expr->defined == DEF_UNDEF) + expr->defined = DEF_DEFINED; +} static void -ApplyModifiersState_Define(ApplyModifiersState *st) +Expr_SetValue(Expr *expr, FStr value) { - if (st->exprStatus == VES_UNDEF) - st->exprStatus = VES_DEF; + FStr_Done(&expr->value); + expr->value = value; } +static void +Expr_SetValueOwn(Expr *expr, char *value) +{ + Expr_SetValue(expr, FStr_InitOwn(value)); +} + +static void +Expr_SetValueRefer(Expr *expr, const char *value) +{ + Expr_SetValue(expr, FStr_InitRefer(value)); +} + +static bool +Expr_ShouldEval(const Expr *expr) +{ + return VarEvalMode_ShouldEval(expr->emode); +} + +static bool +ModChain_ShouldEval(const ModChain *ch) +{ + return Expr_ShouldEval(ch->expr); +} + + typedef enum ApplyModifierResult { /* Continue parsing */ AMR_OK, - /* Not a match, try other modifiers as well */ + /* Not a match, try other modifiers as well. */ AMR_UNKNOWN, - /* Error out with "Bad modifier" message */ + /* Error out with "Bad modifier" message. */ AMR_BAD, - /* Error out without error message */ + /* Error out without the standard error message. */ AMR_CLEANUP } ApplyModifierResult; /* * Allow backslashes to escape the delimiter, $, and \, but don't touch other * backslashes. */ -static Boolean +static bool IsEscapedModifierPart(const char *p, char delim, struct ModifyWord_SubstArgs *subst) { if (p[0] != '\\') - return FALSE; + return false; if (p[1] == delim || p[1] == '\\' || p[1] == '$') - return TRUE; + return true; return p[1] == '&' && subst != NULL; } -/* See ParseModifierPart */ +/* See ParseModifierPart for the documentation. */ static VarParseResult ParseModifierPartSubst( const char **pp, char delim, - VarEvalFlags eflags, - ApplyModifiersState *st, - char **out_part, - /* Optionally stores the length of the returned string, just to save - * another strlen call. */ - size_t *out_length, - /* For the first part of the :S modifier, sets the VARP_ANCHOR_END flag - * if the last character of the pattern is a $. */ - VarPatternFlags *out_pflags, + VarEvalMode emode, + ModChain *ch, + LazyBuf *part, + /* For the first part of the modifier ':S', set anchorEnd if the last + * character of the pattern is a $. */ + PatternFlags *out_pflags, /* For the second part of the :S modifier, allow ampersands to be * escaped and replace unescaped ampersands with subst->lhs. */ struct ModifyWord_SubstArgs *subst ) { - Buffer buf; const char *p; - Buf_Init(&buf); + p = *pp; + LazyBuf_Init(part, p); /* * 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]); + LazyBuf_Add(part, p[1]); p += 2; continue; } if (*p != '$') { /* Unescaped, simple text */ if (subst != NULL && *p == '&') - Buf_AddBytes(&buf, subst->lhs, subst->lhsLen); + LazyBuf_AddSubstring(part, subst->lhs); else - Buf_AddByte(&buf, *p); + LazyBuf_Add(part, *p); p++; continue; } if (p[1] == delim) { /* Unescaped $ at end of pattern */ if (out_pflags != NULL) - out_pflags->anchorEnd = TRUE; + out_pflags->anchorEnd = true; else - Buf_AddByte(&buf, *p); + LazyBuf_Add(part, *p); p++; continue; } - if (eflags & VARE_WANTRES) { /* Nested variable, evaluated */ + if (VarEvalMode_ShouldEval(emode)) { + /* Nested variable, evaluated */ const char *nested_p = p; FStr nested_val; - VarEvalFlags nested_eflags = - eflags & ~(unsigned)VARE_KEEP_DOLLAR; - (void)Var_Parse(&nested_p, st->scope, nested_eflags, - &nested_val); + (void)Var_Parse(&nested_p, ch->expr->scope, + VarEvalMode_WithoutKeepDollar(emode), &nested_val); /* TODO: handle errors */ - Buf_AddStr(&buf, nested_val.str); + LazyBuf_AddStr(part, nested_val.str); FStr_Done(&nested_val); 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. + * 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); + LazyBuf_AddBytesBetween(part, varstart, p); } else { - Buf_AddByte(&buf, *varstart); + LazyBuf_Add(part, *varstart); p++; } } if (*p != delim) { *pp = p; - Error("Unfinished modifier for %s ('%c' missing)", - st->var->name.str, delim); - *out_part = NULL; + Error("Unfinished modifier for \"%s\" ('%c' missing)", + ch->expr->name, delim); + LazyBuf_Done(part); return VPR_ERR; } *pp = p + 1; - if (out_length != NULL) - *out_length = buf.len; - *out_part = Buf_DoneData(&buf); - DEBUG1(VAR, "Modifier part: \"%s\"\n", *out_part); + { + Substring sub = LazyBuf_Get(part); + DEBUG2(VAR, "Modifier part: \"%.*s\"\n", + (int)Substring_Length(sub), sub.start); + } + return VPR_OK; } /* * 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. + * Return VPR_OK if parsing succeeded, together with the parsed (and possibly + * expanded) part. In that case, pp points right after the delimiter. The + * delimiter is not included in the part though. */ static VarParseResult ParseModifierPart( /* The parsing position, updated upon return */ const char **pp, /* Parsing stops at this delimiter */ char delim, - /* Flags for evaluating nested variables; if VARE_WANTRES is not set, - * the text is only parsed. */ - VarEvalFlags eflags, - ApplyModifiersState *st, - char **out_part + /* Mode for evaluating nested variables. */ + VarEvalMode emode, + ModChain *ch, + LazyBuf *part ) { - return ParseModifierPartSubst(pp, delim, eflags, st, out_part, - NULL, NULL, NULL); + return ParseModifierPartSubst(pp, delim, emode, ch, part, NULL, NULL); +} + +MAKE_INLINE bool +IsDelimiter(char c, const ModChain *ch) +{ + return c == ':' || c == ch->endc; } /* Test whether mod starts with modname, followed by a delimiter. */ -MAKE_INLINE Boolean -ModMatch(const char *mod, const char *modname, char endc) +MAKE_INLINE bool +ModMatch(const char *mod, const char *modname, const ModChain *ch) { size_t n = strlen(modname); - return strncmp(mod, modname, n) == 0 && - (mod[n] == endc || mod[n] == ':'); + return strncmp(mod, modname, n) == 0 && IsDelimiter(mod[n], ch); } /* Test whether mod starts with modname, followed by a delimiter or '='. */ -MAKE_INLINE Boolean -ModMatchEq(const char *mod, const char *modname, char endc) +MAKE_INLINE bool +ModMatchEq(const char *mod, const char *modname, const ModChain *ch) { size_t n = strlen(modname); return strncmp(mod, modname, n) == 0 && - (mod[n] == endc || mod[n] == ':' || mod[n] == '='); + (IsDelimiter(mod[n], ch) || mod[n] == '='); } -static Boolean +static bool TryParseIntBase0(const char **pp, int *out_num) { char *end; long n; errno = 0; n = strtol(*pp, &end, 0); + + if (end == *pp) + return false; if ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE) - return FALSE; + return false; if (n < INT_MIN || n > INT_MAX) - return FALSE; + return false; *pp = end; *out_num = (int)n; - return TRUE; + return true; } -static Boolean +static bool TryParseSize(const char **pp, size_t *out_num) { char *end; unsigned long n; if (!ch_isdigit(**pp)) - return FALSE; + return false; errno = 0; n = strtoul(*pp, &end, 10); if (n == ULONG_MAX && errno == ERANGE) - return FALSE; + return false; if (n > SIZE_MAX) - return FALSE; + return false; *pp = end; *out_num = (size_t)n; - return TRUE; + return true; } -static Boolean +static bool TryParseChar(const char **pp, int base, char *out_ch) { char *end; unsigned long n; if (!ch_isalnum(**pp)) - return FALSE; + return false; errno = 0; n = strtoul(*pp, &end, base); if (n == ULONG_MAX && errno == ERANGE) - return FALSE; + return false; if (n > UCHAR_MAX) - return FALSE; + return false; *pp = end; *out_ch = (char)n; - return TRUE; + return true; +} + +/* + * Modify each word of the expression using the given function and place the + * result back in the expression. + */ +static void +ModifyWords(ModChain *ch, + ModifyWordProc modifyWord, void *modifyWord_args, + bool oneBigWord) +{ + Expr *expr = ch->expr; + const char *val = expr->value.str; + SepBuf result; + SubstringWords words; + size_t i; + Substring word; + + if (oneBigWord) { + SepBuf_Init(&result, ch->sep); + /* XXX: performance: Substring_InitStr calls strlen */ + word = Substring_InitStr(val); + modifyWord(word, &result, modifyWord_args); + goto done; + } + + words = Substring_Words(val, false); + + DEBUG2(VAR, "ModifyWords: split \"%s\" into %u words\n", + val, (unsigned)words.len); + + SepBuf_Init(&result, ch->sep); + for (i = 0; i < words.len; i++) { + modifyWord(words.words[i], &result, modifyWord_args); + if (result.buf.len > 0) + SepBuf_Sep(&result); + } + + SubstringWords_Free(words); + +done: + Expr_SetValueOwn(expr, SepBuf_DoneData(&result)); } /* :@var@...${var}...@ */ static ApplyModifierResult -ApplyModifier_Loop(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Loop(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; struct ModifyWord_LoopArgs args; char prev_sep; VarParseResult res; + LazyBuf tvarBuf, strBuf; + FStr tvar, str; - args.scope = st->scope; + args.scope = expr->scope; (*pp)++; /* Skip the first '@' */ - res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.tvar); + res = ParseModifierPart(pp, '@', VARE_PARSE_ONLY, ch, &tvarBuf); if (res != VPR_OK) return AMR_CLEANUP; - if (opts.strict && strchr(args.tvar, '$') != NULL) { + tvar = LazyBuf_DoneGet(&tvarBuf); + args.var = tvar.str; + if (strchr(args.var, '$') != NULL) { Parse_Error(PARSE_FATAL, "In the :@ modifier of \"%s\", the variable name \"%s\" " "must not contain a dollar.", - st->var->name.str, args.tvar); + expr->name, args.var); return AMR_CLEANUP; } - res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.str); + res = ParseModifierPart(pp, '@', VARE_PARSE_ONLY, ch, &strBuf); if (res != VPR_OK) return AMR_CLEANUP; - - args.eflags = st->eflags & ~(unsigned)VARE_KEEP_DOLLAR; - prev_sep = st->sep; - st->sep = ' '; /* XXX: should be st->sep for consistency */ - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_Loop, &args, st->oneBigWord, st->sep)); - st->sep = prev_sep; - /* XXX: Consider restoring the previous variable instead of deleting. */ - /* - * XXX: The variable name should not be expanded here, see - * ModifyWord_Loop. - */ - Var_DeleteExpand(st->scope, args.tvar); - free(args.tvar); - free(args.str); + str = LazyBuf_DoneGet(&strBuf); + args.body = str.str; + + if (!Expr_ShouldEval(expr)) + goto done; + + args.emode = VarEvalMode_WithoutKeepDollar(expr->emode); + prev_sep = ch->sep; + ch->sep = ' '; /* XXX: should be ch->sep for consistency */ + ModifyWords(ch, ModifyWord_Loop, &args, ch->oneBigWord); + ch->sep = prev_sep; + /* XXX: Consider restoring the previous value instead of deleting. */ + Var_Delete(expr->scope, args.var); + +done: + FStr_Done(&tvar); + FStr_Done(&str); return AMR_OK; } /* :Ddefined or :Uundefined */ static ApplyModifierResult -ApplyModifier_Defined(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Defined(const char **pp, ModChain *ch) { - Buffer buf; + Expr *expr = ch->expr; + LazyBuf buf; const char *p; - VarEvalFlags eflags = VARE_NONE; - if (st->eflags & VARE_WANTRES) - if ((**pp == 'D') == (st->exprStatus == VES_NONE)) - eflags = st->eflags; + VarEvalMode emode = VARE_PARSE_ONLY; + if (Expr_ShouldEval(expr)) + if ((**pp == 'D') == (expr->defined == DEF_REGULAR)) + emode = expr->emode; - Buf_Init(&buf); p = *pp + 1; - while (*p != st->endc && *p != ':' && *p != '\0') { + LazyBuf_Init(&buf, p); + while (!IsDelimiter(*p, ch) && *p != '\0') { /* XXX: This code is similar to the one in Var_Parse. * See if the code can be merged. - * See also ApplyModifier_Match. */ + * See also ApplyModifier_Match and ParseModifierPart. */ /* Escaped delimiter or other special character */ + /* See Buf_AddEscaped in for.c. */ if (*p == '\\') { char c = p[1]; - if (c == st->endc || c == ':' || c == '$' || - c == '\\') { - Buf_AddByte(&buf, c); + if (IsDelimiter(c, ch) || c == '$' || c == '\\') { + LazyBuf_Add(&buf, c); p += 2; continue; } } /* Nested variable expression */ if (*p == '$') { FStr nested_val; - (void)Var_Parse(&p, st->scope, eflags, &nested_val); + (void)Var_Parse(&p, expr->scope, emode, &nested_val); /* TODO: handle errors */ - Buf_AddStr(&buf, nested_val.str); + if (Expr_ShouldEval(expr)) + LazyBuf_AddStr(&buf, nested_val.str); FStr_Done(&nested_val); continue; } /* Ordinary text */ - Buf_AddByte(&buf, *p); + LazyBuf_Add(&buf, *p); p++; } *pp = p; - ApplyModifiersState_Define(st); + Expr_Define(expr); + + if (VarEvalMode_ShouldEval(emode)) + Expr_SetValue(expr, Substring_Str(LazyBuf_Get(&buf))); + else + LazyBuf_Done(&buf); - if (eflags & VARE_WANTRES) { - st->newVal = FStr_InitOwn(Buf_DoneData(&buf)); - } else { - st->newVal = FStr_InitRefer(val); - Buf_Done(&buf); - } return AMR_OK; } /* :L */ static ApplyModifierResult -ApplyModifier_Literal(const char **pp, ApplyModifiersState *st) +ApplyModifier_Literal(const char **pp, ModChain *ch) { - ApplyModifiersState_Define(st); - st->newVal = FStr_InitOwn(bmake_strdup(st->var->name.str)); + Expr *expr = ch->expr; + (*pp)++; + + if (Expr_ShouldEval(expr)) { + Expr_Define(expr); + Expr_SetValueOwn(expr, bmake_strdup(expr->name)); + } + return AMR_OK; } -static Boolean +static bool TryParseTime(const char **pp, time_t *out_time) { char *end; unsigned long n; if (!ch_isdigit(**pp)) - return FALSE; + return false; errno = 0; n = strtoul(*pp, &end, 10); if (n == ULONG_MAX && errno == ERANGE) - return FALSE; + return false; *pp = end; *out_time = (time_t)n; /* ignore possible truncation for now */ - return TRUE; + return true; } /* :gmtime */ static ApplyModifierResult -ApplyModifier_Gmtime(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Gmtime(const char **pp, ModChain *ch) { time_t utc; const char *mod = *pp; - if (!ModMatchEq(mod, "gmtime", st->endc)) + if (!ModMatchEq(mod, "gmtime", ch)) return AMR_UNKNOWN; if (mod[6] == '=') { - const char *arg = mod + 7; - if (!TryParseTime(&arg, &utc)) { + const char *p = mod + 7; + if (!TryParseTime(&p, &utc)) { Parse_Error(PARSE_FATAL, "Invalid time value: %s", mod + 7); return AMR_CLEANUP; } - *pp = arg; + *pp = p; } else { utc = 0; *pp = mod + 6; } - st->newVal = FStr_InitOwn(VarStrftime(val, TRUE, utc)); + + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(ch->expr, + VarStrftime(ch->expr->value.str, true, utc)); + return AMR_OK; } /* :localtime */ static ApplyModifierResult -ApplyModifier_Localtime(const char **pp, const char *val, - ApplyModifiersState *st) +ApplyModifier_Localtime(const char **pp, ModChain *ch) { time_t utc; const char *mod = *pp; - if (!ModMatchEq(mod, "localtime", st->endc)) + if (!ModMatchEq(mod, "localtime", ch)) return AMR_UNKNOWN; if (mod[9] == '=') { - const char *arg = mod + 10; - if (!TryParseTime(&arg, &utc)) { + const char *p = mod + 10; + if (!TryParseTime(&p, &utc)) { Parse_Error(PARSE_FATAL, "Invalid time value: %s", mod + 10); return AMR_CLEANUP; } - *pp = arg; + *pp = p; } else { utc = 0; *pp = mod + 9; } - st->newVal = FStr_InitOwn(VarStrftime(val, FALSE, utc)); + + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(ch->expr, + VarStrftime(ch->expr->value.str, false, utc)); + return AMR_OK; } /* :hash */ static ApplyModifierResult -ApplyModifier_Hash(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Hash(const char **pp, ModChain *ch) { - if (!ModMatch(*pp, "hash", st->endc)) + if (!ModMatch(*pp, "hash", ch)) return AMR_UNKNOWN; - - st->newVal = FStr_InitOwn(VarHash(val)); *pp += 4; + + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(ch->expr, VarHash(ch->expr->value.str)); + return AMR_OK; } /* :P */ static ApplyModifierResult -ApplyModifier_Path(const char **pp, ApplyModifiersState *st) +ApplyModifier_Path(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; GNode *gn; char *path; - ApplyModifiersState_Define(st); + (*pp)++; + + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + + Expr_Define(expr); - gn = Targ_FindNode(st->var->name.str); + gn = Targ_FindNode(expr->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->var->name.str, searchPath); + path = Dir_FindFile(expr->name, searchPath); } if (path == NULL) - path = bmake_strdup(st->var->name.str); - st->newVal = FStr_InitOwn(path); + path = bmake_strdup(expr->name); + Expr_SetValueOwn(expr, path); - (*pp)++; return AMR_OK; } /* :!cmd! */ static ApplyModifierResult -ApplyModifier_ShellCommand(const char **pp, ApplyModifiersState *st) +ApplyModifier_ShellCommand(const char **pp, ModChain *ch) { - char *cmd; + Expr *expr = ch->expr; const char *errfmt; VarParseResult res; + LazyBuf cmdBuf; + FStr cmd; (*pp)++; - res = ParseModifierPart(pp, '!', st->eflags, st, &cmd); + res = ParseModifierPart(pp, '!', expr->emode, ch, &cmdBuf); if (res != VPR_OK) return AMR_CLEANUP; + cmd = LazyBuf_DoneGet(&cmdBuf); + errfmt = NULL; - if (st->eflags & VARE_WANTRES) - st->newVal = FStr_InitOwn(Cmd_Exec(cmd, &errfmt)); + if (Expr_ShouldEval(expr)) + Expr_SetValueOwn(expr, Cmd_Exec(cmd.str, &errfmt)); else - st->newVal = FStr_InitRefer(""); + Expr_SetValueRefer(expr, ""); if (errfmt != NULL) - Error(errfmt, cmd); /* XXX: why still return AMR_OK? */ - free(cmd); + Error(errfmt, cmd.str); /* XXX: why still return AMR_OK? */ + FStr_Done(&cmd); + Expr_Define(expr); - 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, const char *val, ApplyModifiersState *st) +ApplyModifier_Range(const char **pp, ModChain *ch) { size_t n; Buffer buf; size_t i; const char *mod = *pp; - if (!ModMatchEq(mod, "range", st->endc)) + if (!ModMatchEq(mod, "range", ch)) return AMR_UNKNOWN; if (mod[5] == '=') { const char *p = mod + 6; if (!TryParseSize(&p, &n)) { Parse_Error(PARSE_FATAL, - "Invalid number: %s", mod + 6); + "Invalid number \"%s\" for ':range' modifier", + mod + 6); return AMR_CLEANUP; } *pp = p; } else { n = 0; *pp = mod + 5; } + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + if (n == 0) { - Words words = Str_Words(val, FALSE); + Words words = Str_Words(ch->expr->value.str, false); n = words.len; Words_Free(words); } Buf_Init(&buf); for (i = 0; i < n; i++) { if (i != 0) { - /* XXX: Use st->sep instead of ' ', for consistency. */ + /* XXX: Use ch->sep instead of ' ', for consistency. */ Buf_AddByte(&buf, ' '); } Buf_AddInt(&buf, 1 + (int)i); } - st->newVal = FStr_InitOwn(Buf_DoneData(&buf)); + Expr_SetValueOwn(ch->expr, Buf_DoneData(&buf)); return AMR_OK; } -/* :Mpattern or :Npattern */ -static ApplyModifierResult -ApplyModifier_Match(const char **pp, const char *val, ApplyModifiersState *st) +/* Parse a ':M' or ':N' modifier. */ +static void +ParseModifier_Match(const char **pp, const ModChain *ch, + char **out_pattern) { const char *mod = *pp; - Boolean copy = FALSE; /* pattern should be, or has been, copied */ - Boolean needSubst = FALSE; + Expr *expr = ch->expr; + bool copy = false; /* pattern should be, or has been, copied */ + bool 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 code is similar to the one in Var_Parse. + /* + * XXX: This code is similar to the one in Var_Parse. * See if the code can be merged. - * See also ApplyModifier_Defined. */ + * 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)) { + (IsDelimiter(p[1], ch) || p[1] == ch->startc)) { if (!needSubst) - copy = TRUE; + copy = true; p++; continue; } if (*p == '$') - needSubst = TRUE; + 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)) + /* XXX: ch->startc is missing here; see above */ + IsDelimiter(src[1], ch)) src++; *dst = *src; } *dst = '\0'; } else { pattern = bmake_strsedup(mod + 1, endpat); } if (needSubst) { char *old_pattern = pattern; - (void)Var_Subst(pattern, st->scope, st->eflags, &pattern); + (void)Var_Subst(pattern, expr->scope, expr->emode, &pattern); /* TODO: handle errors */ free(old_pattern); } - DEBUG3(VAR, "Pattern[%s] for [%s] is [%s]\n", - st->var->name.str, val, pattern); + DEBUG2(VAR, "Pattern for ':%c' is \"%s\"\n", mod[0], pattern); + + *out_pattern = pattern; +} + +/* :Mpattern or :Npattern */ +static ApplyModifierResult +ApplyModifier_Match(const char **pp, ModChain *ch) +{ + const char mod = **pp; + char *pattern; + + ParseModifier_Match(pp, ch, &pattern); + + if (ModChain_ShouldEval(ch)) { + ModifyWordProc modifyWord = + mod == 'M' ? ModifyWord_Match : ModifyWord_NoMatch; + ModifyWords(ch, modifyWord, pattern, ch->oneBigWord); + } - callback = mod[0] == 'M' ? ModifyWord_Match : ModifyWord_NoMatch; - st->newVal = FStr_InitOwn(ModifyWords(val, callback, pattern, - st->oneBigWord, st->sep)); free(pattern); return AMR_OK; } +static void +ParsePatternFlags(const char **pp, PatternFlags *pflags, bool *oneBigWord) +{ + for (;; (*pp)++) { + if (**pp == 'g') + pflags->subGlobal = true; + else if (**pp == '1') + pflags->subOnce = true; + else if (**pp == 'W') + *oneBigWord = true; + else + break; + } +} + +MAKE_INLINE PatternFlags +PatternFlags_None(void) +{ + PatternFlags pflags = { false, false, false, false }; + return pflags; +} + /* :S,from,to, */ static ApplyModifierResult -ApplyModifier_Subst(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Subst(const char **pp, ModChain *ch) { struct ModifyWord_SubstArgs args; - char *lhs, *rhs; - Boolean oneBigWord; + bool oneBigWord; VarParseResult res; + LazyBuf lhsBuf, rhsBuf; char delim = (*pp)[1]; if (delim == '\0') { - Error("Missing delimiter for :S modifier"); + Error("Missing delimiter for modifier ':S'"); (*pp)++; return AMR_CLEANUP; } *pp += 2; - args.pflags = (VarPatternFlags){ FALSE, FALSE, FALSE, FALSE }; - args.matched = FALSE; + args.pflags = PatternFlags_None(); + 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.anchorStart = TRUE; + args.pflags.anchorStart = true; (*pp)++; } - res = ParseModifierPartSubst(pp, delim, st->eflags, st, &lhs, - &args.lhsLen, &args.pflags, NULL); + res = ParseModifierPartSubst(pp, delim, ch->expr->emode, ch, &lhsBuf, + &args.pflags, NULL); if (res != VPR_OK) return AMR_CLEANUP; - args.lhs = lhs; + args.lhs = LazyBuf_Get(&lhsBuf); - res = ParseModifierPartSubst(pp, delim, st->eflags, st, &rhs, - &args.rhsLen, NULL, &args); - if (res != VPR_OK) + res = ParseModifierPartSubst(pp, delim, ch->expr->emode, ch, &rhsBuf, + NULL, &args); + if (res != VPR_OK) { + LazyBuf_Done(&lhsBuf); return AMR_CLEANUP; - args.rhs = rhs; - - oneBigWord = st->oneBigWord; - for (;; (*pp)++) { - switch (**pp) { - case 'g': - args.pflags.subGlobal = TRUE; - continue; - case '1': - args.pflags.subOnce = TRUE; - continue; - case 'W': - oneBigWord = TRUE; - continue; - } - break; } + args.rhs = LazyBuf_Get(&rhsBuf); + + oneBigWord = ch->oneBigWord; + ParsePatternFlags(pp, &args.pflags, &oneBigWord); - st->newVal = FStr_InitOwn(ModifyWords(val, ModifyWord_Subst, &args, - oneBigWord, st->sep)); + ModifyWords(ch, ModifyWord_Subst, &args, oneBigWord); - free(lhs); - free(rhs); + LazyBuf_Done(&lhsBuf); + LazyBuf_Done(&rhsBuf); return AMR_OK; } #ifndef NO_REGEX /* :C,from,to, */ static ApplyModifierResult -ApplyModifier_Regex(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Regex(const char **pp, ModChain *ch) { - char *re; struct ModifyWord_SubstRegexArgs args; - Boolean oneBigWord; + bool oneBigWord; int error; VarParseResult res; + LazyBuf reBuf, replaceBuf; + FStr re, replace; 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); + res = ParseModifierPart(pp, delim, ch->expr->emode, ch, &reBuf); if (res != VPR_OK) return AMR_CLEANUP; + re = LazyBuf_DoneGet(&reBuf); - res = ParseModifierPart(pp, delim, st->eflags, st, &args.replace); - if (args.replace == NULL) { - free(re); + res = ParseModifierPart(pp, delim, ch->expr->emode, ch, &replaceBuf); + if (res != VPR_OK) { + FStr_Done(&re); return AMR_CLEANUP; } + replace = LazyBuf_DoneGet(&replaceBuf); + args.replace = replace.str; - args.pflags = (VarPatternFlags){ FALSE, FALSE, FALSE, FALSE }; - args.matched = FALSE; - oneBigWord = st->oneBigWord; - for (;; (*pp)++) { - switch (**pp) { - case 'g': - args.pflags.subGlobal = TRUE; - continue; - case '1': - args.pflags.subOnce = TRUE; - continue; - case 'W': - oneBigWord = TRUE; - continue; - } - break; + args.pflags = PatternFlags_None(); + args.matched = false; + oneBigWord = ch->oneBigWord; + ParsePatternFlags(pp, &args.pflags, &oneBigWord); + + if (!ModChain_ShouldEval(ch)) { + FStr_Done(&replace); + FStr_Done(&re); + return AMR_OK; } - error = regcomp(&args.re, re, REG_EXTENDED); - free(re); + error = regcomp(&args.re, re.str, REG_EXTENDED); if (error != 0) { VarREError(error, &args.re, "Regex compilation error"); - free(args.replace); + FStr_Done(&replace); + FStr_Done(&re); return AMR_CLEANUP; } args.nsub = args.re.re_nsub + 1; if (args.nsub > 10) args.nsub = 10; - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_SubstRegex, &args, - oneBigWord, st->sep)); + + ModifyWords(ch, ModifyWord_SubstRegex, &args, oneBigWord); + regfree(&args.re); - free(args.replace); + FStr_Done(&replace); + FStr_Done(&re); return AMR_OK; } #endif /* :Q, :q */ static ApplyModifierResult -ApplyModifier_Quote(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Quote(const char **pp, ModChain *ch) { - if ((*pp)[1] == st->endc || (*pp)[1] == ':') { - st->newVal = FStr_InitOwn(VarQuote(val, **pp == 'q')); - (*pp)++; - return AMR_OK; - } else + LazyBuf buf; + bool quoteDollar; + + quoteDollar = **pp == 'q'; + if (!IsDelimiter((*pp)[1], ch)) return AMR_UNKNOWN; + (*pp)++; + + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + + VarQuote(ch->expr->value.str, quoteDollar, &buf); + if (buf.data != NULL) + Expr_SetValue(ch->expr, LazyBuf_DoneGet(&buf)); + else + LazyBuf_Done(&buf); + + return AMR_OK; } /*ARGSUSED*/ static void -ModifyWord_Copy(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) +ModifyWord_Copy(Substring word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) { - SepBuf_AddStr(buf, word); + SepBuf_AddSubstring(buf, word); } /* :ts */ static ApplyModifierResult -ApplyModifier_ToSep(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_ToSep(const char **pp, ModChain *ch) { const char *sep = *pp + 2; + /* + * Even in parse-only mode, proceed as normal since there is + * neither any observable side effect nor a performance penalty. + * Checking for wantRes for every single piece of code in here + * would make the code in this function too hard to read. + */ + /* ":ts" or ":ts:" */ - if (sep[0] != st->endc && (sep[1] == st->endc || sep[1] == ':')) { - st->sep = sep[0]; + if (sep[0] != ch->endc && IsDelimiter(sep[1], ch)) { *pp = sep + 1; + ch->sep = sep[0]; goto ok; } /* ":ts" or ":ts:" */ - if (sep[0] == st->endc || sep[0] == ':') { - st->sep = '\0'; /* no separator */ + if (IsDelimiter(sep[0], ch)) { *pp = sep; + ch->sep = '\0'; /* no separator */ 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; + ch->sep = '\n'; goto ok; } /* ":ts\t" */ if (sep[1] == 't') { - st->sep = '\t'; *pp = sep + 2; + ch->sep = '\t'; 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)) { + if (!TryParseChar(&p, base, &ch->sep)) { Parse_Error(PARSE_FATAL, "Invalid character number: %s", p); return AMR_CLEANUP; } - if (*p != ':' && *p != st->endc) { + if (!IsDelimiter(*p, ch)) { (*pp)++; /* just for backwards compatibility */ return AMR_BAD; } *pp = p; } ok: - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_Copy, NULL, st->oneBigWord, st->sep)); + ModifyWords(ch, ModifyWord_Copy, NULL, ch->oneBigWord); return AMR_OK; } static char * str_toupper(const char *str) { char *res; size_t i, len; len = strlen(str); res = bmake_malloc(len + 1); for (i = 0; i < len + 1; i++) res[i] = ch_toupper(str[i]); return res; } static char * str_tolower(const char *str) { char *res; size_t i, len; len = strlen(str); res = bmake_malloc(len + 1); for (i = 0; i < len + 1; i++) res[i] = ch_tolower(str[i]); return res; } /* :tA, :tu, :tl, :ts, etc. */ static ApplyModifierResult -ApplyModifier_To(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_To(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; const char *mod = *pp; assert(mod[0] == 't'); - if (mod[1] == st->endc || mod[1] == ':' || mod[1] == '\0') { + if (IsDelimiter(mod[1], ch) || mod[1] == '\0') { *pp = mod + 1; return AMR_BAD; /* Found ":t" or ":t:". */ } if (mod[1] == 's') - return ApplyModifier_ToSep(pp, val, st); + return ApplyModifier_ToSep(pp, ch); - if (mod[2] != st->endc && mod[2] != ':') { + if (!IsDelimiter(mod[2], ch)) { /* :t */ *pp = mod + 1; - return AMR_BAD; /* Found ":t". */ + return AMR_BAD; } - /* Check for two-character options: ":tu", ":tl" */ - if (mod[1] == 'A') { /* absolute path */ - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_Realpath, NULL, - st->oneBigWord, st->sep)); + if (mod[1] == 'A') { /* :tA */ *pp = mod + 2; + ModifyWords(ch, ModifyWord_Realpath, NULL, ch->oneBigWord); return AMR_OK; } - if (mod[1] == 'u') { /* :tu */ - st->newVal = FStr_InitOwn(str_toupper(val)); + if (mod[1] == 'u') { /* :tu */ *pp = mod + 2; + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(expr, str_toupper(expr->value.str)); return AMR_OK; } - if (mod[1] == 'l') { /* :tl */ - st->newVal = FStr_InitOwn(str_tolower(val)); + if (mod[1] == 'l') { /* :tl */ *pp = mod + 2; + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(expr, str_tolower(expr->value.str)); return AMR_OK; } - if (mod[1] == 'W' || mod[1] == 'w') { /* :tW, :tw */ - st->oneBigWord = mod[1] == 'W'; - st->newVal = FStr_InitRefer(val); + if (mod[1] == 'W' || mod[1] == 'w') { /* :tW, :tw */ *pp = mod + 2; + ch->oneBigWord = mod[1] == 'W'; return AMR_OK; } /* Found ":t:" or ":t". */ - *pp = mod + 1; + *pp = mod + 1; /* XXX: unnecessary but observable */ return AMR_BAD; } /* :[#], :[1], :[-1..1], etc. */ static ApplyModifierResult -ApplyModifier_Words(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Words(const char **pp, ModChain *ch) { - char *estr; + Expr *expr = ch->expr; + const char *estr; int first, last; VarParseResult res; const char *p; + LazyBuf estrBuf; + FStr festr; (*pp)++; /* skip the '[' */ - res = ParseModifierPart(pp, ']', st->eflags, st, &estr); + res = ParseModifierPart(pp, ']', expr->emode, ch, &estrBuf); if (res != VPR_OK) return AMR_CLEANUP; + festr = LazyBuf_DoneGet(&estrBuf); + estr = festr.str; - /* now *pp points just after the closing ']' */ - if (**pp != ':' && **pp != st->endc) - goto bad_modifier; /* Found junk after ']' */ + if (!IsDelimiter(**pp, ch)) + goto bad_modifier; /* Found junk after ']' */ + + if (!ModChain_ShouldEval(ch)) + goto ok; if (estr[0] == '\0') - goto bad_modifier; /* empty square brackets in ":[]". */ + goto bad_modifier; /* Found ":[]". */ - if (estr[0] == '#' && estr[1] == '\0') { /* Found ":[#]" */ - if (st->oneBigWord) { - st->newVal = FStr_InitRefer("1"); + if (estr[0] == '#' && estr[1] == '\0') { /* Found ":[#]" */ + if (ch->oneBigWord) { + Expr_SetValueRefer(expr, "1"); } else { Buffer buf; - Words words = Str_Words(val, FALSE); + Words words = Str_Words(expr->value.str, false); size_t ac = words.len; Words_Free(words); /* 3 digits + '\0' is usually enough */ Buf_InitSize(&buf, 4); Buf_AddInt(&buf, (int)ac); - st->newVal = FStr_InitOwn(Buf_DoneData(&buf)); + Expr_SetValueOwn(expr, Buf_DoneData(&buf)); } goto ok; } - if (estr[0] == '*' && estr[1] == '\0') { - /* Found ":[*]" */ - st->oneBigWord = TRUE; - st->newVal = FStr_InitRefer(val); + if (estr[0] == '*' && estr[1] == '\0') { /* Found ":[*]" */ + ch->oneBigWord = true; goto ok; } - if (estr[0] == '@' && estr[1] == '\0') { - /* Found ":[@]" */ - st->oneBigWord = FALSE; - st->newVal = FStr_InitRefer(val); + if (estr[0] == '@' && estr[1] == '\0') { /* Found ":[@]" */ + ch->oneBigWord = false; 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 = FStr_InitRefer(val); + ch->oneBigWord = true; 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 = FStr_InitOwn( - VarSelectWords(st->sep, st->oneBigWord, val, first, last)); + Expr_SetValueOwn(expr, + VarSelectWords(expr->value.str, first, last, + ch->sep, ch->oneBigWord)); ok: - free(estr); + FStr_Done(&festr); return AMR_OK; bad_modifier: - free(estr); + FStr_Done(&festr); 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); } static void ShuffleStrings(char **strs, size_t n) { size_t i; for (i = n - 1; i > 0; i--) { size_t rndidx = (size_t)random() % (i + 1); char *t = strs[i]; strs[i] = strs[rndidx]; strs[rndidx] = t; } } /* :O (order ascending) or :Or (order descending) or :Ox (shuffle) */ static ApplyModifierResult -ApplyModifier_Order(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Order(const char **pp, ModChain *ch) { const char *mod = (*pp)++; /* skip past the 'O' in any case */ + Words words; + enum SortMode { + ASC, DESC, SHUFFLE + } mode; - Words words = Str_Words(val, FALSE); - - if (mod[1] == st->endc || mod[1] == ':') { - /* :O sorts ascending */ - qsort(words.words, words.len, sizeof words.words[0], - str_cmp_asc); - + if (IsDelimiter(mod[1], ch)) { + mode = ASC; } else if ((mod[1] == 'r' || mod[1] == 'x') && - (mod[2] == st->endc || mod[2] == ':')) { + IsDelimiter(mod[2], ch)) { (*pp)++; - - if (mod[1] == 'r') { /* :Or sorts descending */ - qsort(words.words, words.len, sizeof words.words[0], - str_cmp_desc); - } else - ShuffleStrings(words.words, words.len); - } else { - Words_Free(words); + mode = mod[1] == 'r' ? DESC : SHUFFLE; + } else return AMR_BAD; - } - st->newVal = FStr_InitOwn(Words_JoinFree(words)); + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + + words = Str_Words(ch->expr->value.str, false); + if (mode == SHUFFLE) + ShuffleStrings(words.words, words.len); + else + qsort(words.words, words.len, sizeof words.words[0], + mode == ASC ? str_cmp_asc : str_cmp_desc); + Expr_SetValueOwn(ch->expr, Words_JoinFree(words)); + return AMR_OK; } /* :? then : else */ static ApplyModifierResult -ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) +ApplyModifier_IfElse(const char **pp, ModChain *ch) { - char *then_expr, *else_expr; + Expr *expr = ch->expr; VarParseResult res; + LazyBuf buf; + FStr then_expr, else_expr; - Boolean value = FALSE; - VarEvalFlags then_eflags = VARE_NONE; - VarEvalFlags else_eflags = VARE_NONE; + bool value = false; + VarEvalMode then_emode = VARE_PARSE_ONLY; + VarEvalMode else_emode = VARE_PARSE_ONLY; int cond_rc = COND_PARSE; /* anything other than COND_INVALID */ - if (st->eflags & VARE_WANTRES) { - cond_rc = Cond_EvalCondition(st->var->name.str, &value); + if (Expr_ShouldEval(expr)) { + cond_rc = Cond_EvalCondition(expr->name, &value); if (cond_rc != COND_INVALID && value) - then_eflags = st->eflags; + then_emode = expr->emode; if (cond_rc != COND_INVALID && !value) - else_eflags = st->eflags; + else_emode = expr->emode; } (*pp)++; /* skip past the '?' */ - res = ParseModifierPart(pp, ':', then_eflags, st, &then_expr); + res = ParseModifierPart(pp, ':', then_emode, ch, &buf); if (res != VPR_OK) return AMR_CLEANUP; + then_expr = LazyBuf_DoneGet(&buf); - res = ParseModifierPart(pp, st->endc, else_eflags, st, &else_expr); - if (res != VPR_OK) + res = ParseModifierPart(pp, ch->endc, else_emode, ch, &buf); + if (res != VPR_OK) { + FStr_Done(&then_expr); return AMR_CLEANUP; + } + else_expr = LazyBuf_DoneGet(&buf); + + (*pp)--; /* Go back to the ch->endc. */ - (*pp)--; if (cond_rc == COND_INVALID) { - Error("Bad conditional expression `%s' in %s?%s:%s", - st->var->name.str, st->var->name.str, then_expr, else_expr); + Error("Bad conditional expression '%s' in '%s?%s:%s'", + expr->name, expr->name, then_expr.str, else_expr.str); return AMR_CLEANUP; } - if (value) { - st->newVal = FStr_InitOwn(then_expr); - free(else_expr); + if (!ModChain_ShouldEval(ch)) { + FStr_Done(&then_expr); + FStr_Done(&else_expr); + } else if (value) { + Expr_SetValue(expr, then_expr); + FStr_Done(&else_expr); } else { - st->newVal = FStr_InitOwn(else_expr); - free(then_expr); + FStr_Done(&then_expr); + Expr_SetValue(expr, else_expr); } - ApplyModifiersState_Define(st); + Expr_Define(expr); 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. + * The ::= modifiers are special in that they do not read the variable value + * but instead assign to that variable. They always expand to an empty + * string. + * + * Their main purpose is in supporting .for loops that generate shell commands + * since an ordinary variable assignment at that point would terminate the + * dependency group for these targets. For example: * - * foo: .USE + * list-targets: .USE * .for i in ${.TARGET} ${.TARGET:R}.gz - * @: ${t::=$i} - * @echo blah ${t:T} + * @${t::=$i} + * @echo 'The target is ${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) +ApplyModifier_Assign(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; GNode *scope; - char delim; - char *val; + FStr val; VarParseResult res; + LazyBuf buf; 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->var->name.str[0] == '\0') { +ok: + if (expr->name[0] == '\0') { *pp = mod + 1; return AMR_BAD; } - scope = st->scope; /* scope where v belongs */ - if (st->exprStatus == VES_NONE && st->scope != SCOPE_GLOBAL) { - Var *gv = VarFind(st->var->name.str, st->scope, FALSE); - if (gv == NULL) - scope = SCOPE_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); + res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &buf); if (res != VPR_OK) return AMR_CLEANUP; + val = LazyBuf_DoneGet(&buf); - (*pp)--; + (*pp)--; /* Go back to the ch->endc. */ - /* XXX: Expanding the variable name at this point sounds wrong. */ - if (st->eflags & VARE_WANTRES) { - switch (op[0]) { - case '+': - Var_AppendExpand(scope, st->var->name.str, val); - break; - case '!': { - const char *errfmt; - char *cmd_output = Cmd_Exec(val, &errfmt); - if (errfmt != NULL) - Error(errfmt, val); - else - Var_SetExpand(scope, - st->var->name.str, cmd_output); - free(cmd_output); - break; - } - case '?': - if (st->exprStatus == VES_NONE) - break; - /* FALLTHROUGH */ - default: - Var_SetExpand(scope, st->var->name.str, val); + if (!Expr_ShouldEval(expr)) + goto done; + + scope = expr->scope; /* scope where v belongs */ + if (expr->defined == DEF_REGULAR && expr->scope != SCOPE_GLOBAL) { + Var *gv = VarFind(expr->name, expr->scope, false); + if (gv == NULL) + scope = SCOPE_GLOBAL; + else + VarFreeEnv(gv); + } + + switch (op[0]) { + case '+': + Var_Append(scope, expr->name, val.str); + break; + case '!': { + const char *errfmt; + char *cmd_output = Cmd_Exec(val.str, &errfmt); + if (errfmt != NULL) + Error(errfmt, val.str); + else + Var_Set(scope, expr->name, cmd_output); + free(cmd_output); + break; + } + case '?': + if (expr->defined == DEF_REGULAR) break; - } + /* FALLTHROUGH */ + default: + Var_Set(scope, expr->name, val.str); + break; } - free(val); - st->newVal = FStr_InitRefer(""); + Expr_SetValueRefer(expr, ""); + +done: + FStr_Done(&val); return AMR_OK; } /* * :_=... * remember current value */ static ApplyModifierResult -ApplyModifier_Remember(const char **pp, const char *val, - ApplyModifiersState *st) +ApplyModifier_Remember(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; const char *mod = *pp; - if (!ModMatchEq(mod, "_", st->endc)) + FStr name; + + if (!ModMatchEq(mod, "_", ch)) return AMR_UNKNOWN; + name = FStr_InitRefer("_"); if (mod[1] == '=') { - size_t n = strcspn(mod + 2, ":)}"); - char *name = bmake_strldup(mod + 2, n); - Var_SetExpand(st->scope, name, val); - free(name); - *pp = mod + 2 + n; - } else { - Var_Set(st->scope, "_", val); + /* + * XXX: This ad-hoc call to strcspn deviates from the usual + * behavior defined in ParseModifierPart. This creates an + * unnecessary, undocumented inconsistency in make. + */ + const char *arg = mod + 2; + size_t argLen = strcspn(arg, ":)}"); + *pp = arg + argLen; + name = FStr_InitOwn(bmake_strldup(arg, argLen)); + } else *pp = mod + 1; - } - st->newVal = FStr_InitRefer(val); + + if (Expr_ShouldEval(expr)) + Var_Set(expr->scope, name.str, expr->value.str); + FStr_Done(&name); + 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, const char *val, - ApplyModifiersState *st, ModifyWordsCallback modifyWord) +ApplyModifier_WordFunc(const char **pp, ModChain *ch, + ModifyWordProc modifyWord) { - char delim = (*pp)[1]; - if (delim != st->endc && delim != ':') + if (!IsDelimiter((*pp)[1], ch)) return AMR_UNKNOWN; - - st->newVal = FStr_InitOwn(ModifyWords(val, modifyWord, NULL, - st->oneBigWord, st->sep)); (*pp)++; + + if (ModChain_ShouldEval(ch)) + ModifyWords(ch, modifyWord, NULL, ch->oneBigWord); + return AMR_OK; } +/* Remove adjacent duplicate words. */ static ApplyModifierResult -ApplyModifier_Unique(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Unique(const char **pp, ModChain *ch) { - if ((*pp)[1] == st->endc || (*pp)[1] == ':') { - st->newVal = FStr_InitOwn(VarUniq(val)); - (*pp)++; - return AMR_OK; - } else + Words words; + + if (!IsDelimiter((*pp)[1], ch)) return AMR_UNKNOWN; + (*pp)++; + + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + + words = Str_Words(ch->expr->value.str, false); + + if (words.len > 1) { + size_t si, di; + + di = 0; + for (si = 1; si < words.len; si++) { + if (strcmp(words.words[si], words.words[di]) != 0) { + di++; + if (di != si) + words.words[di] = words.words[si]; + } + } + words.len = di + 1; + } + + Expr_SetValueOwn(ch->expr, Words_JoinFree(words)); + + return AMR_OK; } #ifdef SYSVVARSUB /* :from=to */ static ApplyModifierResult -ApplyModifier_SysV(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_SysV(const char **pp, ModChain *ch) { - char *lhs, *rhs; + Expr *expr = ch->expr; VarParseResult res; + LazyBuf lhsBuf, rhsBuf; + FStr rhs; + struct ModifyWord_SysVSubstArgs args; + Substring lhs; + const char *lhsSuffix; const char *mod = *pp; - Boolean eqFound = FALSE; + bool 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) + eqFound = true; + /* continue looking for ch->endc */ + } else if (*p == ch->endc) depth--; - else if (*p == st->startc) + else if (*p == ch->startc) depth++; if (depth > 0) p++; } - if (*p != st->endc || !eqFound) + if (*p != ch->endc || !eqFound) return AMR_UNKNOWN; - res = ParseModifierPart(pp, '=', st->eflags, st, &lhs); + res = ParseModifierPart(pp, '=', expr->emode, ch, &lhsBuf); 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); - if (res != VPR_OK) + res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &rhsBuf); + if (res != VPR_OK) { + LazyBuf_Done(&lhsBuf); return AMR_CLEANUP; - - (*pp)--; - if (lhs[0] == '\0' && val[0] == '\0') { - st->newVal = FStr_InitRefer(val); /* special case */ - } else { - struct ModifyWord_SYSVSubstArgs args = { st->scope, lhs, rhs }; - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_SYSVSubst, &args, - st->oneBigWord, st->sep)); } - free(lhs); - free(rhs); + rhs = LazyBuf_DoneGet(&rhsBuf); + + (*pp)--; /* Go back to the ch->endc. */ + + /* Do not turn an empty expression into non-empty. */ + if (lhsBuf.len == 0 && expr->value.str[0] == '\0') + goto done; + + lhs = LazyBuf_Get(&lhsBuf); + lhsSuffix = Substring_SkipFirst(lhs, '%'); + + args.scope = expr->scope; + args.lhsPrefix = Substring_Init(lhs.start, + lhsSuffix != lhs.start ? lhsSuffix - 1 : lhs.start); + args.lhsPercent = lhsSuffix != lhs.start; + args.lhsSuffix = Substring_Init(lhsSuffix, lhs.end); + args.rhs = rhs.str; + + ModifyWords(ch, ModifyWord_SysVSubst, &args, ch->oneBigWord); + +done: + LazyBuf_Done(&lhsBuf); return AMR_OK; } #endif #ifdef SUNSHCMD /* :sh */ static ApplyModifierResult -ApplyModifier_SunShell(const char **pp, const char *val, - ApplyModifiersState *st) +ApplyModifier_SunShell(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; const char *p = *pp; - if (p[1] == 'h' && (p[2] == st->endc || p[2] == ':')) { - if (st->eflags & VARE_WANTRES) { - const char *errfmt; - st->newVal = FStr_InitOwn(Cmd_Exec(val, &errfmt)); - if (errfmt != NULL) - Error(errfmt, val); - } else - st->newVal = FStr_InitRefer(""); - *pp = p + 2; - return AMR_OK; - } else + if (!(p[1] == 'h' && IsDelimiter(p[2], ch))) return AMR_UNKNOWN; + *pp = p + 2; + + if (Expr_ShouldEval(expr)) { + const char *errfmt; + char *output = Cmd_Exec(expr->value.str, &errfmt); + if (errfmt != NULL) + Error(errfmt, expr->value.str); + Expr_SetValueOwn(expr, output); + } + + return AMR_OK; } #endif static void -LogBeforeApply(const ApplyModifiersState *st, const char *mod, char endc, - const char *val) +LogBeforeApply(const ModChain *ch, const char *mod) { - char eflags_str[VarEvalFlags_ToStringSize]; - char vflags_str[VarFlags_ToStringSize]; - Boolean is_single_char = mod[0] != '\0' && - (mod[1] == endc || mod[1] == ':'); + const Expr *expr = ch->expr; + bool is_single_char = mod[0] != '\0' && IsDelimiter(mod[1], ch); + + /* + * At this point, only the first character of the modifier can + * be used since the end of the modifier is not yet known. + */ - /* 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->var->name.str, mod[0], is_single_char ? "" : "...", val, - VarEvalFlags_ToString(eflags_str, st->eflags), - VarFlags_ToString(vflags_str, st->var->flags), - VarExprStatus_Name[st->exprStatus]); + if (!Expr_ShouldEval(expr)) { + debug_printf("Parsing modifier ${%s:%c%s}\n", + expr->name, mod[0], is_single_char ? "" : "..."); + return; + } + + if ((expr->emode == VARE_WANTRES || expr->emode == VARE_UNDEFERR) && + expr->defined == DEF_REGULAR) { + debug_printf( + "Evaluating modifier ${%s:%c%s} on value \"%s\"\n", + expr->name, mod[0], is_single_char ? "" : "...", + expr->value.str); + return; + } + + debug_printf( + "Evaluating modifier ${%s:%c%s} on value \"%s\" (%s, %s)\n", + expr->name, mod[0], is_single_char ? "" : "...", expr->value.str, + VarEvalMode_Name[expr->emode], ExprDefined_Name[expr->defined]); } static void -LogAfterApply(ApplyModifiersState *st, const char *p, const char *mod) +LogAfterApply(const ModChain *ch, const char *p, const char *mod) { - char eflags_str[VarEvalFlags_ToStringSize]; - char vflags_str[VarFlags_ToStringSize]; - const char *quot = st->newVal.str == var_Error ? "" : "\""; - const char *newVal = - st->newVal.str == var_Error ? "error" : st->newVal.str; + const Expr *expr = ch->expr; + const char *value = expr->value.str; + const char *quot = value == var_Error ? "" : "\""; - debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s, %s)\n", - st->var->name.str, (int)(p - mod), mod, quot, newVal, quot, - VarEvalFlags_ToString(eflags_str, st->eflags), - VarFlags_ToString(vflags_str, st->var->flags), - VarExprStatus_Name[st->exprStatus]); + if ((expr->emode == VARE_WANTRES || expr->emode == VARE_UNDEFERR) && + expr->defined == DEF_REGULAR) { + + debug_printf("Result of ${%s:%.*s} is %s%s%s\n", + expr->name, (int)(p - mod), mod, + quot, value == var_Error ? "error" : value, quot); + return; + } + + debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s)\n", + expr->name, (int)(p - mod), mod, + quot, value == var_Error ? "error" : value, quot, + VarEvalMode_Name[expr->emode], + ExprDefined_Name[expr->defined]); } static ApplyModifierResult -ApplyModifier(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier(const char **pp, ModChain *ch) { switch (**pp) { + case '!': + return ApplyModifier_ShellCommand(pp, ch); case ':': - return ApplyModifier_Assign(pp, st); + return ApplyModifier_Assign(pp, ch); + case '?': + return ApplyModifier_IfElse(pp, ch); case '@': - return ApplyModifier_Loop(pp, val, st); + return ApplyModifier_Loop(pp, ch); + case '[': + return ApplyModifier_Words(pp, ch); case '_': - return ApplyModifier_Remember(pp, val, st); + return ApplyModifier_Remember(pp, ch); +#ifndef NO_REGEX + case 'C': + return ApplyModifier_Regex(pp, ch); +#endif case 'D': - case 'U': - return ApplyModifier_Defined(pp, val, 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, val, st); + return ApplyModifier_Defined(pp, ch); + case 'E': + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Suffix); case 'g': - return ApplyModifier_Gmtime(pp, val, st); + return ApplyModifier_Gmtime(pp, ch); + case 'H': + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Head); case 'h': - return ApplyModifier_Hash(pp, val, st); + return ApplyModifier_Hash(pp, ch); + case 'L': + return ApplyModifier_Literal(pp, ch); case 'l': - return ApplyModifier_Localtime(pp, val, st); - case 't': - return ApplyModifier_To(pp, val, st); - case 'N': + return ApplyModifier_Localtime(pp, ch); case 'M': - return ApplyModifier_Match(pp, val, st); - case 'S': - return ApplyModifier_Subst(pp, val, st); - case '?': - return ApplyModifier_IfElse(pp, st); -#ifndef NO_REGEX - case 'C': - return ApplyModifier_Regex(pp, val, st); -#endif - case 'q': + case 'N': + return ApplyModifier_Match(pp, ch); + case 'O': + return ApplyModifier_Order(pp, ch); + case 'P': + return ApplyModifier_Path(pp, ch); case 'Q': - return ApplyModifier_Quote(pp, val, st); - case 'T': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Tail); - case 'H': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Head); - case 'E': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Suffix); + case 'q': + return ApplyModifier_Quote(pp, ch); case 'R': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Root); + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Root); case 'r': - return ApplyModifier_Range(pp, val, st); - case 'O': - return ApplyModifier_Order(pp, val, st); - case 'u': - return ApplyModifier_Unique(pp, val, st); + return ApplyModifier_Range(pp, ch); + case 'S': + return ApplyModifier_Subst(pp, ch); #ifdef SUNSHCMD case 's': - return ApplyModifier_SunShell(pp, val, st); + return ApplyModifier_SunShell(pp, ch); #endif + case 'T': + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Tail); + case 't': + return ApplyModifier_To(pp, ch); + case 'U': + return ApplyModifier_Defined(pp, ch); + case 'u': + return ApplyModifier_Unique(pp, ch); default: return AMR_UNKNOWN; } } -static FStr ApplyModifiers(const char **, FStr, char, char, Var *, - VarExprStatus *, GNode *, VarEvalFlags); +static void ApplyModifiers(Expr *, const char **, char, char); typedef enum ApplyModifiersIndirectResult { /* The indirect modifiers have been applied successfully. */ AMIR_CONTINUE, /* Fall back to the SysV modifier. */ - AMIR_APPLY_MODS, + AMIR_SYSV, /* Error out. */ AMIR_OUT } ApplyModifiersIndirectResult; /* * While expanding a variable expression, expand and apply indirect modifiers, * such as in ${VAR:${M_indirect}}. * * All indirect modifiers of a group must come from a single variable * expression. ${VAR:${M1}} is valid but ${VAR:${M1}${M2}} is not. * * Multiple groups of indirect modifiers can be chained by separating them * with colons. ${VAR:${M1}:${M2}} contains 2 indirect modifiers. * - * If the variable expression is not followed by st->endc or ':', fall + * If the variable expression is not followed by ch->endc or ':', fall * back to trying the SysV modifier, such as in ${VAR:${FROM}=${TO}}. - * - * The expression ${VAR:${M1}${M2}} is not treated as an indirect - * modifier, and it is neither a SysV modifier but a parse error. */ static ApplyModifiersIndirectResult -ApplyModifiersIndirect(ApplyModifiersState *st, const char **pp, - FStr *inout_value) +ApplyModifiersIndirect(ModChain *ch, const char **pp) { + Expr *expr = ch->expr; const char *p = *pp; FStr mods; - (void)Var_Parse(&p, st->scope, st->eflags, &mods); + (void)Var_Parse(&p, expr->scope, expr->emode, &mods); /* TODO: handle errors */ - if (mods.str[0] != '\0' && *p != '\0' && *p != ':' && *p != st->endc) { + if (mods.str[0] != '\0' && *p != '\0' && !IsDelimiter(*p, ch)) { FStr_Done(&mods); - return AMIR_APPLY_MODS; + return AMIR_SYSV; } DEBUG3(VAR, "Indirect modifier \"%s\" from \"%.*s\"\n", mods.str, (int)(p - *pp), *pp); if (mods.str[0] != '\0') { const char *modsp = mods.str; - FStr newVal = ApplyModifiers(&modsp, *inout_value, '\0', '\0', - st->var, &st->exprStatus, st->scope, st->eflags); - *inout_value = newVal; - if (newVal.str == var_Error || *modsp != '\0') { + ApplyModifiers(expr, &modsp, '\0', '\0'); + if (expr->value.str == var_Error || *modsp != '\0') { FStr_Done(&mods); *pp = p; return AMIR_OUT; /* error already reported */ } } FStr_Done(&mods); if (*p == ':') p++; - else if (*p == '\0' && st->endc != '\0') { - Error("Unclosed variable specification after complex " - "modifier (expecting '%c') for %s", - st->endc, st->var->name.str); + else if (*p == '\0' && ch->endc != '\0') { + Error("Unclosed variable expression after indirect " + "modifier, expecting '%c' for variable \"%s\"", + ch->endc, expr->name); *pp = p; return AMIR_OUT; } *pp = p; return AMIR_CONTINUE; } static ApplyModifierResult -ApplySingleModifier(ApplyModifiersState *st, const char *mod, char endc, - const char **pp, FStr *inout_value) +ApplySingleModifier(const char **pp, ModChain *ch) { ApplyModifierResult res; + const char *mod = *pp; const char *p = *pp; - const char *const val = inout_value->str; if (DEBUG(VAR)) - LogBeforeApply(st, mod, endc, val); + LogBeforeApply(ch, mod); - res = ApplyModifier(&p, val, st); + res = ApplyModifier(&p, ch); #ifdef SYSVVARSUB if (res == AMR_UNKNOWN) { assert(p == mod); - res = ApplyModifier_SysV(&p, val, st); + res = ApplyModifier_SysV(&p, ch); } #endif if (res == AMR_UNKNOWN) { - Parse_Error(PARSE_FATAL, "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++) + for (p++; !IsDelimiter(*p, ch) && *p != '\0'; p++) continue; - st->newVal = FStr_InitRefer(var_Error); + Parse_Error(PARSE_FATAL, "Unknown modifier \"%.*s\"", + (int)(p - mod), mod); + Expr_SetValueRefer(ch->expr, var_Error); } if (res == AMR_CLEANUP || res == AMR_BAD) { *pp = p; return res; } if (DEBUG(VAR)) - LogAfterApply(st, p, mod); + LogAfterApply(ch, p, mod); - if (st->newVal.str != val) { - FStr_Done(inout_value); - *inout_value = st->newVal; - } - if (*p == '\0' && st->endc != '\0') { + if (*p == '\0' && ch->endc != '\0') { Error( - "Unclosed variable specification (expecting '%c') " - "for \"%s\" (value \"%s\") modifier %c", - st->endc, st->var->name.str, inout_value->str, *mod); + "Unclosed variable expression, expecting '%c' for " + "modifier \"%.*s\" of variable \"%s\" with value \"%s\"", + ch->endc, + (int)(p - mod), mod, + ch->expr->name, ch->expr->value.str); } else if (*p == ':') { p++; - } else if (opts.strict && *p != '\0' && *p != endc) { + } else if (opts.strict && *p != '\0' && *p != ch->endc) { Parse_Error(PARSE_FATAL, "Missing delimiter ':' after modifier \"%.*s\"", (int)(p - mod), mod); /* * TODO: propagate parse error to the enclosing * expression */ } *pp = p; return AMR_OK; } +#if __STDC_VERSION__ >= 199901L +#define ModChain_Literal(expr, startc, endc, sep, oneBigWord) \ + (ModChain) { expr, startc, endc, sep, oneBigWord } +#else +MAKE_INLINE ModChain +ModChain_Literal(Expr *expr, char startc, char endc, char sep, bool oneBigWord) +{ + ModChain ch; + ch.expr = expr; + ch.startc = startc; + ch.endc = endc; + ch.sep = sep; + ch.oneBigWord = oneBigWord; + return ch; +} +#endif + /* Apply any modifiers (such as :Mpattern or :@var@loop@ or :Q or ::=value). */ -static FStr +static void ApplyModifiers( - const char **pp, /* the parsing position, updated upon return */ - FStr value, /* the current value of the expression */ - char startc, /* '(' or '{', or '\0' for indirect modifiers */ - char endc, /* ')' or '}', or '\0' for indirect modifiers */ - Var *v, - VarExprStatus *exprStatus, - GNode *scope, /* for looking up and modifying variables */ - VarEvalFlags eflags + Expr *expr, + const char **pp, /* the parsing position, updated upon return */ + char startc, /* '(' or '{'; or '\0' for indirect modifiers */ + char endc /* ')' or '}'; or '\0' for indirect modifiers */ ) { - ApplyModifiersState st = { - startc, endc, v, scope, eflags, -#if defined(lint) - /* lint cannot parse C99 struct initializers yet. */ - { var_Error, NULL }, -#else - FStr_InitRefer(var_Error), /* .newVal */ -#endif - ' ', /* .sep */ - FALSE, /* .oneBigWord */ - *exprStatus /* .exprStatus */ - }; + ModChain ch = ModChain_Literal(expr, startc, endc, ' ', false); const char *p; const char *mod; assert(startc == '(' || startc == '{' || startc == '\0'); assert(endc == ')' || endc == '}' || endc == '\0'); - assert(value.str != NULL); + assert(expr->value.str != NULL); p = *pp; if (*p == '\0' && endc != '\0') { Error( "Unclosed variable expression (expecting '%c') for \"%s\"", - st.endc, st.var->name.str); + ch.endc, expr->name); goto cleanup; } while (*p != '\0' && *p != endc) { ApplyModifierResult res; if (*p == '$') { - ApplyModifiersIndirectResult amir; - amir = ApplyModifiersIndirect(&st, &p, &value); + ApplyModifiersIndirectResult amir = + ApplyModifiersIndirect(&ch, &p); if (amir == AMIR_CONTINUE) continue; if (amir == AMIR_OUT) break; + /* + * It's neither '${VAR}:' nor '${VAR}}'. Try to parse + * it as a SysV modifier, as that is the only modifier + * that can start with '$'. + */ } - /* default value, in case of errors */ - st.newVal = FStr_InitRefer(var_Error); mod = p; - res = ApplySingleModifier(&st, mod, endc, &p, &value); + res = ApplySingleModifier(&p, &ch); if (res == AMR_CLEANUP) goto cleanup; if (res == AMR_BAD) goto bad_modifier; } *pp = p; - assert(value.str != NULL); /* Use var_Error or varUndefined instead. */ - *exprStatus = st.exprStatus; - return value; + assert(expr->value.str != NULL); /* Use var_Error or varUndefined. */ + return; bad_modifier: /* XXX: The modifier end is only guessed. */ - Error("Bad modifier `:%.*s' for %s", - (int)strcspn(mod, ":)}"), mod, st.var->name.str); + Error("Bad modifier \":%.*s\" for variable \"%s\"", + (int)strcspn(mod, ":)}"), mod, expr->name); cleanup: + /* + * TODO: Use p + strlen(p) instead, to stop parsing immediately. + * + * In the unit tests, this generates a few unterminated strings in the + * shell commands though. Instead of producing these unfinished + * strings, commands with evaluation errors should not be run at all. + * + * To make that happen, Var_Subst must report the actual errors + * instead of returning VPR_OK unconditionally. + */ *pp = p; - FStr_Done(&value); - *exprStatus = st.exprStatus; - return FStr_InitRefer(var_Error); + Expr_SetValueRefer(expr, 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. + * Only 4 of the 7 local variables are treated specially as they are the only + * ones that will be set when dynamic sources are expanded. */ -static Boolean -VarnameIsDynamic(const char *name, size_t len) +static bool +VarnameIsDynamic(Substring varname) { + const char *name; + size_t len; + + name = varname.start; + len = Substring_Length(varname); if (len == 1 || (len == 2 && (name[1] == 'F' || name[1] == 'D'))) { switch (name[0]) { case '@': case '%': case '*': case '!': - return TRUE; + return true; } - return FALSE; + 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 Substring_Equals(varname, ".TARGET") || + Substring_Equals(varname, ".ARCHIVE") || + Substring_Equals(varname, ".PREFIX") || + Substring_Equals(varname, ".MEMBER"); } - return FALSE; + return false; } static const char * UndefinedShortVarValue(char varname, const GNode *scope) { if (scope == SCOPE_CMDLINE || scope == SCOPE_GLOBAL) { /* * If substituting a local variable in a non-local scope, * 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 NULL; } /* * Parse a variable name, until the end character or a colon, whichever * comes first. */ -static char * +static void ParseVarname(const char **pp, char startc, char endc, - GNode *scope, VarEvalFlags eflags, - size_t *out_varname_len) + GNode *scope, VarEvalMode emode, + LazyBuf *buf) { - Buffer buf; const char *p = *pp; - int depth = 1; + int depth = 0; /* Track depth so we can spot parse errors. */ - Buf_Init(&buf); + LazyBuf_Init(buf, p); while (*p != '\0') { - /* Track depth so we can spot parse errors. */ + if ((*p == endc || *p == ':') && depth == 0) + break; if (*p == startc) depth++; - if (*p == endc) { - if (--depth == 0) - break; - } - if (*p == ':' && depth == 1) - break; + if (*p == endc) + depth--; /* A variable inside a variable, expand. */ if (*p == '$') { FStr nested_val; - (void)Var_Parse(&p, scope, eflags, &nested_val); + (void)Var_Parse(&p, scope, emode, &nested_val); /* TODO: handle errors */ - Buf_AddStr(&buf, nested_val.str); + LazyBuf_AddStr(buf, nested_val.str); FStr_Done(&nested_val); } else { - Buf_AddByte(&buf, *p); + LazyBuf_Add(buf, *p); p++; } } *pp = p; - *out_varname_len = buf.len; - return Buf_DoneData(&buf); } static VarParseResult ValidShortVarname(char varname, const char *start) { - switch (varname) { - case '\0': - case ')': - case '}': - case ':': - case '$': - break; /* and continue below */ - default: + if (varname != '$' && varname != ':' && varname != '}' && + varname != ')' && varname != '\0') return VPR_OK; - } if (!opts.strict) return VPR_ERR; /* XXX: Missing error message */ 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 VPR_ERR; } /* - * Parse a single-character variable name such as $V or $@. + * Parse a single-character variable name such as in $V or $@. * Return whether to continue parsing. */ -static Boolean -ParseVarnameShort(char startc, const char **pp, GNode *scope, - VarEvalFlags eflags, - VarParseResult *out_FALSE_res, const char **out_FALSE_val, - Var **out_TRUE_var) +static bool +ParseVarnameShort(char varname, const char **pp, GNode *scope, + VarEvalMode emode, + 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. - */ - - vpr = ValidShortVarname(startc, *pp); + vpr = ValidShortVarname(varname, *pp); if (vpr != VPR_OK) { (*pp)++; - *out_FALSE_val = var_Error; - *out_FALSE_res = vpr; - return FALSE; + *out_false_res = vpr; + *out_false_val = var_Error; + return false; } - name[0] = startc; + name[0] = varname; name[1] = '\0'; - v = VarFind(name, scope, TRUE); + v = VarFind(name, scope, true); if (v == NULL) { const char *val; *pp += 2; - val = UndefinedShortVarValue(startc, scope); + val = UndefinedShortVarValue(varname, scope); if (val == NULL) - val = eflags & VARE_UNDEFERR ? var_Error : varUndefined; + val = emode == VARE_UNDEFERR + ? var_Error : varUndefined; if (opts.strict && val == var_Error) { Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", name); - *out_FALSE_res = VPR_ERR; - *out_FALSE_val = val; - return FALSE; + *out_false_res = VPR_ERR; + *out_false_val = val; + return false; } /* * XXX: This looks completely wrong. * * If undefined expressions are not allowed, this should * rather be VPR_ERR instead of VPR_UNDEF, together with an * error message. * * If undefined expressions are allowed, this should rather * be VPR_UNDEF instead of VPR_OK. */ - *out_FALSE_res = eflags & VARE_UNDEFERR ? VPR_UNDEF : VPR_OK; - *out_FALSE_val = val; - return FALSE; + *out_false_res = emode == VARE_UNDEFERR + ? VPR_UNDEF : VPR_OK; + *out_false_val = val; + return false; } - *out_TRUE_var = v; - return TRUE; + *out_true_var = v; + return true; } /* Find variables like @F or ", varname[0]) == NULL) + if (strchr("@%?*!<>", varname.start[0]) == NULL) return NULL; - { - char name[] = { varname[0], '\0' }; - Var *v = VarFind(name, scope, FALSE); - - if (v != NULL) { - if (varname[1] == 'D') { - *out_extraModifiers = "H:"; - } else { /* F */ - *out_extraModifiers = "T:"; - } - } - return v; - } + v = VarFindSubstring(Substring_Sub(varname, 0, 1), scope, false); + if (v == NULL) + return NULL; + + *out_extraModifiers = varname.start[1] == 'D' ? "H:" : "T:"; + return v; } static VarParseResult -EvalUndefined(Boolean dynamic, const char *start, const char *p, char *varname, - VarEvalFlags eflags, - FStr *out_val) +EvalUndefined(bool dynamic, const char *start, const char *p, + Substring varname, VarEvalMode emode, FStr *out_val) { if (dynamic) { *out_val = FStr_InitOwn(bmake_strsedup(start, p)); - free(varname); return VPR_OK; } - if ((eflags & VARE_UNDEFERR) && opts.strict) { + if (emode == VARE_UNDEFERR && opts.strict) { Parse_Error(PARSE_FATAL, - "Variable \"%s\" is undefined", varname); - free(varname); + "Variable \"%.*s\" is undefined", + (int)Substring_Length(varname), varname.start); *out_val = FStr_InitRefer(var_Error); return VPR_ERR; } - if (eflags & VARE_UNDEFERR) { - free(varname); + if (emode == VARE_UNDEFERR) { *out_val = FStr_InitRefer(var_Error); return VPR_UNDEF; /* XXX: Should be VPR_ERR instead. */ } - free(varname); *out_val = FStr_InitRefer(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 +static bool ParseVarnameLong( - const char *p, + const char **pp, char startc, GNode *scope, - VarEvalFlags eflags, - - const char **out_FALSE_pp, - VarParseResult *out_FALSE_res, - FStr *out_FALSE_val, - - 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, - VarExprStatus *out_TRUE_exprStatus + VarEvalMode emode, + + const char **out_false_pp, + VarParseResult *out_false_res, + FStr *out_false_val, + + char *out_true_endc, + Var **out_true_v, + bool *out_true_haveModifier, + const char **out_true_extraModifiers, + bool *out_true_dynamic, + ExprDefined *out_true_exprDefined ) { - size_t namelen; - char *varname; + LazyBuf varname; Var *v; - Boolean haveModifier; - Boolean dynamic = FALSE; + bool haveModifier; + bool dynamic = false; + const char *p = *pp; const char *const start = p; char endc = startc == '(' ? ')' : '}'; p += 2; /* skip "${" or "$(" or "y(" */ - varname = ParseVarname(&p, startc, endc, scope, eflags, &namelen); + ParseVarname(&p, startc, endc, scope, emode, &varname); if (*p == ':') { - haveModifier = TRUE; + haveModifier = true; } else if (*p == endc) { - haveModifier = FALSE; + haveModifier = false; } else { - Parse_Error(PARSE_FATAL, "Unclosed variable \"%s\"", varname); - free(varname); - *out_FALSE_pp = p; - *out_FALSE_val = FStr_InitRefer(var_Error); - *out_FALSE_res = VPR_ERR; - return FALSE; + Substring name = LazyBuf_Get(&varname); + Parse_Error(PARSE_FATAL, "Unclosed variable \"%.*s\"", + (int)Substring_Length(name), name.start); + LazyBuf_Done(&varname); + *out_false_pp = p; + *out_false_val = FStr_InitRefer(var_Error); + *out_false_res = VPR_ERR; + return false; } - v = VarFind(varname, scope, TRUE); + v = VarFindSubstring(LazyBuf_Get(&varname), scope, true); /* At this point, p points just after the variable name, * either at ':' or at endc. */ if (v == NULL) { - v = FindLocalLegacyVar(varname, namelen, scope, - out_TRUE_extraModifiers); + v = FindLocalLegacyVar(LazyBuf_Get(&varname), scope, + out_true_extraModifiers); } if (v == NULL) { /* * Defer expansion of dynamic variables if they appear in * non-local scope since they are not defined there. */ - dynamic = VarnameIsDynamic(varname, namelen) && + dynamic = VarnameIsDynamic(LazyBuf_Get(&varname)) && (scope == SCOPE_CMDLINE || scope == SCOPE_GLOBAL); if (!haveModifier) { p++; /* skip endc */ - *out_FALSE_pp = p; - *out_FALSE_res = EvalUndefined(dynamic, start, p, - varname, eflags, out_FALSE_val); - return FALSE; + *out_false_pp = p; + *out_false_res = EvalUndefined(dynamic, start, p, + LazyBuf_Get(&varname), emode, 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, + * state (VES_UNDEF), only a few modifiers like :D, :U, :L, * :P turn this undefined expression into a defined - * expression (VEF_DEF). + * expression (VES_DEF). * - * At the end, after applying all modifiers, if the expression + * In 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(FStr_InitOwn(varname), "", VAR_NONE); - *out_TRUE_exprStatus = VES_UNDEF; + v = VarNew(LazyBuf_DoneGet(&varname), "", false, false); + *out_true_exprDefined = DEF_UNDEF; } else - free(varname); + LazyBuf_Done(&varname); - *out_TRUE_endc = endc; - *out_TRUE_p = p; - *out_TRUE_v = v; - *out_TRUE_haveModifier = haveModifier; - *out_TRUE_dynamic = dynamic; - return TRUE; + *pp = p; + *out_true_endc = endc; + *out_true_v = v; + *out_true_haveModifier = haveModifier; + *out_true_dynamic = dynamic; + return true; } /* Free the environment variable now since we own it. */ static void -FreeEnvVar(void **out_val_freeIt, Var *v, const char *value) +FreeEnvVar(Var *v, FStr *inout_val) { char *varValue = Buf_DoneData(&v->val); - if (value == varValue) - *out_val_freeIt = varValue; + if (inout_val->str == varValue) + inout_val->freeIt = varValue; else free(varValue); FStr_Done(&v->name); free(v); } +#if __STDC_VERSION__ >= 199901L +#define Expr_Literal(name, value, emode, scope, defined) \ + { name, value, emode, scope, defined } +#else +MAKE_INLINE Expr +Expr_Literal(const char *name, FStr value, + VarEvalMode emode, GNode *scope, ExprDefined defined) +{ + Expr expr; + + expr.name = name; + expr.value = value; + expr.emode = emode; + expr.scope = scope; + expr.defined = defined; + return expr; +} +#endif + +/* + * Expressions of the form ${:U...} with a trivial value are often generated + * by .for loops and are boring, therefore parse and evaluate them in a fast + * lane without debug logging. + */ +static bool +Var_Parse_FastLane(const char **pp, VarEvalMode emode, FStr *out_value) +{ + const char *p; + + p = *pp; + if (!(p[0] == '$' && p[1] == '{' && p[2] == ':' && p[3] == 'U')) + return false; + + p += 4; + while (*p != '$' && *p != '{' && *p != ':' && *p != '\\' && + *p != '}' && *p != '\0') + p++; + if (*p != '}') + return false; + + if (emode == VARE_PARSE_ONLY) + *out_value = FStr_InitRefer(""); + else + *out_value = FStr_InitOwn(bmake_strsedup(*pp + 4, p)); + *pp = p + 1; + return true; +} + /* * 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. * * Input: * *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. * scope The scope for finding variables - * eflags Control the exact details of parsing + * emode Controls the exact details of parsing and evaluation * * 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 + * undefined, emode is 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, + * was undefined, emode was not 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 *scope, VarEvalFlags eflags, FStr *out_val) +Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val) { const char *p = *pp; const char *const start = p; - /* TRUE if have modifiers for the variable. */ - Boolean haveModifier; + /* true if have modifiers for the variable. */ + bool haveModifier; /* Starting character if variable in parens or braces. */ char startc; /* Ending character if variable in parens or braces. */ char endc; /* - * TRUE if the variable is local and we're expanding it in a + * true if the variable is local and we're expanding it in a * non-local scope. This is done to support dynamic sources. * The result is just the expression, unaltered. */ - Boolean dynamic; + bool dynamic; const char *extramodifiers; Var *v; - FStr value; - char eflags_str[VarEvalFlags_ToStringSize]; - VarExprStatus exprStatus = VES_NONE; + Expr expr = Expr_Literal(NULL, FStr_InitRefer(NULL), emode, + scope, DEF_REGULAR); - DEBUG2(VAR, "Var_Parse: %s with %s\n", start, - VarEvalFlags_ToString(eflags_str, eflags)); + if (Var_Parse_FastLane(pp, emode, out_val)) + return VPR_OK; + + DEBUG2(VAR, "Var_Parse: %s (%s)\n", start, VarEvalMode_Name[emode]); *out_val = FStr_InitRefer(NULL); extramodifiers = NULL; /* extra modifiers to apply first */ - dynamic = FALSE; + dynamic = false; /* * Appease GCC, which thinks that the variable might not be * initialized. */ endc = '\0'; startc = p[1]; if (startc != '(' && startc != '{') { VarParseResult res; - if (!ParseVarnameShort(startc, pp, scope, eflags, &res, + if (!ParseVarnameShort(startc, pp, scope, emode, &res, &out_val->str, &v)) return res; - haveModifier = FALSE; + haveModifier = false; p++; } else { VarParseResult res; - if (!ParseVarnameLong(p, startc, scope, eflags, + if (!ParseVarnameLong(&p, startc, scope, emode, pp, &res, out_val, - &endc, &p, &v, &haveModifier, &extramodifiers, - &dynamic, &exprStatus)) + &endc, &v, &haveModifier, &extramodifiers, + &dynamic, &expr.defined)) return res; } - if (v->flags & VAR_IN_USE) + expr.name = v->name.str; + if (v->inUse) Fatal("Variable %s is recursive.", v->name.str); /* * 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 = FStr_InitRefer(v->val.data); + expr.value = FStr_InitRefer(v->val.data); /* * Before applying any modifiers, expand any nested expressions from * the variable value. */ - if (strchr(value.str, '$') != NULL && (eflags & VARE_WANTRES)) { + if (strchr(expr.value.str, '$') != NULL && + VarEvalMode_ShouldEval(emode)) { char *expanded; - VarEvalFlags nested_eflags = eflags; + VarEvalMode nested_emode = emode; if (opts.strict) - nested_eflags &= ~(unsigned)VARE_UNDEFERR; - v->flags |= VAR_IN_USE; - (void)Var_Subst(value.str, scope, nested_eflags, &expanded); - v->flags &= ~(unsigned)VAR_IN_USE; + nested_emode = VarEvalMode_UndefOk(nested_emode); + v->inUse = true; + (void)Var_Subst(expr.value.str, scope, nested_emode, + &expanded); + v->inUse = false; /* TODO: handle errors */ - value = FStr_InitOwn(expanded); + Expr_SetValueOwn(&expr, expanded); } - if (haveModifier || extramodifiers != NULL) { - if (extramodifiers != NULL) { - const char *em = extramodifiers; - value = ApplyModifiers(&em, value, '\0', '\0', - v, &exprStatus, scope, eflags); - } - - if (haveModifier) { - p++; /* Skip initial colon. */ + if (extramodifiers != NULL) { + const char *em = extramodifiers; + ApplyModifiers(&expr, &em, '\0', '\0'); + } - value = ApplyModifiers(&p, value, startc, endc, - v, &exprStatus, scope, eflags); - } + if (haveModifier) { + p++; /* Skip initial colon. */ + ApplyModifiers(&expr, &p, startc, endc); } if (*p != '\0') /* Skip past endc if possible. */ p++; *pp = p; - if (v->flags & VAR_FROM_ENV) { - FreeEnvVar(&value.freeIt, v, value.str); + if (v->fromEnv) { + FreeEnvVar(v, &expr.value); - } else if (exprStatus != VES_NONE) { - if (exprStatus != VES_DEF) { - FStr_Done(&value); + } else if (expr.defined != DEF_REGULAR) { + if (expr.defined == DEF_UNDEF) { if (dynamic) { - value = FStr_InitOwn(bmake_strsedup(start, p)); + Expr_SetValueOwn(&expr, + bmake_strsedup(start, p)); } else { /* * The expression is still undefined, * therefore discard the actual value and * return an error marker instead. */ - value = FStr_InitRefer(eflags & VARE_UNDEFERR - ? var_Error : varUndefined); + Expr_SetValueRefer(&expr, + emode == VARE_UNDEFERR + ? var_Error : varUndefined); } } - if (value.str != v->val.data) + /* XXX: This is not standard memory management. */ + if (expr.value.str != v->val.data) Buf_Done(&v->val); FStr_Done(&v->name); free(v); } - *out_val = (FStr){ value.str, value.freeIt }; + *out_val = expr.value; return VPR_OK; /* XXX: Is not correct in all cases */ } static void -VarSubstDollarDollar(const char **pp, Buffer *res, VarEvalFlags eflags) +VarSubstDollarDollar(const char **pp, Buffer *res, VarEvalMode emode) { - /* - * A dollar sign may be escaped with another dollar - * sign. - */ - if (save_dollars && (eflags & VARE_KEEP_DOLLAR)) + /* A dollar sign may be escaped with another dollar sign. */ + if (save_dollars && VarEvalMode_ShouldKeepDollar(emode)) Buf_AddByte(res, '$'); Buf_AddByte(res, '$'); *pp += 2; } static void VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, - VarEvalFlags eflags, Boolean *inout_errorReported) + VarEvalMode emode, bool *inout_errorReported) { const char *p = *pp; const char *nested_p = p; FStr val; - (void)Var_Parse(&nested_p, scope, eflags, &val); + (void)Var_Parse(&nested_p, scope, emode, &val); /* TODO: handle errors */ if (val.str == var_Error || val.str == varUndefined) { - if (!(eflags & VARE_KEEP_UNDEF)) { + if (!VarEvalMode_ShouldKeepUndef(emode)) { p = nested_p; - } else if ((eflags & VARE_UNDEFERR) || val.str == var_Error) { + } else if (emode == VARE_UNDEFERR || val.str == 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; + *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.str); } FStr_Done(&val); *pp = p; } /* * Skip as many characters as possible -- either to the end of the string * or to the next dollar sign (variable expression). */ static void VarSubstPlain(const char **pp, Buffer *res) { const char *p = *pp; const char *start = p; for (p++; *p != '$' && *p != '\0'; p++) continue; Buf_AddBytesBetween(res, start, p); *pp = p; } /* * Expand all variable expressions like $V, ${VAR}, $(VAR:Modifiers) in the * given string. * * Input: * str The string in which the variable expressions are * expanded. * scope The scope in which to start searching for * variables. The other scopes are searched as well. - * eflags Special effects during expansion. + * emode The mode for parsing or evaluating subexpressions. */ VarParseResult -Var_Subst(const char *str, GNode *scope, VarEvalFlags eflags, char **out_res) +Var_Subst(const char *str, GNode *scope, VarEvalMode emode, char **out_res) { const char *p = str; Buffer res; /* 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; + static bool errorReported; Buf_Init(&res); - errorReported = FALSE; + errorReported = false; while (*p != '\0') { if (p[0] == '$' && p[1] == '$') - VarSubstDollarDollar(&p, &res, eflags); + VarSubstDollarDollar(&p, &res, emode); else if (p[0] == '$') - VarSubstExpr(&p, &res, scope, eflags, &errorReported); + VarSubstExpr(&p, &res, scope, emode, &errorReported); else VarSubstPlain(&p, &res); } *out_res = Buf_DoneDataCompact(&res); return VPR_OK; } /* Initialize the variables module. */ void Var_Init(void) { SCOPE_INTERNAL = GNode_New("Internal"); SCOPE_GLOBAL = GNode_New("Global"); SCOPE_CMDLINE = GNode_New("Command"); } /* Clean up the variables module. */ void Var_End(void) { Var_Stats(); } void Var_Stats(void) { HashTable_DebugStats(&SCOPE_GLOBAL->vars, "Global variables"); } /* Print all variables in a scope, sorted by name. */ void Var_Dump(GNode *scope) { Vector /* of const char * */ vec; HashIter hi; size_t i; const char **varnames; Vector_Init(&vec, sizeof(const char *)); HashIter_Init(&hi, &scope->vars); 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(&scope->vars, varname); debug_printf("%-16s = %s\n", varname, var->val.data); } Vector_Done(&vec); }