diff options
author | Simon J. Gerraty <sjg@FreeBSD.org> | 2020-11-07 19:39:21 +0000 |
---|---|---|
committer | Simon J. Gerraty <sjg@FreeBSD.org> | 2020-11-07 19:39:21 +0000 |
commit | 302da1a3d35c15cb29d76e0a939f8bcb13f7ad80 (patch) | |
tree | c2146dca82d530521c4d2cc46a95c26964311a2c | |
parent | 6bbc783f48498b808e19db4441299dc7d85a278b (diff) | |
download | src-test2-302da1a3d35c15cb29d76e0a939f8bcb13f7ad80.tar.gz src-test2-302da1a3d35c15cb29d76e0a939f8bcb13f7ad80.zip |
Notes
387 files changed, 15726 insertions, 12190 deletions
diff --git a/ChangeLog b/ChangeLog index 847c2e4c0f90..82995f735b2d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,267 @@ +2020-11-01 Simon J Gerraty <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * configure.in: remove inappropriate use of AC_INCLUDES_DEFAULT + +2020-10-18 Simon J Gerraty <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20201001 + Merge with NetBSD make, pick up + o compat.c: comment about "..." + +2020-09-30 Simon J Gerraty <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * make-bootstrap.sh.in: adjust object list + 2020-09-02 Simon J Gerraty <sjg@beast.crufty.net> * VERSION (_MAKE_VERSION): 20200902 @@ -65,18 +65,16 @@ sigcompat.c str.c stresep.c strlcpy.c -strlist.c -strlist.h suff.c targ.c trace.c trace.h unit-tests/Makefile unit-tests/Makefile.config.in -unit-tests/archive.exp -unit-tests/archive.mk unit-tests/archive-suffix.exp unit-tests/archive-suffix.mk +unit-tests/archive.exp +unit-tests/archive.mk unit-tests/cmd-interrupt.exp unit-tests/cmd-interrupt.mk unit-tests/cmdline.exp @@ -99,8 +97,8 @@ 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-func.exp -unit-tests/cond-func.mk +unit-tests/cond-cmp-unary.exp +unit-tests/cond-cmp-unary.mk unit-tests/cond-func-commands.exp unit-tests/cond-func-commands.mk unit-tests/cond-func-defined.exp @@ -113,6 +111,8 @@ 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.exp @@ -135,26 +135,36 @@ 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/cond2.exp -unit-tests/cond2.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 @@ -185,10 +195,10 @@ unit-tests/depsrc-silent.exp unit-tests/depsrc-silent.mk unit-tests/depsrc-use.exp unit-tests/depsrc-use.mk -unit-tests/depsrc-usebefore.exp -unit-tests/depsrc-usebefore.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 @@ -199,6 +209,8 @@ unit-tests/deptgt-default.exp unit-tests/deptgt-default.mk unit-tests/deptgt-delete_on_error.exp unit-tests/deptgt-delete_on_error.mk +unit-tests/deptgt-end-jobs.exp +unit-tests/deptgt-end-jobs.mk unit-tests/deptgt-end.exp unit-tests/deptgt-end.mk unit-tests/deptgt-error.exp @@ -239,10 +251,12 @@ unit-tests/deptgt-suffixes.exp unit-tests/deptgt-suffixes.mk unit-tests/deptgt.exp unit-tests/deptgt.mk -unit-tests/dir.exp -unit-tests/dir.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 @@ -261,14 +275,18 @@ unit-tests/directive-error.exp unit-tests/directive-error.mk unit-tests/directive-export-env.exp unit-tests/directive-export-env.mk +unit-tests/directive-export-gmake.exp +unit-tests/directive-export-gmake.mk unit-tests/directive-export-literal.exp unit-tests/directive-export-literal.mk unit-tests/directive-export.exp unit-tests/directive-export.mk -unit-tests/directive-for.exp -unit-tests/directive-for.mk unit-tests/directive-for-generating-endif.exp unit-tests/directive-for-generating-endif.mk +unit-tests/directive-for.exp +unit-tests/directive-for.mk +unit-tests/directive-hyphen-include.exp +unit-tests/directive-hyphen-include.mk unit-tests/directive-if.exp unit-tests/directive-if.mk unit-tests/directive-ifdef.exp @@ -279,8 +297,14 @@ unit-tests/directive-ifndef.exp unit-tests/directive-ifndef.mk unit-tests/directive-ifnmake.exp unit-tests/directive-ifnmake.mk +unit-tests/directive-include-fatal.exp +unit-tests/directive-include-fatal.mk +unit-tests/directive-include.exp +unit-tests/directive-include.mk unit-tests/directive-info.exp unit-tests/directive-info.mk +unit-tests/directive-sinclude.exp +unit-tests/directive-sinclude.mk unit-tests/directive-undef.exp unit-tests/directive-undef.mk unit-tests/directive-unexport-env.exp @@ -317,20 +341,20 @@ unit-tests/forloop.exp unit-tests/forloop.mk unit-tests/forsubst.exp unit-tests/forsubst.mk -unit-tests/hash.exp -unit-tests/hash.mk +unit-tests/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-output-long-lines.exp +unit-tests/job-output-long-lines.mk unit-tests/lint.exp unit-tests/lint.mk unit-tests/make-exported.exp unit-tests/make-exported.mk -unit-tests/misc.exp -unit-tests/misc.mk unit-tests/moderrs.exp unit-tests/moderrs.mk unit-tests/modmatch.exp @@ -345,10 +369,56 @@ unit-tests/opt-backwards.exp unit-tests/opt-backwards.mk unit-tests/opt-chdir.exp unit-tests/opt-chdir.mk +unit-tests/opt-debug-all.exp +unit-tests/opt-debug-all.mk +unit-tests/opt-debug-archive.exp +unit-tests/opt-debug-archive.mk +unit-tests/opt-debug-cond.exp +unit-tests/opt-debug-cond.mk +unit-tests/opt-debug-curdir.exp +unit-tests/opt-debug-curdir.mk +unit-tests/opt-debug-dir.exp +unit-tests/opt-debug-dir.mk +unit-tests/opt-debug-errors.exp +unit-tests/opt-debug-errors.mk +unit-tests/opt-debug-file.exp +unit-tests/opt-debug-file.mk +unit-tests/opt-debug-for.exp +unit-tests/opt-debug-for.mk +unit-tests/opt-debug-graph1.exp +unit-tests/opt-debug-graph1.mk +unit-tests/opt-debug-graph2.exp +unit-tests/opt-debug-graph2.mk +unit-tests/opt-debug-graph3.exp +unit-tests/opt-debug-graph3.mk +unit-tests/opt-debug-hash.exp +unit-tests/opt-debug-hash.mk +unit-tests/opt-debug-jobs.exp +unit-tests/opt-debug-jobs.mk +unit-tests/opt-debug-lint.exp +unit-tests/opt-debug-lint.mk +unit-tests/opt-debug-loud.exp +unit-tests/opt-debug-loud.mk +unit-tests/opt-debug-making.exp +unit-tests/opt-debug-making.mk +unit-tests/opt-debug-meta.exp +unit-tests/opt-debug-meta.mk +unit-tests/opt-debug-no-rm.exp +unit-tests/opt-debug-no-rm.mk +unit-tests/opt-debug-parse.exp +unit-tests/opt-debug-parse.mk +unit-tests/opt-debug-suff.exp +unit-tests/opt-debug-suff.mk +unit-tests/opt-debug-targets.exp +unit-tests/opt-debug-targets.mk +unit-tests/opt-debug-var.exp +unit-tests/opt-debug-var.mk +unit-tests/opt-debug-varraw.exp +unit-tests/opt-debug-varraw.mk +unit-tests/opt-debug-x-trace.exp +unit-tests/opt-debug-x-trace.mk unit-tests/opt-debug.exp unit-tests/opt-debug.mk -unit-tests/opt-debug-g1.exp -unit-tests/opt-debug-g1.mk unit-tests/opt-define.exp unit-tests/opt-define.mk unit-tests/opt-env.exp @@ -395,6 +465,8 @@ 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 @@ -425,12 +497,34 @@ unit-tests/sh-single-line.exp unit-tests/sh-single-line.mk unit-tests/sh.exp unit-tests/sh.mk -unit-tests/suffixes.exp -unit-tests/suffixes.mk +unit-tests/shell-csh.exp +unit-tests/shell-csh.mk +unit-tests/shell-custom.exp +unit-tests/shell-custom.mk +unit-tests/shell-ksh.exp +unit-tests/shell-ksh.mk +unit-tests/shell-sh.exp +unit-tests/shell-sh.mk +unit-tests/suff-add-later.exp +unit-tests/suff-add-later.mk +unit-tests/suff-clear-regular.exp +unit-tests/suff-clear-regular.mk +unit-tests/suff-clear-single.exp +unit-tests/suff-clear-single.mk +unit-tests/suff-lookup.exp +unit-tests/suff-lookup.mk +unit-tests/suff-main.exp +unit-tests/suff-main.mk +unit-tests/suff-rebuild.exp +unit-tests/suff-rebuild.mk +unit-tests/suff-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/sysv.exp -unit-tests/sysv.mk unit-tests/ternary.exp unit-tests/ternary.mk unit-tests/unexport-env.exp @@ -461,8 +555,12 @@ 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 @@ -555,12 +653,12 @@ 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-includes.exp -unit-tests/varname-dot-includes.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 @@ -623,8 +721,12 @@ 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 @@ -635,6 +737,10 @@ unit-tests/varname.exp unit-tests/varname.mk unit-tests/varparse-dynamic.exp unit-tests/varparse-dynamic.mk +unit-tests/varparse-mod.exp +unit-tests/varparse-mod.mk +unit-tests/varparse-undef-partial.exp +unit-tests/varparse-undef-partial.mk unit-tests/varquote.exp unit-tests/varquote.mk unit-tests/varshell.exp @@ -1,4 +1,4 @@ -# $Id: Makefile,v 1.112 2020/08/28 16:26:17 sjg Exp $ +# $Id: Makefile,v 1.113 2020/10/26 17:55:09 sjg Exp $ PROG= bmake @@ -20,7 +20,6 @@ SRCS= \ metachar.c \ parse.c \ str.c \ - strlist.c \ suff.c \ targ.c \ trace.c \ @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20200902 +_MAKE_VERSION=20201101 @@ -1,4 +1,4 @@ -/* $NetBSD: arch.c,v 1.107 2020/08/30 11:15:05 rillig Exp $ */ +/* $NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,19 +68,6 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: arch.c,v 1.107 2020/08/30 11:15:05 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)arch.c 8.2 (Berkeley) 1/2/94"; -#else -__RCSID("$NetBSD: arch.c,v 1.107 2020/08/30 11:15:05 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - /*- * arch.c -- * Functions to manipulate libraries, archives and their members. @@ -92,52 +79,53 @@ __RCSID("$NetBSD: arch.c,v 1.107 2020/08/30 11:15:05 rillig Exp $"); * is referenced. * * The interface to this module is: - * Arch_ParseArchive Given an archive specification, return a list - * of GNode's, one for each member in the spec. - * FALSE is returned if the specification is - * invalid for some reason. + * Arch_ParseArchive + * Given an archive specification, return a list + * of GNode's, one for each member in the spec. + * FALSE is returned if the specification is + * invalid for some reason. * - * Arch_Touch Alter the modification time of the archive - * member described by the given node to be - * the current time. + * Arch_Touch Alter the modification time of the archive + * member described by the given node to be + * the current time. * - * Arch_TouchLib Update the modification time of the library - * described by the given node. This is special - * because it also updates the modification time - * of the library's table of contents. + * Arch_TouchLib Update the modification time of the library + * described by the given node. This is special + * because it also updates the modification time + * of the library's table of contents. * - * Arch_MTime Find the modification time of a member of - * an archive *in the archive*. The time is also - * placed in the member's GNode. Returns the - * modification time. + * Arch_MTime Find the modification time of a member of + * an archive *in the archive*. The time is also + * placed in the member's GNode. Returns the + * modification time. * - * Arch_MemTime Find the modification time of a member of - * an archive. Called when the member doesn't - * already exist. Looks in the archive for the - * modification time. Returns the modification - * time. + * Arch_MemTime Find the modification time of a member of + * an archive. Called when the member doesn't + * already exist. Looks in the archive for the + * modification time. Returns the modification + * time. * - * Arch_FindLib Search for a library along a path. The - * library name in the GNode should be in - * -l<name> format. + * Arch_FindLib Search for a library along a path. The + * library name in the GNode should be in + * -l<name> format. * - * Arch_LibOODate Special function to decide if a library node - * is out-of-date. + * Arch_LibOODate Special function to decide if a library node + * is out-of-date. * - * Arch_Init Initialize this module. + * Arch_Init Initialize this module. * - * Arch_End Cleanup this module. + * Arch_End Clean up this module. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/param.h> #ifdef HAVE_AR_H -#include <ar.h> +#include <ar.h> #else struct ar_hdr { char ar_name[16]; /* name */ @@ -153,17 +141,17 @@ struct ar_hdr { }; #endif #if defined(HAVE_RANLIB_H) && !(defined(__ELF__) || defined(NO_RANLIB)) -#include <ranlib.h> +#include <ranlib.h> #endif -#include <stdio.h> -#include <stdlib.h> #ifdef HAVE_UTIME_H -#include <utime.h> +#include <utime.h> #endif -#include "make.h" -#include "hash.h" -#include "dir.h" +#include "make.h" +#include "dir.h" + +/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ +MAKE_RCSID("$NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $"); #ifdef TARGET_MACHINE #undef MAKE_MACHINE @@ -174,17 +162,19 @@ struct ar_hdr { #define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH #endif -static Lst archives; /* Lst of archives we've already examined */ +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 */ - Hash_Table members; /* All the members of the archive described - * by <name, struct ar_hdr *> key/value pairs */ - char *fnametab; /* Extended name table strings */ - size_t fnamesize; /* Size of the string table */ + char *name; /* Name of archive */ + HashTable members; /* All the members of the archive described + * by <name, struct ar_hdr *> key/value pairs */ + char *fnametab; /* Extended name table strings */ + size_t fnamesize; /* Size of the string table */ } Arch; -static struct ar_hdr *ArchStatMember(const char *, const char *, Boolean); static FILE *ArchFindMember(const char *, const char *, struct ar_hdr *, const char *); #if defined(__svr4__) || defined(__SVR4) || defined(__ELF__) @@ -219,25 +209,22 @@ static int ArchSVR4Entry(Arch *, char *, size_t, FILE *); # define SARMAG 8 #endif -#define AR_MAX_NAME_LEN (sizeof(arh.AR_NAME)-1) #ifdef CLEANUP static void ArchFree(void *ap) { - Arch *a = (Arch *)ap; - Hash_Search search; - Hash_Entry *entry; + Arch *a = ap; + HashIter hi; /* Free memory from hash entries */ - for (entry = Hash_EnumFirst(&a->members, &search); - entry != NULL; - entry = Hash_EnumNext(&search)) - free(Hash_GetValue(entry)); + HashIter_Init(&hi, &a->members); + while (HashIter_Next(&hi) != NULL) + free(hi.entry->value); free(a->name); free(a->fnametab); - Hash_DeleteTable(&a->members); + HashTable_Done(&a->members); free(a); } #endif @@ -262,46 +249,49 @@ ArchFree(void *ap) *----------------------------------------------------------------------- */ Boolean -Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt) +Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) { - char *cp; /* Pointer into line */ - GNode *gn; /* New node */ - char *libName; /* Library-part of specification */ - char *memName; /* Member-part of specification */ - char saveChar; /* Ending delimiter of member-name */ - Boolean subLibName; /* TRUE if libName should have/had - * variable substitution performed on it */ + char *cp; /* Pointer into line */ + GNode *gn; /* New node */ + char *libName; /* Library-part of specification */ + char *memName; /* Member-part of specification */ + char saveChar; /* Ending delimiter of member-name */ + Boolean subLibName; /* TRUE if libName should have/had + * variable substitution performed on it */ libName = *linePtr; subLibName = FALSE; - for (cp = libName; *cp != '(' && *cp != '\0'; cp++) { + for (cp = libName; *cp != '(' && *cp != '\0';) { if (*cp == '$') { /* * Variable spec, so call the Var module to parse the puppy * so we can safely advance beyond it... */ - int length; - void *result_freeIt; - const char *result; + const char *nested_p = cp; + void *result_freeIt; + const char *result; Boolean isError; - result = Var_Parse(cp, ctxt, VARE_UNDEFERR|VARE_WANTRES, - &length, &result_freeIt); + (void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES, + &result, &result_freeIt); + /* TODO: handle errors */ isError = result == var_Error; free(result_freeIt); if (isError) return FALSE; subLibName = TRUE; - cp += length - 1; - } + cp += nested_p - cp; + } else + cp++; } *cp++ = '\0'; if (subLibName) { - libName = Var_Subst(libName, ctxt, VARE_UNDEFERR|VARE_WANTRES); + (void)Var_Subst(libName, ctxt, VARE_UNDEFERR|VARE_WANTRES, &libName); + /* TODO: handle errors */ } @@ -311,25 +301,25 @@ Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt) * place and skip to the end of it (either white-space or * a close paren). */ - Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */ + Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */ + + pp_skip_whitespace(&cp); - while (*cp != '\0' && *cp != ')' && isspace ((unsigned char)*cp)) { - cp++; - } memName = cp; - while (*cp != '\0' && *cp != ')' && !isspace ((unsigned char)*cp)) { + while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) { if (*cp == '$') { /* * Variable spec, so call the Var module to parse the puppy * so we can safely advance beyond it... */ - int length; - void *freeIt; + void *freeIt; const char *result; Boolean isError; + const char *nested_p = cp; - result = Var_Parse(cp, ctxt, VARE_UNDEFERR|VARE_WANTRES, - &length, &freeIt); + (void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES, + &result, &freeIt); + /* TODO: handle errors */ isError = result == var_Error; free(freeIt); @@ -337,7 +327,7 @@ Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt) return FALSE; doSubst = TRUE; - cp += length; + cp += nested_p - cp; } else { cp++; } @@ -376,11 +366,13 @@ Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt) * later. */ if (doSubst) { - char *buf; - char *sacrifice; - char *oldMemName = memName; + char *buf; + char *sacrifice; + char *oldMemName = memName; - memName = Var_Subst(memName, ctxt, VARE_UNDEFERR | VARE_WANTRES); + (void)Var_Subst(memName, ctxt, VARE_UNDEFERR|VARE_WANTRES, + &memName); + /* TODO: handle errors */ /* * Now form an archive spec and recurse to deal with nested @@ -395,85 +387,48 @@ Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt) * Just create an ARCHV node for the thing and let * SuffExpandChildren handle it... */ - gn = Targ_FindNode(buf, TARG_CREATE); + gn = Targ_GetNode(buf); + gn->type |= OP_ARCHV; + Lst_Append(nodeLst, gn); - if (gn == NULL) { - free(buf); - return FALSE; - } else { - gn->type |= OP_ARCHV; - Lst_Append(nodeLst, gn); - } } else if (!Arch_ParseArchive(&sacrifice, nodeLst, ctxt)) { - /* - * Error in nested call -- free buffer and return FALSE - * ourselves. - */ + /* Error in nested call. */ free(buf); return FALSE; } - /* - * Free buffer and continue with our work. - */ free(buf); - } else if (Dir_HasWildcards(memName)) { - Lst members = Lst_Init(); - Buffer nameBuf; - Buf_Init(&nameBuf, 0); + } else if (Dir_HasWildcards(memName)) { + StringList *members = Lst_New(); Dir_Expand(memName, dirSearchPath, members); + while (!Lst_IsEmpty(members)) { char *member = Lst_Dequeue(members); - - Buf_Empty(&nameBuf); - Buf_AddStr(&nameBuf, libName); - Buf_AddStr(&nameBuf, "("); - Buf_AddStr(&nameBuf, member); - Buf_AddStr(&nameBuf, ")"); + char *fullname = str_concat4(libName, "(", member, ")"); free(member); - gn = Targ_FindNode(Buf_GetAll(&nameBuf, NULL), TARG_CREATE); - if (gn == NULL) { - Buf_Destroy(&nameBuf, TRUE); - return FALSE; - } else { - /* - * We've found the node, but have to make sure the rest of - * the world knows it's an archive member, without having - * to constantly check for parentheses, so we type the - * thing with the OP_ARCHV bit before we place it on the - * end of the provided list. - */ - gn->type |= OP_ARCHV; - Lst_Append(nodeLst, gn); - } - } - Lst_Free(members); - Buf_Destroy(&nameBuf, TRUE); - } else { - Buffer nameBuf; - - Buf_Init(&nameBuf, 0); - Buf_AddStr(&nameBuf, libName); - Buf_AddStr(&nameBuf, "("); - Buf_AddStr(&nameBuf, memName); - Buf_AddStr(&nameBuf, ")"); + gn = Targ_GetNode(fullname); + free(fullname); - gn = Targ_FindNode(Buf_GetAll(&nameBuf, NULL), TARG_CREATE); - Buf_Destroy(&nameBuf, TRUE); - if (gn == NULL) { - return FALSE; - } else { - /* - * We've found the node, but have to make sure the rest of the - * world knows it's an archive member, without having to - * constantly check for parentheses, so we type the thing with - * the OP_ARCHV bit before we place it on the end of the - * provided list. - */ gn->type |= OP_ARCHV; Lst_Append(nodeLst, gn); } + Lst_Free(members); + + } else { + char *fullname = str_concat4(libName, "(", memName, ")"); + gn = Targ_GetNode(fullname); + free(fullname); + + /* + * We've found the node, but have to make sure the rest of the + * world knows it's an archive member, without having to + * constantly check for parentheses, so we type the thing with + * the OP_ARCHV bit before we place it on the end of the + * provided list. + */ + gn->type |= OP_ARCHV; + Lst_Append(nodeLst, gn); } if (doSubst) { free(memName); @@ -489,91 +444,70 @@ Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt) free(libName); } - /* - * We promised the pointer would be set up at the next non-space, so - * we must advance cp there before setting *linePtr... (note that on - * entrance to the loop, cp is guaranteed to point at a ')') - */ - do { - cp++; - } while (*cp != '\0' && isspace ((unsigned char)*cp)); - + cp++; /* skip the ')' */ + /* We promised that linePtr would be set up at the next non-space. */ + pp_skip_whitespace(&cp); *linePtr = cp; return TRUE; } -/* See if the given archive is the one we are looking for. - * Called via Lst_Find. */ -static Boolean -ArchFindArchive(const void *ar, const void *desiredName) -{ - return strcmp(((const Arch *)ar)->name, desiredName) == 0; -} - -/*- - *----------------------------------------------------------------------- - * ArchStatMember -- - * Locate a member of an archive, given the path of the archive and - * the path of the desired member. +/* 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. + * member Name of member; only its basename is used. * hash TRUE if archive should be hashed if not already so. * * Results: - * A pointer to the current struct ar_hdr structure for the member. Note - * That no position is returned, so this is not useful for touching - * archive members. This is mostly because we have no assurances that - * The archive will remain constant after we read all the headers, so - * there's not much point in remembering the position... - *----------------------------------------------------------------------- + * The ar_hdr for the member. */ static struct ar_hdr * ArchStatMember(const char *archive, const char *member, Boolean hash) { - FILE * arch; /* Stream to archive */ - size_t size; /* Size of archive member */ - char magic[SARMAG]; - LstNode ln; /* Lst member containing archive descriptor */ - Arch *ar; /* Archive descriptor */ - Hash_Entry *he; /* Entry containing member's description */ - struct ar_hdr arh; /* archive-member header for reading archive */ - char memName[MAXPATHLEN+1]; - /* Current member name while hashing. */ +#define AR_MAX_NAME_LEN (sizeof(arh.AR_NAME) - 1) + FILE *arch; /* Stream to archive */ + size_t size; /* Size of archive member */ + char magic[SARMAG]; + ArchListNode *ln; + Arch *ar; /* Archive descriptor */ + struct ar_hdr arh; /* archive-member header for reading archive */ + char memName[MAXPATHLEN + 1]; + /* Current member name while hashing. */ /* * Because of space constraints and similar things, files are archived - * using their final path components, not the entire thing, so we need - * to point 'member' to the final component, if there is one, to make - * the comparisons easier... + * using their basename, not the entire path. */ - const char *base = strrchr(member, '/'); - if (base != NULL) { - member = base + 1; + const char *lastSlash = strrchr(member, '/'); + if (lastSlash != NULL) + member = lastSlash + 1; + + for (ln = archives->first; ln != NULL; ln = ln->next) { + const Arch *archPtr = ln->datum; + if (strcmp(archPtr->name, archive) == 0) + break; } - ln = Lst_Find(archives, ArchFindArchive, archive); if (ln != NULL) { - ar = LstNode_Datum(ln); + struct ar_hdr *hdr; - he = Hash_FindEntry(&ar->members, member); + ar = ln->datum; + hdr = HashTable_FindValue(&ar->members, member); + if (hdr != NULL) + return hdr; - if (he != NULL) { - return (struct ar_hdr *)Hash_GetValue(he); - } else { + { /* Try truncated name */ - char copy[AR_MAX_NAME_LEN+1]; + char copy[AR_MAX_NAME_LEN + 1]; size_t len = strlen(member); if (len > AR_MAX_NAME_LEN) { len = AR_MAX_NAME_LEN; snprintf(copy, sizeof copy, "%s", member); } - if ((he = Hash_FindEntry(&ar->members, copy)) != NULL) - return (struct ar_hdr *)Hash_GetValue(he); - return NULL; + hdr = HashTable_FindValue(&ar->members, copy); + return hdr; } } @@ -585,16 +519,14 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) * 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"); + static struct ar_hdr sarh; - if (arch == NULL) { + arch = ArchFindMember(archive, member, &sarh, "r"); + if (arch == NULL) return NULL; - } else { - fclose(arch); - return &sarh; - } + + fclose(arch); + return &sarh; } /* @@ -602,9 +534,8 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) * everything that's in it and cache it so we can get at it quickly. */ arch = fopen(archive, "r"); - if (arch == NULL) { + if (arch == NULL) return NULL; - } /* * We use the ARMAG string to make sure this is an archive we @@ -612,19 +543,19 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) */ if ((fread(magic, SARMAG, 1, arch) != 1) || (strncmp(magic, ARMAG, SARMAG) != 0)) { - fclose(arch); - return NULL; + fclose(arch); + return NULL; } ar = bmake_malloc(sizeof(Arch)); ar->name = bmake_strdup(archive); ar->fnametab = NULL; ar->fnamesize = 0; - Hash_InitTable(&ar->members, -1); + HashTable_Init(&ar->members); memName[AR_MAX_NAME_LEN] = '\0'; while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) { - if (strncmp( arh.AR_FMAG, ARFMAG, sizeof(arh.AR_FMAG)) != 0) { + if (strncmp(arh.AR_FMAG, ARFMAG, sizeof(arh.AR_FMAG)) != 0) { /* * The header is bogus, so the archive is bad * and there's no way we can recover... @@ -639,7 +570,7 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) * 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'; + 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)); @@ -658,15 +589,14 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) * svr4 magic mode; handle it */ switch (ArchSVR4Entry(ar, memName, size, arch)) { - case -1: /* Invalid data */ + case -1: /* Invalid data */ goto badarch; - case 0: /* List of files entry */ + case 0: /* List of files entry */ continue; - default: /* Got the entry */ + default: /* Got the entry */ break; } - } - else { + } else { if (nameend[0] == '/') nameend[0] = '\0'; } @@ -678,26 +608,30 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) * first <namelen> bytes of the file */ if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && - isdigit((unsigned char)memName[sizeof(AR_EFMT1) - 1])) { + ch_isdigit(memName[sizeof(AR_EFMT1) - 1])) { - int elen = atoi(&memName[sizeof(AR_EFMT1)-1]); + int elen = atoi(&memName[sizeof(AR_EFMT1) - 1]); if ((unsigned int)elen > MAXPATHLEN) - goto badarch; + goto badarch; if (fread(memName, (size_t)elen, 1, arch) != 1) - goto badarch; + goto badarch; memName[elen] = '\0'; if (fseek(arch, -elen, SEEK_CUR) != 0) - goto badarch; + goto badarch; if (DEBUG(ARCH) || DEBUG(MAKE)) { - fprintf(debug_file, "ArchStat: Extended format entry for %s\n", memName); + debug_printf("ArchStat: Extended format entry for %s\n", + memName); } } #endif - he = Hash_CreateEntry(&ar->members, memName, NULL); - Hash_SetValue(he, bmake_malloc(sizeof(struct ar_hdr))); - memcpy(Hash_GetValue(he), &arh, sizeof(struct ar_hdr)); + { + HashEntry *he; + he = HashTable_CreateEntry(&ar->members, memName, NULL); + HashEntry_Set(he, bmake_malloc(sizeof(struct ar_hdr))); + memcpy(HashEntry_Get(he), &arh, sizeof(struct ar_hdr)); + } } if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0) goto badarch; @@ -711,17 +645,11 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) * Now that the archive has been read and cached, we can look into * the hash table to find the desired member's header. */ - he = Hash_FindEntry(&ar->members, member); - - if (he != NULL) { - return (struct ar_hdr *)Hash_GetValue(he); - } else { - return NULL; - } + return HashTable_FindValue(&ar->members, member); badarch: fclose(arch); - Hash_DeleteTable(&ar->members); + HashTable_Done(&ar->members); free(ar->fnametab); free(ar); return NULL; @@ -757,9 +685,7 @@ ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) { if (ar->fnametab != NULL) { - if (DEBUG(ARCH)) { - fprintf(debug_file, "Attempted to redefine an SVR4 name table\n"); - } + DEBUG0(ARCH, "Attempted to redefine an SVR4 name table\n"); return -1; } @@ -771,29 +697,17 @@ ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) ar->fnamesize = size; if (fread(ar->fnametab, size, 1, arch) != 1) { - if (DEBUG(ARCH)) { - fprintf(debug_file, "Reading an SVR4 name table failed\n"); - } + DEBUG0(ARCH, "Reading an SVR4 name table failed\n"); return -1; } eptr = ar->fnametab + size; for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++) - switch (*ptr) { - case '/': + if (*ptr == '/') { entry++; *ptr = '\0'; - break; - - case '\n': - break; - - default: - break; } - if (DEBUG(ARCH)) { - fprintf(debug_file, "Found svr4 archive name table with %lu entries\n", - (unsigned long)entry); - } + DEBUG1(ARCH, "Found svr4 archive name table with %lu entries\n", + (unsigned long)entry); return 0; } @@ -802,22 +716,16 @@ ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) entry = (size_t)strtol(&name[1], &eptr, 0); if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) { - if (DEBUG(ARCH)) { - fprintf(debug_file, "Could not parse SVR4 name %s\n", name); - } + DEBUG1(ARCH, "Could not parse SVR4 name %s\n", name); return 2; } if (entry >= ar->fnamesize) { - if (DEBUG(ARCH)) { - fprintf(debug_file, "SVR4 entry offset %s is greater than %lu\n", - name, (unsigned long)ar->fnamesize); - } + DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n", + name, (unsigned long)ar->fnamesize); return 2; } - if (DEBUG(ARCH)) { - fprintf(debug_file, "Replaced %s with %s\n", name, &ar->fnametab[entry]); - } + DEBUG2(ARCH, "Replaced %s with %s\n", name, &ar->fnametab[entry]); snprintf(name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]); return 1; @@ -848,18 +756,17 @@ ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) */ static FILE * ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, - const char *mode) + const char *mode) { - FILE * arch; /* Stream to archive */ - int size; /* Size of archive member */ - char magic[SARMAG]; - size_t len, tlen; - const char * base; + FILE *arch; /* Stream to archive */ + int size; /* Size of archive member */ + char magic[SARMAG]; + size_t len, tlen; + const char *lastSlash; arch = fopen(archive, mode); - if (arch == NULL) { + if (arch == NULL) return NULL; - } /* * We use the ARMAG string to make sure this is an archive we @@ -867,34 +774,35 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, */ if ((fread(magic, SARMAG, 1, arch) != 1) || (strncmp(magic, ARMAG, SARMAG) != 0)) { - fclose(arch); - return NULL; + fclose(arch); + return NULL; } /* * Because of space constraints and similar things, files are archived - * using their final path components, not the entire thing, so we need - * to point 'member' to the final component, if there is one, to make - * the comparisons easier... + * using their basename, not the entire path. */ - base = strrchr(member, '/'); - if (base != NULL) { - member = base + 1; - } + lastSlash = strrchr(member, '/'); + if (lastSlash != NULL) + member = lastSlash + 1; + len = tlen = strlen(member); if (len > sizeof(arhPtr->AR_NAME)) { tlen = sizeof(arhPtr->AR_NAME); } while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) { - if (strncmp(arhPtr->AR_FMAG, ARFMAG, sizeof(arhPtr->AR_FMAG) ) != 0) { - /* - * The header is bogus, so the archive is bad - * and there's no way we can recover... - */ - fclose(arch); - return NULL; - } else if (strncmp(member, arhPtr->AR_NAME, tlen) == 0) { + + if (strncmp(arhPtr->AR_FMAG, ARFMAG, sizeof(arhPtr->AR_FMAG)) != 0) { + /* + * The header is bogus, so the archive is bad + * and there's no way we can recover... + */ + fclose(arch); + return NULL; + } + + if (strncmp(member, arhPtr->AR_NAME, tlen) == 0) { /* * If the member's name doesn't take up the entire 'name' field, * we have to be careful of matching prefixes. Names are space- @@ -902,79 +810,76 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, * of the matched string is anything but a space, this isn't the * member we sought. */ - if (tlen != sizeof(arhPtr->AR_NAME) && arhPtr->AR_NAME[tlen] != ' '){ + if (tlen != sizeof arhPtr->AR_NAME && arhPtr->AR_NAME[tlen] != ' ') goto skip; - } else { - /* - * To make life easier, we reposition the file at the start - * of the header we just read before we return the stream. - * In a more general situation, it might be better to leave - * the file at the actual member, rather than its header, but - * not here... - */ - if (fseek(arch, -(long)sizeof(struct ar_hdr), SEEK_CUR) != 0) { - fclose(arch); - return NULL; - } - return arch; + + /* + * To make life easier, we reposition the file at the start + * of the header we just read before we return the stream. + * In a more general situation, it might be better to leave + * the file at the actual member, rather than its header, but + * not here... + */ + if (fseek(arch, -(long)sizeof(struct ar_hdr), SEEK_CUR) != 0) { + fclose(arch); + return NULL; } - } else -#ifdef AR_EFMT1 - /* - * BSD 4.4 extended AR format: #1/<namelen>, with name as the - * first <namelen> bytes of the file - */ - if (strncmp(arhPtr->AR_NAME, AR_EFMT1, - sizeof(AR_EFMT1) - 1) == 0 && - isdigit((unsigned char)arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1])) { + return arch; + } - int elen = atoi(&arhPtr->AR_NAME[sizeof(AR_EFMT1)-1]); - char ename[MAXPATHLEN + 1]; +#ifdef AR_EFMT1 + /* + * BSD 4.4 extended AR format: #1/<namelen>, with name as the + * first <namelen> bytes of the file + */ + if (strncmp(arhPtr->AR_NAME, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && + ch_isdigit(arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1])) + { + int elen = atoi(&arhPtr->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)) { - fprintf(debug_file, "ArchFind: 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) { + if ((unsigned int)elen > MAXPATHLEN) { + fclose(arch); + return NULL; + } + if (fread(ename, (size_t)elen, 1, arch) != 1) { + fclose(arch); + return NULL; + } + ename[elen] = '\0'; + if (DEBUG(ARCH) || DEBUG(MAKE)) { + debug_printf("ArchFind: Extended format entry for %s\n", ename); + } + if (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; } - goto skip; - } else -#endif - { -skip: - /* - * This isn't the member we're after, so we need to advance the - * stream's pointer to the start of the next header. Files are - * padded with newlines to an even-byte boundary, so we need to - * extract the size of the file from the 'size' field of the - * header and round it up during the seek. - */ - arhPtr->AR_SIZE[sizeof(arhPtr->AR_SIZE)-1] = '\0'; - size = (int)strtol(arhPtr->AR_SIZE, NULL, 10); - if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) { + return arch; + } + if (fseek(arch, -elen, SEEK_CUR) != 0) { fclose(arch); return NULL; } } +#endif + +skip: + /* + * This isn't the member we're after, so we need to advance the + * stream's pointer to the start of the next header. Files are + * padded with newlines to an even-byte boundary, so we need to + * extract the size of the file from the 'size' field of the + * header and round it up during the seek. + */ + arhPtr->ar_size[sizeof(arhPtr->ar_size) - 1] = '\0'; + size = (int)strtol(arhPtr->ar_size, NULL, 10); + if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) { + fclose(arch); + return NULL; + } } /* @@ -1003,18 +908,13 @@ skip: void Arch_Touch(GNode *gn) { - FILE * arch; /* Stream open to archive, positioned properly */ - struct ar_hdr arh; /* Current header describing member */ - char *p1, *p2; + FILE *arch; /* Stream open to archive, positioned properly */ + struct ar_hdr arh; /* Current header describing member */ - arch = ArchFindMember(Var_Value(ARCHIVE, gn, &p1), - Var_Value(MEMBER, gn, &p2), + arch = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+"); - bmake_free(p1); - bmake_free(p2); - - snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long) now); + snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long)now); if (arch != NULL) { (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); @@ -1036,7 +936,7 @@ Arch_TouchLib(GNode *gn) { #ifdef RANLIBMAG FILE * arch; /* Stream open to archive */ - struct ar_hdr arh; /* Header describing table of contents */ + struct ar_hdr arh; /* Header describing table of contents */ struct utimbuf times; /* Times for utime() call */ arch = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); @@ -1063,17 +963,10 @@ Arch_TouchLib(GNode *gn) time_t Arch_MTime(GNode *gn) { - struct ar_hdr *arhPtr; /* Header of desired member */ - time_t modTime; /* Modification time as an integer */ - char *p1, *p2; - - arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1), - Var_Value(MEMBER, gn, &p2), - TRUE); - - bmake_free(p1); - bmake_free(p2); + struct ar_hdr *arhPtr; /* Header of desired member */ + time_t modTime; /* Modification time as an integer */ + arhPtr = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE); if (arhPtr != NULL) { modTime = (time_t)strtol(arhPtr->AR_DATE, NULL, 10); } else { @@ -1089,12 +982,10 @@ Arch_MTime(GNode *gn) time_t Arch_MemMTime(GNode *gn) { - LstNode ln; - GNode *pgn; + GNodeListNode *ln; - Lst_Open(gn->parents); - while ((ln = Lst_Next(gn->parents)) != NULL) { - pgn = LstNode_Datum(ln); + for (ln = gn->parents->first; ln != NULL; ln = ln->next) { + GNode *pgn = ln->datum; if (pgn->type & OP_ARCHV) { /* @@ -1122,8 +1013,6 @@ Arch_MemMTime(GNode *gn) } } - Lst_Close(gn->parents); - return gn->mtime; } @@ -1140,26 +1029,19 @@ Arch_MemMTime(GNode *gn) * * Input: * gn Node of library to find - * path Search path */ void -Arch_FindLib(GNode *gn, Lst path) +Arch_FindLib(GNode *gn, SearchPath *path) { - char *libName; /* file name for archive */ - size_t sz = strlen(gn->name) + 6 - 2; - - libName = bmake_malloc(sz); - snprintf(libName, sz, "lib%s.a", &gn->name[2]); - + char *libName = str_concat3("lib", gn->name + 2, ".a"); gn->path = Dir_FindFile(libName, path); - free(libName); #ifdef LIBRARIES Var_Set(TARGET, gn->name, gn); #else Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn); -#endif /* LIBRARIES */ +#endif } /* Decide if a node with the OP_LIB attribute is out-of-date. Called from @@ -1176,7 +1058,7 @@ Arch_FindLib(GNode *gn, Lst path) * given that it is a target on a dependency line somewhere: * * Its modification time is less than that of one of its sources - * (gn->mtime < gn->cmgn->mtime). + * (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 @@ -1198,20 +1080,21 @@ Arch_FindLib(GNode *gn, Lst path) Boolean Arch_LibOODate(GNode *gn) { - Boolean oodate; + Boolean oodate; if (gn->type & OP_PHONY) { oodate = TRUE; - } else if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) { + } else if (!GNode_IsTarget(gn) && Lst_IsEmpty(gn->children)) { oodate = FALSE; - } else if ((!Lst_IsEmpty(gn->children) && gn->cmgn == NULL) || + } else if ((!Lst_IsEmpty(gn->children) && gn->youngestChild == NULL) || (gn->mtime > now) || - (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime)) { + (gn->youngestChild != NULL && + gn->mtime < gn->youngestChild->mtime)) { oodate = TRUE; } else { #ifdef RANLIBMAG - struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ - int modTimeTOC; /* The table-of-contents's mod time */ + struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ + int modTimeTOC; /* The table-of-contents's mod time */ arhPtr = ArchStatMember(gn->path, RANLIBMAG, FALSE); @@ -1219,15 +1102,15 @@ Arch_LibOODate(GNode *gn) modTimeTOC = (int)strtol(arhPtr->AR_DATE, NULL, 10); if (DEBUG(ARCH) || DEBUG(MAKE)) { - fprintf(debug_file, "%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); + debug_printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); } - oodate = (gn->cmgn == NULL || gn->cmgn->mtime > modTimeTOC); + oodate = (gn->youngestChild == NULL || gn->youngestChild->mtime > modTimeTOC); } else { /* * A library w/o a table of contents is out-of-date */ if (DEBUG(ARCH) || DEBUG(MAKE)) { - fprintf(debug_file, "No t.o.c...."); + debug_printf("No t.o.c...."); } oodate = TRUE; } @@ -1238,14 +1121,14 @@ Arch_LibOODate(GNode *gn) return oodate; } -/* Initialize things for this module. */ +/* Initialize the archives module. */ void Arch_Init(void) { - archives = Lst_Init(); + archives = Lst_New(); } -/* Clean up things for this module. */ +/* Clean up the archives module. */ void Arch_End(void) { @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.289 2020/08/28 17:15:04 rillig Exp $ +.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,7 +29,7 @@ .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd August 28, 2020 +.Dd November 1, 2020 .Dt BMAKE 1 .Os .Sh NAME @@ -1872,7 +1872,7 @@ has been defined and has commands associated with it. .Ar Expression may also be an arithmetic or string comparison. Variable expansion is -performed on both sides of the comparison, after which the integral +performed on both sides of the comparison, after which the numerical values are compared. A value is interpreted as hexadecimal if it is preceded by 0x, otherwise it is decimal; octal numbers are not supported. @@ -1882,7 +1882,7 @@ variable expansion, either the left or right hand side of a .Ql Ic == or .Ql Ic "!=" -operator is not an integral value, then +operator is not a numerical value, then string comparison is performed between the expanded variables. If no relational operator is given, it is assumed that the expanded diff --git a/bmake.cat1 b/bmake.cat1 index 564e811737da..5e78b65c13c5 100644 --- a/bmake.cat1 +++ b/bmake.cat1 @@ -1197,11 +1197,11 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) [4mExpression[24m may also be an arithmetic or string comparison. Variable expansion is performed on both sides of the comparison, after which the - integral values are compared. A value is interpreted as hexadecimal if + numerical values are compared. A value is interpreted as hexadecimal if it is preceded by 0x, otherwise it is decimal; octal numbers are not sup- ported. The standard C relational operators are all supported. If after variable expansion, either the left or right hand side of a `[1m==[22m' or `[1m!=[22m' - operator is not an integral value, then string comparison is performed + operator is not a numerical value, then string comparison is performed between the expanded variables. If no relational operator is given, it is assumed that the expanded variable is being compared against 0, or an empty string in the case of a string comparison. @@ -1568,4 +1568,4 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) There is no way of escaping a space character in a filename. -FreeBSD 11.3 August 28, 2020 FreeBSD 11.3 +FreeBSD 11.3 November 1, 2020 FreeBSD 11.3 diff --git a/boot-strap b/boot-strap index 00c86b6725e4..f3b98f2a7b23 100755 --- a/boot-strap +++ b/boot-strap @@ -115,7 +115,7 @@ # Simon J. Gerraty <sjg@crufty.net> # RCSid: -# $Id: boot-strap,v 1.51 2020/02/19 16:46:23 sjg Exp $ +# $Id: boot-strap,v 1.53 2020/09/16 02:12:01 sjg Exp $ # # @(#) Copyright (c) 2001 Simon J. Gerraty # @@ -458,17 +458,25 @@ op_all() { else op_test MAKE_VERSION=`sed -n '/^_MAKE_VERSION/ { s,.*= *,,;p; }' $srcdir/Makefile` - echo You can install by running: - echo - echo $0 $cmd_args op=install - echo - echo "Use --install-prefix=/something to install somewhere other than $prefix" - echo "Use --install-destdir=/somewhere to set DESTDIR during install" - echo "Use --install-host-target to use INSTALL_BIN=$HOST_TARGET/bin" - echo "Use -DWITH_PROG_VERSION to install as bmake-$MAKE_VERSION" - echo "Use -DWITHOUT_PROG_LINK to suppress bmake -> bmake-$MAKE_VERSION symlink" - echo "Use -DWITHOUT_INSTALL_MK to skip installing files to $prefix/share/mk" + cat << EOM +You can install by running: + +$0 $cmd_args op=install + +Use --install-prefix=/something to install somewhere other than $prefix +Use --install-destdir=/somewhere to set DESTDIR during install +Use --install-host-target to use INSTALL_BIN=$HOST_TARGET/bin +Use -DWITH_PROG_VERSION to install as bmake-$MAKE_VERSION +Use -DWITHOUT_PROG_LINK to suppress bmake -> bmake-$MAKE_VERSION symlink +Use -DWITHOUT_INSTALL_MK to skip installing files to $prefix/share/mk +EOM fi + cat << EOM + +Note: bmake.cat1 contains ANSI escape sequences. +You may need the -r or -R option to more/less to view it correctly. + +EOM } op_$op @@ -1,4 +1,4 @@ -/* $NetBSD: buf.c,v 1.37 2020/08/23 08:21:50 rillig Exp $ */ +/* $NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,115 +69,103 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: buf.c,v 1.37 2020/08/23 08:21:50 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)buf.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: buf.c,v 1.37 2020/08/23 08:21:50 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - -/* Functions for automatically-expanded null-terminated buffers. */ +/* Automatically-expanding null-terminated buffers. */ #include <limits.h> #include "make.h" -/* Extend the buffer for adding a single byte. */ +/* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */ +MAKE_RCSID("$NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $"); + +/* Make space in the buffer for adding a single byte. */ void -Buf_Expand_1(Buffer *bp) +Buf_Expand_1(Buffer *buf) { - bp->size += MAX(bp->size, 16); - bp->buffer = bmake_realloc(bp->buffer, bp->size); + buf->cap += buf->cap > 16 ? buf->cap : 16; + buf->data = bmake_realloc(buf->data, buf->cap); } -/* Add the given bytes to the buffer. */ +/* Add the bytes to the buffer. */ void -Buf_AddBytes(Buffer *bp, const char *bytesPtr, size_t numBytes) +Buf_AddBytes(Buffer *buf, const char *bytes, size_t bytes_len) { - size_t count = bp->count; - char *ptr; + size_t old_len = buf->len; + char *end; - if (__predict_false(count + numBytes >= bp->size)) { - bp->size += MAX(bp->size, numBytes + 16); - bp->buffer = bmake_realloc(bp->buffer, bp->size); + if (__predict_false(old_len + bytes_len >= buf->cap)) { + buf->cap += buf->cap > bytes_len + 16 ? buf->cap : bytes_len + 16; + buf->data = bmake_realloc(buf->data, buf->cap); } - ptr = bp->buffer + count; - bp->count = count + numBytes; - memcpy(ptr, bytesPtr, numBytes); - ptr[numBytes] = '\0'; + end = buf->data + old_len; + buf->len = old_len + bytes_len; + memcpy(end, bytes, bytes_len); + end[bytes_len] = '\0'; } /* Add the bytes between start and end to the buffer. */ void -Buf_AddBytesBetween(Buffer *bp, const char *start, const char *end) +Buf_AddBytesBetween(Buffer *buf, const char *start, const char *end) { - Buf_AddBytes(bp, start, (size_t)(end - start)); + Buf_AddBytes(buf, start, (size_t)(end - start)); } -/* Add the given string to the buffer. */ +/* Add the string to the buffer. */ void -Buf_AddStr(Buffer *bp, const char *str) +Buf_AddStr(Buffer *buf, const char *str) { - Buf_AddBytes(bp, str, strlen(str)); + Buf_AddBytes(buf, str, strlen(str)); } -/* Add the given number to the buffer. */ +/* Add the number to the buffer. */ void -Buf_AddInt(Buffer *bp, int n) +Buf_AddInt(Buffer *buf, int n) { enum { bits = sizeof(int) * CHAR_BIT, max_octal_digits = (bits + 2) / 3, max_decimal_digits = /* at most */ max_octal_digits, max_sign_chars = 1, - buf_size = max_sign_chars + max_decimal_digits + 1 + str_size = max_sign_chars + max_decimal_digits + 1 }; - char buf[buf_size]; + char str[str_size]; - size_t len = (size_t)snprintf(buf, sizeof buf, "%d", n); - Buf_AddBytes(bp, buf, len); + size_t len = (size_t)snprintf(str, sizeof str, "%d", n); + Buf_AddBytes(buf, str, len); } /* Get the data (usually a string) from the buffer. * The returned data is valid until the next modifying operation * on the buffer. * - * Returns the pointer to the data and optionally the length of the - * data in the buffer. */ + * Returns the data and optionally the length of the data. */ char * -Buf_GetAll(Buffer *bp, size_t *numBytesPtr) +Buf_GetAll(Buffer *buf, size_t *out_len) { - if (numBytesPtr != NULL) - *numBytesPtr = bp->count; - return bp->buffer; + if (out_len != NULL) + *out_len = buf->len; + return buf->data; } /* Mark the buffer as empty, so it can be filled with data again. */ void -Buf_Empty(Buffer *bp) +Buf_Empty(Buffer *buf) { - bp->count = 0; - bp->buffer[0] = '\0'; + buf->len = 0; + buf->data[0] = '\0'; } /* Initialize a buffer. - * If the given initial size is 0, a reasonable default is used. */ + * If the given initial capacity is 0, a reasonable default is used. */ void -Buf_Init(Buffer *bp, size_t size) +Buf_Init(Buffer *buf, size_t cap) { - if (size <= 0) { - size = 256; - } - bp->size = size; - bp->count = 0; - bp->buffer = bmake_malloc(size); - bp->buffer[0] = '\0'; + if (cap <= 0) + cap = 256; + buf->cap = cap; + buf->len = 0; + buf->data = bmake_malloc(cap); + buf->data[0] = '\0'; } /* Reset the buffer. @@ -186,15 +174,15 @@ Buf_Init(Buffer *bp, size_t size) char * Buf_Destroy(Buffer *buf, Boolean freeData) { - char *data = buf->buffer; + char *data = buf->data; if (freeData) { free(data); data = NULL; } - buf->size = 0; - buf->count = 0; - buf->buffer = NULL; + buf->cap = 0; + buf->len = 0; + buf->data = NULL; return data; } @@ -211,10 +199,10 @@ char * Buf_DestroyCompact(Buffer *buf) { #if BUF_COMPACT_LIMIT > 0 - if (buf->size - buf->count >= BUF_COMPACT_LIMIT) { + if (buf->cap - buf->len >= BUF_COMPACT_LIMIT) { /* We trust realloc to be smart */ - char *data = bmake_realloc(buf->buffer, buf->count + 1); - data[buf->count] = '\0'; + char *data = bmake_realloc(buf->data, buf->len + 1); + data[buf->len] = '\0'; /* XXX: unnecessary */ Buf_Destroy(buf, FALSE); return data; } @@ -1,4 +1,4 @@ -/* $NetBSD: buf.h,v 1.28 2020/09/01 17:38:26 rillig Exp $ */ +/* $NetBSD: buf.h,v 1.34 2020/09/27 16:59:02 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -81,9 +81,9 @@ /* An automatically growing null-terminated buffer of characters. */ typedef struct Buffer { - size_t size; /* Allocated size of the buffer, including the null */ - size_t count; /* Number of bytes in buffer, excluding the null */ - char *buffer; /* The buffer itself (always null-terminated) */ + size_t cap; /* Allocated size of the buffer, including the null */ + size_t len; /* Number of bytes in buffer, excluding the null */ + char *data; /* The buffer itself (always null-terminated) */ } Buffer; /* If we aren't on NetBSD, __predict_false() might not be defined. */ @@ -94,22 +94,28 @@ typedef struct Buffer { void Buf_Expand_1(Buffer *); /* Buf_AddByte adds a single byte to a buffer. */ -static inline void MAKE_ATTR_UNUSED -Buf_AddByte(Buffer *bp, char byte) +static inline MAKE_ATTR_UNUSED void +Buf_AddByte(Buffer *buf, char byte) { - size_t count = ++bp->count; - char *ptr; - if (__predict_false(count >= bp->size)) - Buf_Expand_1(bp); - ptr = bp->buffer + count; - ptr[-1] = byte; - ptr[0] = 0; + size_t old_len = buf->len++; + char *end; + if (__predict_false(old_len + 1 >= buf->cap)) + Buf_Expand_1(buf); + end = buf->data + old_len; + end[0] = byte; + end[1] = '\0'; } -static inline size_t MAKE_ATTR_UNUSED -Buf_Size(const Buffer *bp) +static inline MAKE_ATTR_UNUSED size_t +Buf_Len(const Buffer *buf) { - return bp->count; + return buf->len; +} + +static inline MAKE_ATTR_UNUSED Boolean +Buf_EndsWith(const Buffer *buf, char ch) +{ + return buf->len > 0 && buf->data[buf->len - 1] == ch; } void Buf_AddBytes(Buffer *, const char *, size_t); @@ -1,4 +1,4 @@ -/* $NetBSD: compat.c,v 1.139 2020/08/30 20:08:47 rillig Exp $ */ +/* $NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,19 +69,6 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: compat.c,v 1.139 2020/08/30 20:08:47 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)compat.c 8.2 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: compat.c,v 1.139 2020/08/30 20:08:47 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - /*- * compat.c -- * The routines in this file implement the full-compatibility @@ -91,33 +78,30 @@ __RCSID("$NetBSD: compat.c,v 1.139 2020/08/30 20:08:47 rillig Exp $"); * - friendly variable substitution. * * Interface: - * Compat_Run Initialize things for this module and recreate - * thems as need creatin' + * Compat_Run Initialize things for this module and recreate + * thems as need creatin' */ #ifdef HAVE_CONFIG_H # include "config.h" #endif -#include <sys/types.h> -#include <sys/stat.h> -#include "wait.h" - -#include <ctype.h> -#include <errno.h> -#include <signal.h> -#include <stdio.h> - -#include "make.h" -#include "hash.h" -#include "dir.h" -#include "job.h" -#include "metachar.h" -#include "pathnames.h" - - -static GNode *curTarg = NULL; -static GNode *ENDNode; -static void CompatInterrupt(int); +#include <sys/types.h> +#include <sys/stat.h> +#include "wait.h" + +#include <errno.h> +#include <signal.h> + +#include "make.h" +#include "dir.h" +#include "job.h" +#include "metachar.h" +#include "pathnames.h" + +/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $"); + +static GNode *curTarg = NULL; static pid_t compatChild; static int compatSigno; @@ -128,15 +112,12 @@ static int compatSigno; static void CompatDeleteTarget(GNode *gn) { - if ((gn != NULL) && !Targ_Precious (gn)) { - char *p1; - const char *file = Var_Value(TARGET, gn, &p1); + if (gn != NULL && !Targ_Precious(gn)) { + const char *file = GNode_VarTarget(gn); - if (!noExecute && eunlink(file) != -1) { + if (!opts.noExecute && eunlink(file) != -1) { Error("*** %s removed", file); } - - bmake_free(p1); } } @@ -155,22 +136,24 @@ CompatInterrupt(int signo) CompatDeleteTarget(curTarg); - if ((curTarg != NULL) && !Targ_Precious (curTarg)) { + if (curTarg != NULL && !Targ_Precious(curTarg)) { /* * Run .INTERRUPT only if hit with interrupt signal */ if (signo == SIGINT) { - gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); + 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. + * If there is a child running, pass the signal on. + * We will exist after it has exited. */ compatSigno = signo; if (compatChild > 0) { @@ -181,11 +164,8 @@ CompatInterrupt(int signo) } } -/*- - *----------------------------------------------------------------------- - * CompatRunCommand -- - * Execute the next command for a target. If the command returns an - * error, the node's made field is set to ERROR and creation stops. +/* 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 @@ -193,45 +173,38 @@ CompatInterrupt(int signo) * * Results: * 0 if the command succeeded, 1 if an error occurred. - * - * Side Effects: - * The node's 'made' field may be set to ERROR. - * - *----------------------------------------------------------------------- */ int -CompatRunCommand(void *cmdp, void *gnp) +Compat_RunCommand(const char *cmdp, GNode *gn) { - char *cmdStart; /* Start of expanded command */ - char *cp, *bp; - Boolean silent, /* Don't print command */ - doIt; /* Execute even if -n */ - volatile Boolean errCheck; /* Check errors */ - WAIT_T reason; /* Reason for child's death */ - int status; /* Description of child's death */ - pid_t cpid; /* Child actually found */ - pid_t retstat; /* Result of wait */ - LstNode cmdNode; /* Node where current command is located */ - const char ** volatile av; /* Argument vector for thing to exec */ - char ** volatile mav;/* Copy of the argument vector for freeing */ - Boolean useShell; /* TRUE if command should be executed + char *cmdStart; /* Start of expanded command */ + char *bp; + Boolean silent; /* Don't print command */ + Boolean doIt; /* Execute even if -n */ + volatile Boolean errCheck; /* Check errors */ + WAIT_T reason; /* Reason for child's death */ + int status; /* Description of child's death */ + pid_t cpid; /* Child actually found */ + pid_t retstat; /* Result of wait */ + StringListNode *cmdNode; /* Node where current command is located */ + const char **volatile av; /* Argument vector for thing to exec */ + char **volatile mav; /* Copy of the argument vector for freeing */ + Boolean useShell; /* TRUE if command should be executed * using a shell */ - char * volatile cmd = (char *)cmdp; - GNode *gn = (GNode *)gnp; + const char *volatile cmd = cmdp; silent = (gn->type & OP_SILENT) != 0; errCheck = !(gn->type & OP_IGNORE); doIt = FALSE; + /* Luckily the commands don't end up in a string pool, otherwise + * this comparison could match too early, in a dependency using "..." + * for delayed commands, run in parallel mode, using the same shell + * command line more than once; see JobPrintCommand. + * TODO: write a unit-test to protect against this potential bug. */ cmdNode = Lst_FindDatum(gn->commands, cmd); - cmdStart = Var_Subst(cmd, gn, VARE_WANTRES); - - /* - * brk_string will return an argv with a NULL in av[0], thus causing - * execvp to choke and die horribly. Besides, how can we execute a null - * command? In any case, we warn the user that the command expanded to - * nothing (is this the right thing to do?). - */ + (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart); + /* TODO: handle errors */ if (*cmdStart == '\0') { free(cmdStart); @@ -240,17 +213,19 @@ CompatRunCommand(void *cmdp, void *gnp) cmd = cmdStart; LstNode_Set(cmdNode, cmdStart); - if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) { - assert(ENDNode != NULL); - Lst_Append(ENDNode->commands, cmdStart); - return 0; + if (gn->type & OP_SAVE_CMDS) { + GNode *endNode = Targ_GetEndNode(); + if (gn != endNode) { + Lst_Append(endNode->commands, cmdStart); + return 0; + } } if (strcmp(cmdStart, "...") == 0) { gn->type |= OP_SAVE_CMDS; return 0; } - while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) { + while (*cmd == '@' || *cmd == '-' || *cmd == '+') { switch (*cmd) { case '@': silent = !DEBUG(LOUD); @@ -267,7 +242,7 @@ CompatRunCommand(void *cmdp, void *gnp) cmd++; } - while (isspace((unsigned char)*cmd)) + while (ch_isspace(*cmd)) cmd++; /* @@ -295,14 +270,14 @@ CompatRunCommand(void *cmdp, void *gnp) * meta characters as documented in make(1). */ - useShell = needshell(cmd, FALSE); + useShell = needshell(cmd); #endif /* * Print the command before echoing if we're not supposed to be quiet for * this one. We also print the command if -n given. */ - if (!silent || NoExecute(gn)) { + if (!silent || !GNode_ShouldExecute(gn)) { printf("%s\n", cmd); fflush(stdout); } @@ -311,11 +286,10 @@ CompatRunCommand(void *cmdp, void *gnp) * If we're not supposed to execute any commands, this is as far as * we go... */ - if (!doIt && NoExecute(gn)) { + if (!doIt && !GNode_ShouldExecute(gn)) { return 0; } - if (DEBUG(JOB)) - fprintf(debug_file, "Execute: '%s'\n", cmd); + DEBUG1(JOB, "Execute: '%s'\n", cmd); if (useShell) { /* @@ -374,8 +348,7 @@ CompatRunCommand(void *cmdp, void *gnp) } #endif (void)execvp(av[0], (char *const *)UNCONST(av)); - execError("exec", av[0]); - _exit(1); + execDie("exec", av[0]); } free(mav); @@ -394,85 +367,77 @@ CompatRunCommand(void *cmdp, void *gnp) /* * The child is off and running. Now all we can do is wait... */ - while (1) { - - while ((retstat = wait(&reason)) != cpid) { - if (retstat > 0) - JobReapChild(retstat, reason, FALSE); /* not ours? */ - if (retstat == -1 && errno != EINTR) { - break; - } + while ((retstat = wait(&reason)) != cpid) { + if (retstat > 0) + JobReapChild(retstat, reason, FALSE); /* not ours? */ + if (retstat == -1 && errno != EINTR) { + break; } + } - if (retstat > -1) { - if (WIFSTOPPED(reason)) { - status = WSTOPSIG(reason); /* stopped */ - } else if (WIFEXITED(reason)) { - status = WEXITSTATUS(reason); /* exited */ + if (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); - } + if (useMeta) { + meta_cmd_finish(NULL); + } #endif - if (status != 0) { - if (DEBUG(ERROR)) { - fprintf(debug_file, "\n*** Failed target: %s\n*** Failed command: ", - gn->name); - for (cp = cmd; *cp; ) { - if (isspace((unsigned char)*cp)) { - fprintf(debug_file, " "); - while (isspace((unsigned char)*cp)) - cp++; - } else { - fprintf(debug_file, "%c", *cp); - cp++; - } - } - fprintf(debug_file, "\n"); + if (status != 0) { + if (DEBUG(ERROR)) { + const char *cp; + debug_printf("\n*** Failed target: %s\n*** Failed command: ", + gn->name); + for (cp = cmd; *cp; ) { + if (ch_isspace(*cp)) { + debug_printf(" "); + while (ch_isspace(*cp)) + cp++; + } else { + debug_printf("%c", *cp); + cp++; } - printf("*** Error code %d", status); } - } else { - status = WTERMSIG(reason); /* signaled */ - printf("*** Signal %d", status); + debug_printf("\n"); } + printf("*** Error code %d", status); + } + } else { + status = WTERMSIG(reason); /* signaled */ + printf("*** Signal %d", status); + } - if (!WIFEXITED(reason) || (status != 0)) { - if (errCheck) { + if (!WIFEXITED(reason) || status != 0) { + if (errCheck) { #ifdef USE_META - if (useMeta) { - meta_job_error(NULL, gn, 0, status); - } + if (useMeta) { + meta_job_error(NULL, gn, 0, status); + } #endif - gn->made = ERROR; - if (keepgoing) { - /* - * Abort the current target, but let others - * continue. - */ - printf(" (continuing)\n"); - } else { - 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; - } + gn->made = ERROR; + if (opts.keepgoing) { + /* Abort the current target, but let others continue. */ + printf(" (continuing)\n"); + } else { + printf("\n"); } - break; + if (deleteOnError) + CompatDeleteTarget(gn); } else { - Fatal("error in wait: %d: %s", retstat, strerror(errno)); - /*NOTREACHED*/ + /* + * Continue executing commands for this target. + * If we return 0, this will happen... + */ + printf(" (ignored)\n"); + status = 0; } } + free(cmdStart); compatChild = 0; if (compatSigno) { @@ -483,32 +448,41 @@ CompatRunCommand(void *cmdp, void *gnp) return status; } -/*- - *----------------------------------------------------------------------- - * Compat_Make -- - * Make a target. - * - * Input: - * gnp The node to make - * pgnp Parent to abort if necessary - * - * Results: - * 0 +static void +RunCommands(GNode *gn) +{ + StringListNode *ln; + for (ln = gn->commands->first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; + if (Compat_RunCommand(cmd, gn) != 0) + break; + } +} + +static void +MakeNodes(GNodeList *gnodes, GNode *pgn) +{ + GNodeListNode *ln; + for (ln = gnodes->first; ln != NULL; ln = ln->next) { + GNode *cohort = ln->datum; + Compat_Make(cohort, pgn); + } +} + +/* Make a target. * - * Side Effects: - * If an error is detected and not being ignored, the process exits. + * If an error is detected and not being ignored, the process exits. * - *----------------------------------------------------------------------- + * Input: + * gn The node to make + * pgn Parent to abort if necessary */ -int -Compat_Make(void *gnp, void *pgnp) +void +Compat_Make(GNode *gn, GNode *pgn) { - GNode *gn = (GNode *)gnp; - GNode *pgn = (GNode *)pgnp; - if (!shellName) /* we came here from jobs */ Shell_Init(); - if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) { + if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) { /* * First mark ourselves to be made, then apply whatever transformations * the suffix module thinks are necessary. Once that's done, we can @@ -519,45 +493,37 @@ Compat_Make(void *gnp, void *pgnp) */ gn->flags |= REMAKE; gn->made = BEINGMADE; - if ((gn->type & OP_MADE) == 0) + if (!(gn->type & OP_MADE)) Suff_FindDeps(gn); - Lst_ForEach(gn->children, Compat_Make, gn); - if ((gn->flags & REMAKE) == 0) { + MakeNodes(gn->children, gn); + if (!(gn->flags & REMAKE)) { gn->made = ABORTED; pgn->flags &= ~(unsigned)REMAKE; goto cohorts; } - if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) { - char *p1; - Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn); - bmake_free(p1); - } + if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) + Var_Set(IMPSRC, GNode_VarTarget(gn), pgn); /* - * All the children were made ok. Now cmgn->mtime contains the + * All the children were made ok. Now youngestChild->mtime contains the * modification time of the newest child, we need to find out if we * exist and when we were modified last. The criteria for datedness * are defined by the Make_OODate function. */ - if (DEBUG(MAKE)) { - fprintf(debug_file, "Examining %s...", gn->name); - } - if (! Make_OODate(gn)) { + DEBUG1(MAKE, "Examining %s...", gn->name); + if (!Make_OODate(gn)) { gn->made = UPTODATE; - if (DEBUG(MAKE)) { - fprintf(debug_file, "up-to-date.\n"); - } + DEBUG0(MAKE, "up-to-date.\n"); goto cohorts; - } else if (DEBUG(MAKE)) { - fprintf(debug_file, "out-of-date.\n"); - } + } else + DEBUG0(MAKE, "out-of-date.\n"); /* * If the user is just seeing if something is out-of-date, exit now * to tell him/her "yes". */ - if (queryFlag) { + if (opts.queryFlag) { exit(1); } @@ -572,26 +538,24 @@ Compat_Make(void *gnp, void *pgnp) * Alter our type to tell if errors should be ignored or things * should not be printed so CompatRunCommand knows what to do. */ - if (Targ_Ignore(gn)) { + if (Targ_Ignore(gn)) gn->type |= OP_IGNORE; - } - if (Targ_Silent(gn)) { + if (Targ_Silent(gn)) gn->type |= OP_SILENT; - } if (Job_CheckCommands(gn, Fatal)) { /* * Our commands are ok, but we still have to worry about the -t * flag... */ - if (!touchFlag || (gn->type & OP_MAKE)) { + if (!opts.touchFlag || (gn->type & OP_MAKE)) { curTarg = gn; #ifdef USE_META - if (useMeta && !NoExecute(gn)) { + if (useMeta && GNode_ShouldExecute(gn)) { meta_job_start(NULL, gn); } #endif - Lst_ForEach(gn->commands, CompatRunCommand, gn); + RunCommands(gn); curTarg = NULL; } else { Job_Touch(gn, (gn->type & OP_SILENT) != 0); @@ -600,7 +564,7 @@ Compat_Make(void *gnp, void *pgnp) gn->made = ERROR; } #ifdef USE_META - if (useMeta && !NoExecute(gn)) { + if (useMeta && GNode_ShouldExecute(gn)) { if (meta_job_finish(NULL) != 0) gn->made = ERROR; } @@ -619,24 +583,19 @@ Compat_Make(void *gnp, void *pgnp) pgn->flags |= CHILDMADE; Make_TimeStamp(pgn, gn); } - } else if (keepgoing) { + } else if (opts.keepgoing) { pgn->flags &= ~(unsigned)REMAKE; } else { PrintOnError(gn, "\nStop."); exit(1); } } else if (gn->made == ERROR) { - /* - * Already had an error when making this beastie. Tell the parent - * to abort. - */ + /* Already had an error when making this. Tell the parent to abort. */ pgn->flags &= ~(unsigned)REMAKE; } else { if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) { - char *p1; - const char *target = Var_Value(TARGET, gn, &p1); + const char *target = GNode_VarTarget(gn); Var_Set(IMPSRC, target != NULL ? target : "", pgn); - bmake_free(p1); } switch(gn->made) { case BEINGMADE: @@ -661,8 +620,7 @@ Compat_Make(void *gnp, void *pgnp) } cohorts: - Lst_ForEach(gn->cohorts, Compat_Make, pgnp); - return 0; + MakeNodes(gn->cohorts, pgn); } /* Initialize this module and start making. @@ -671,35 +629,33 @@ cohorts: * targs The target nodes to re-create */ void -Compat_Run(Lst targs) +Compat_Run(GNodeList *targs) { - GNode *gn = NULL;/* Current root target */ - int errors; /* Number of targets not remade due to errors */ + GNode *gn = NULL; /* Current root target */ + int errors; /* Number of targets not remade due to errors */ if (!shellName) Shell_Init(); - if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) { + if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) bmake_signal(SIGINT, CompatInterrupt); - } - if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) { + if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) bmake_signal(SIGTERM, CompatInterrupt); - } - if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) { + if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) bmake_signal(SIGHUP, CompatInterrupt); - } - if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) { + if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) bmake_signal(SIGQUIT, CompatInterrupt); - } - ENDNode = Targ_FindNode(".END", TARG_CREATE); - ENDNode->type = OP_SPECIAL; + /* Create the .END node now, to keep the (debug) output of the + * counter.mk test the same as before 2020-09-23. This implementation + * detail probably doesn't matter though. */ + (void)Targ_GetEndNode(); /* * If the user has defined a .BEGIN target, execute the commands attached * to it. */ - if (!queryFlag) { - gn = Targ_FindNode(".BEGIN", TARG_NOCREATE); + if (!opts.queryFlag) { + gn = Targ_FindNode(".BEGIN"); if (gn != NULL) { Compat_Make(gn, gn); if (gn->made == ERROR) { @@ -719,11 +675,11 @@ Compat_Run(Lst targs) * For each entry in the list of targets to create, call Compat_Make on * it to create the thing. Compat_Make will leave the 'made' field of gn * in one of several states: - * UPTODATE gn was already up-to-date - * MADE gn was recreated successfully - * ERROR An error occurred while gn was being created - * ABORTED gn was not remade because one of its inferiors - * could not be made due to errors. + * UPTODATE gn was already up-to-date + * MADE gn was recreated successfully + * ERROR An error occurred while gn was being created + * ABORTED gn was not remade because one of its inferiors + * could not be made due to errors. */ errors = 0; while (!Lst_IsEmpty(targs)) { @@ -734,7 +690,7 @@ Compat_Run(Lst targs) printf("`%s' is up to date.\n", gn->name); } else if (gn->made == ABORTED) { printf("`%s' not remade because of errors.\n", gn->name); - errors += 1; + errors++; } } @@ -742,7 +698,9 @@ Compat_Run(Lst targs) * If the user has defined a .END target, run its commands. */ if (errors == 0) { - Compat_Make(ENDNode, ENDNode); + GNode *endNode = Targ_GetEndNode(); + Compat_Make(endNode, endNode); + /* XXX: Did you mean endNode->made instead of gn->made? */ if (gn->made == ERROR) { PrintOnError(gn, "\nStop."); exit(1); @@ -1,4 +1,4 @@ -/* $NetBSD: cond.c,v 1.106 2020/08/29 13:38:48 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,26 +69,22 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: cond.c,v 1.106 2020/08/29 13:38:48 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)cond.c 8.2 (Berkeley) 1/2/94"; -#else -__RCSID("$NetBSD: cond.c,v 1.106 2020/08/29 13:38:48 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * cond.c -- - * Functions to handle conditionals in a makefile. +/* Handling of conditionals in a makefile. * * Interface: - * Cond_Eval Evaluate the conditional in the passed line. + * Cond_EvalLine Evaluate the conditional. + * + * 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 <errno.h> @@ -96,6 +92,9 @@ __RCSID("$NetBSD: cond.c,v 1.106 2020/08/29 13:38:48 rillig Exp $"); #include "make.h" #include "dir.h" +/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ +MAKE_RCSID("$NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $"); + /* * The parsing of conditional expressions is based on this grammar: * E -> F || E @@ -119,185 +118,198 @@ __RCSID("$NetBSD: cond.c,v 1.106 2020/08/29 13:38:48 rillig Exp $"); * * 'symbol' is some other symbol to which the default function is applied. * - * Tokens are scanned from the 'condExpr' string. The scanner (CondToken) - * will return TOK_AND for '&' and '&&', TOK_OR for '|' and '||', - * TOK_NOT for '!', TOK_LPAREN for '(', TOK_RPAREN for ')' and will evaluate - * the other terminal symbols, using either the default function or the - * function given in the terminal, and return the result as either TOK_TRUE - * or TOK_FALSE. + * The tokens are scanned by CondToken, which returns: + * TOK_AND for '&' or '&&' + * TOK_OR for '|' or '||' + * TOK_NOT for '!' + * TOK_LPAREN for '(' + * TOK_RPAREN for ')' + * Other terminal symbols are evaluated using either the default function or + * the function given in the terminal, they return either TOK_TRUE or + * TOK_FALSE. * * TOK_FALSE is 0 and TOK_TRUE 1 so we can directly assign C comparisons. * - * All Non-Terminal functions (CondE, CondF and CondT) return TOK_ERROR on - * error. + * All non-terminal functions (CondParser_Expr, CondParser_Factor and + * CondParser_Term) return either TOK_FALSE, TOK_TRUE, or TOK_ERROR on error. */ -typedef enum { +typedef enum Token { TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT, TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR } Token; -static Token CondE(Boolean); -static CondEvalResult do_Cond_EvalExpression(Boolean *); +typedef struct CondParser { + const struct If *if_info; /* Info for current statement */ + const char *p; /* The remaining condition to parse */ + Token curr; /* Single push-back token used in parsing */ + + /* Whether an error message has already been printed for this condition. + * The first available error message is usually the most specific one, + * therefore it makes sense to suppress the standard "Malformed + * conditional" message. */ + Boolean printedError; +} CondParser; -static const struct If *if_info; /* Info for current statement */ -static const char *condExpr; /* The expression to parse */ -static Token condPushBack = TOK_NONE; /* Single push-back token used in - * parsing */ +static Token CondParser_Expr(CondParser *par, Boolean); static unsigned int cond_depth = 0; /* current .if nesting level */ static unsigned int cond_min_depth = 0; /* depth at makefile open */ /* * Indicate when we should be strict about lhs of comparisons. - * TRUE when Cond_EvalExpression is called from Cond_Eval (.if etc) - * FALSE when Cond_EvalExpression is called from var.c:ApplyModifiers + * In strict mode, the lhs must be a variable expression or a string literal + * in quotes. In non-strict mode it may also be an unquoted string literal. + * + * TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) + * FALSE when CondEvalExpression is called from ApplyModifier_IfElse * since lhs is already expanded and we cannot tell if * it was a variable reference or not. */ static Boolean lhsStrict; static int -istoken(const char *str, const char *tok, size_t len) +is_token(const char *str, const char *tok, size_t len) { - return strncmp(str, tok, len) == 0 && !isalpha((unsigned char)str[len]); + return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); } -/* Push back the most recent token read. We only need one level of - * this, so the thing is just stored in 'condPushback'. */ +/* Push back the most recent token read. We only need one level of this. */ static void -CondPushBack(Token t) +CondParser_PushBack(CondParser *par, Token t) { - condPushBack = t; + assert(par->curr == TOK_NONE); + assert(t != TOK_NONE); + + par->curr = t; } -/*- - * Parse the argument of a built-in function. - * - * Results: - * The length of the argument. - * *argPtr receives the argument as string. - * *linePtr is updated to point behind the ')' of the function call. - */ -static int -CondGetArg(Boolean doEval, const char **linePtr, char **argPtr, - const char *func) +static void +CondParser_SkipWhitespace(CondParser *par) { - const char *cp; - Buffer buf; + 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. */ +static size_t +ParseFuncArg(const char **pp, Boolean doEval, const char *func, + char **out_arg) { + const char *p = *pp; + Buffer argBuf; int paren_depth; - char ch; size_t argLen; - cp = *linePtr; if (func != NULL) - /* Skip opening '(' - verified by caller */ - cp++; + p++; /* Skip opening '(' - verified by caller */ - if (*cp == '\0') { + if (*p == '\0') { /* * No arguments whatsoever. Because 'make' and 'defined' aren't really * "reserved words", we don't print a message. I think this is better * than hitting the user with a warning message every time s/he uses * the word 'make' or 'defined' at the beginning of a symbol... */ - *argPtr = NULL; + *out_arg = NULL; return 0; } - while (*cp == ' ' || *cp == '\t') { - cp++; + while (*p == ' ' || *p == '\t') { + p++; } - /* - * Create a buffer for the argument and start it out at 16 characters - * long. Why 16? Why not? - */ - Buf_Init(&buf, 16); + Buf_Init(&argBuf, 16); paren_depth = 0; for (;;) { - ch = *cp; + char ch = *p; if (ch == 0 || ch == ' ' || ch == '\t') break; if ((ch == '&' || ch == '|') && paren_depth == 0) break; - if (*cp == '$') { + if (*p == '$') { /* * Parse the variable spec and install it as part of the argument * if it's valid. We tell Var_Parse to complain on an undefined - * variable, so we don't do it too. Nor do we return an error, + * variable, so we don't need to do it. Nor do we return an error, * though perhaps we should... */ - int len; - void *freeIt; + void *nestedVal_freeIt; VarEvalFlags eflags = VARE_UNDEFERR | (doEval ? VARE_WANTRES : 0); - const char *cp2 = Var_Parse(cp, VAR_CMD, eflags, &len, &freeIt); - Buf_AddStr(&buf, cp2); - free(freeIt); - cp += len; + const char *nestedVal; + (void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal, + &nestedVal_freeIt); + /* TODO: handle errors */ + Buf_AddStr(&argBuf, nestedVal); + free(nestedVal_freeIt); continue; } if (ch == '(') paren_depth++; else if (ch == ')' && --paren_depth < 0) break; - Buf_AddByte(&buf, *cp); - cp++; + Buf_AddByte(&argBuf, *p); + p++; } - *argPtr = Buf_GetAll(&buf, &argLen); - Buf_Destroy(&buf, FALSE); + *out_arg = Buf_GetAll(&argBuf, &argLen); + Buf_Destroy(&argBuf, FALSE); - while (*cp == ' ' || *cp == '\t') { - cp++; + while (*p == ' ' || *p == '\t') { + p++; } - if (func != NULL && *cp++ != ')') { + if (func != NULL && *p++ != ')') { Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", func); + /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ return 0; } - *linePtr = cp; + *pp = p; return argLen; } /* Test whether the given variable is defined. */ static Boolean -CondDoDefined(int argLen MAKE_ATTR_UNUSED, const char *arg) +FuncDefined(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { - char *freeIt; - Boolean result = Var_Value(arg, VAR_CMD, &freeIt) != NULL; + void *freeIt; + Boolean result = Var_Value(arg, VAR_CMDLINE, &freeIt) != NULL; bmake_free(freeIt); return result; } -/* Wrapper around Str_Match, to be used by Lst_Find. */ -static Boolean -CondFindStrMatch(const void *string, const void *pattern) -{ - return Str_Match(string, pattern); -} - /* See if the given target is being made. */ static Boolean -CondDoMake(int argLen MAKE_ATTR_UNUSED, const char *arg) +FuncMake(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { - return Lst_Find(create, CondFindStrMatch, arg) != NULL; + StringListNode *ln; + + for (ln = opts.create->first; ln != NULL; ln = ln->next) + if (Str_Match(ln->datum, arg)) + return TRUE; + return FALSE; } /* See if the given file exists. */ static Boolean -CondDoExists(int argLen MAKE_ATTR_UNUSED, const char *arg) +FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { Boolean result; char *path; path = Dir_FindFile(arg, dirSearchPath); - if (DEBUG(COND)) { - fprintf(debug_file, "exists(%s) result is \"%s\"\n", - arg, path ? path : ""); - } + DEBUG2(COND, "exists(%s) result is \"%s\"\n", arg, path ? path : ""); if (path != NULL) { result = TRUE; free(path); @@ -309,23 +321,19 @@ CondDoExists(int argLen MAKE_ATTR_UNUSED, const char *arg) /* See if the given node exists and is an actual target. */ static Boolean -CondDoTarget(int argLen MAKE_ATTR_UNUSED, const char *arg) +FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { - GNode *gn; - - gn = Targ_FindNode(arg, TARG_NOCREATE); - return gn != NULL && !OP_NOP(gn->type); + GNode *gn = Targ_FindNode(arg); + return gn != NULL && GNode_IsTarget(gn); } /* See if the given node exists and is an actual target with commands * associated with it. */ static Boolean -CondDoCommands(int argLen MAKE_ATTR_UNUSED, const char *arg) +FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { - GNode *gn; - - gn = Targ_FindNode(arg, TARG_NOCREATE); - return gn != NULL && !OP_NOP(gn->type) && !Lst_IsEmpty(gn->commands); + GNode *gn = Targ_FindNode(arg); + return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands); } /*- @@ -338,7 +346,7 @@ CondDoCommands(int argLen MAKE_ATTR_UNUSED, const char *arg) * Returns TRUE if the conversion succeeded. */ static Boolean -CondCvtArg(const char *str, double *value) +TryParseNumber(const char *str, double *value) { char *eptr, ech; unsigned long l_val; @@ -346,15 +354,15 @@ CondCvtArg(const char *str, double *value) errno = 0; if (!*str) { - *value = (double)0; + *value = 0.0; return TRUE; } l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10); ech = *eptr; - if (ech == 0 && errno != ERANGE) { + if (ech == '\0' && errno != ERANGE) { d_val = str[0] == '-' ? -(double)-l_val : (double)l_val; } else { - if (ech != 0 && ech != '.' && ech != 'e' && ech != 'E') + if (ech != '\0' && ech != '.' && ech != 'e' && ech != 'E') return FALSE; d_val = strtod(str, &eptr); if (*eptr) @@ -365,51 +373,59 @@ CondCvtArg(const char *str, double *value) return TRUE; } +static Boolean +is_separator(char ch) +{ + return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL; +} + /*- - * Get a string from a variable reference or an optionally quoted - * string. This is called for the lhs and rhs of string compares. + * Parse a string from a variable reference or an optionally quoted + * string. This is called for the lhs and rhs of string comparisons. * * Results: * Returns the string, absent any quotes, or NULL on error. * Sets quoted if the string was quoted. * Sets freeIt if needed. - * - * Side Effects: - * Moves condExpr past the end of this token. */ -/* coverity:[+alloc : arg-*2] */ +/* coverity:[+alloc : arg-*4] */ static const char * -CondGetString(Boolean doEval, Boolean *quoted, void **freeIt, Boolean strictLHS) +CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, + Boolean *quoted, void **freeIt) { Buffer buf; - const char *cp; const char *str; - int len; + Boolean atStart; + const char *nested_p; Boolean qt; const char *start; VarEvalFlags eflags; + VarParseResult parseResult; Buf_Init(&buf, 0); str = NULL; *freeIt = NULL; - *quoted = qt = *condExpr == '"' ? 1 : 0; + *quoted = qt = par->p[0] == '"' ? 1 : 0; + start = par->p; if (qt) - condExpr++; - for (start = condExpr; *condExpr && str == NULL; condExpr++) { - switch (*condExpr) { + par->p++; + while (par->p[0] && str == NULL) { + switch (par->p[0]) { case '\\': - if (condExpr[1] != '\0') { - condExpr++; - Buf_AddByte(&buf, *condExpr); + par->p++; + if (par->p[0] != '\0') { + Buf_AddByte(&buf, par->p[0]); + par->p++; } - break; + continue; case '"': if (qt) { - condExpr++; /* we don't want the quotes */ + par->p++; /* we don't want the quotes */ goto got_str; - } else - Buf_AddByte(&buf, *condExpr); /* likely? */ - break; + } + Buf_AddByte(&buf, par->p[0]); /* likely? */ + par->p++; + continue; case ')': case '!': case '=': @@ -419,15 +435,21 @@ CondGetString(Boolean doEval, Boolean *quoted, void **freeIt, Boolean strictLHS) case '\t': if (!qt) goto got_str; - else - Buf_AddByte(&buf, *condExpr); - break; + Buf_AddByte(&buf, par->p[0]); + par->p++; + continue; case '$': - /* if we are in quotes, then an undefined variable is ok */ + /* if we are in quotes, an undefined variable is ok */ eflags = ((!qt && doEval) ? VARE_UNDEFERR : 0) | (doEval ? VARE_WANTRES : 0); - str = Var_Parse(condExpr, VAR_CMD, eflags, &len, freeIt); + nested_p = par->p; + atStart = nested_p == start; + parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str, + freeIt); + /* TODO: handle errors */ if (str == var_Error) { + if (parseResult & VPR_ANY_MSG) + par->printedError = TRUE; if (*freeIt) { free(*freeIt); *freeIt = NULL; @@ -439,34 +461,26 @@ CondGetString(Boolean doEval, Boolean *quoted, void **freeIt, Boolean strictLHS) str = NULL; goto cleanup; } - condExpr += len; + par->p = nested_p; + /* - * If the '$' was first char (no quotes), and we are - * followed by space, the operator or end of expression, - * we are done. + * 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 ((condExpr == start + len) && - (*condExpr == '\0' || - isspace((unsigned char)*condExpr) || - strchr("!=><)", *condExpr))) { + if (atStart && is_separator(par->p[0])) goto cleanup; - } - /* - * Nope, we better copy str to buf - */ - for (cp = str; *cp; cp++) { - Buf_AddByte(&buf, *cp); - } + + Buf_AddStr(&buf, str); if (*freeIt) { free(*freeIt); *freeIt = NULL; } str = NULL; /* not finished yet */ - condExpr--; /* don't skip over next char */ - break; + continue; default: - if (strictLHS && !qt && *start != '$' && - !isdigit((unsigned char)*start)) { + if (strictLHS && !qt && *start != '$' && !ch_isdigit(*start)) { /* lhs must be quoted, a variable reference or number */ if (*freeIt) { free(*freeIt); @@ -475,8 +489,9 @@ CondGetString(Boolean doEval, Boolean *quoted, void **freeIt, Boolean strictLHS) str = NULL; goto cleanup; } - Buf_AddByte(&buf, *condExpr); - break; + Buf_AddByte(&buf, par->p[0]); + par->p++; + continue; } } got_str: @@ -487,41 +502,116 @@ cleanup: return str; } -/* The different forms of #if's. */ +/* The different forms of .if directives. */ static const struct If { const char *form; /* Form of if */ size_t formlen; /* Length of form */ Boolean doNot; /* TRUE if default function should be negated */ - Boolean (*defProc)(int, const char *); /* Default function to apply */ + Boolean (*defProc)(size_t, const char *); /* Default function to apply */ } ifs[] = { - { "def", 3, FALSE, CondDoDefined }, - { "ndef", 4, TRUE, CondDoDefined }, - { "make", 4, FALSE, CondDoMake }, - { "nmake", 5, TRUE, CondDoMake }, - { "", 0, FALSE, CondDoDefined }, + { "def", 3, FALSE, FuncDefined }, + { "ndef", 4, TRUE, FuncDefined }, + { "make", 4, FALSE, FuncMake }, + { "nmake", 5, TRUE, FuncMake }, + { "", 0, FALSE, FuncDefined }, { NULL, 0, FALSE, NULL } }; -/*- - * Return the next token from the input. +/* Evaluate a "comparison without operator", such as in ".if ${VAR}" or + * ".if 0". */ +static Token +EvalNotEmpty(CondParser *par, const char *lhs, Boolean lhsQuoted) +{ + double left; + + /* For .ifxxx "..." check for non-empty string. */ + if (lhsQuoted) + return lhs[0] != '\0'; + + /* For .ifxxx <number> compare against zero */ + if (TryParseNumber(lhs, &left)) + return left != 0.0; + + /* For .if ${...} check for non-empty string (defProc is ifdef). */ + if (par->if_info->form[0] == '\0') + return lhs[0] != 0; + + /* Otherwise action default test ... */ + return par->if_info->defProc(strlen(lhs), lhs) == !par->if_info->doNot; +} + +/* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ +static Token +EvalCompareNum(double lhs, const char *op, double rhs) +{ + DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, op); + + switch (op[0]) { + case '!': + if (op[1] != '=') { + Parse_Error(PARSE_WARNING, "Unknown operator"); + /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ + return TOK_ERROR; + } + return lhs != rhs; + case '=': + if (op[1] != '=') { + Parse_Error(PARSE_WARNING, "Unknown operator"); + /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ + return TOK_ERROR; + } + return lhs == rhs; + case '<': + return op[1] == '=' ? lhs <= rhs : lhs < rhs; + case '>': + return op[1] == '=' ? lhs >= rhs : lhs > rhs; + } + return TOK_ERROR; +} + +static Token +EvalCompareStr(const char *lhs, const char *op, const char *rhs) +{ + if (!((op[0] == '!' || op[0] == '=') && op[1] == '=')) { + Parse_Error(PARSE_WARNING, + "String comparison operator must be either == or !="); + /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ + return TOK_ERROR; + } + + DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op); + return (*op == '=') == (strcmp(lhs, rhs) == 0); +} + +/* Evaluate a comparison, such as "${VAR} == 12345". */ +static Token +EvalCompare(const char *lhs, Boolean lhsQuoted, const char *op, + const char *rhs, Boolean rhsQuoted) +{ + double left, right; + + if (!rhsQuoted && !lhsQuoted) + if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) + return EvalCompareNum(left, op, right); + + return EvalCompareStr(lhs, op, rhs); +} + +/* Parse a comparison condition such as: * - * Side Effects: - * condPushback will be set back to TOK_NONE if it is used. + * 0 + * ${VAR:Mpattern} + * ${VAR} == value + * ${VAR:U0} < 12345 */ static Token -compare_expression(Boolean doEval) +CondParser_Comparison(CondParser *par, Boolean doEval) { - Token t; - const char *lhs; - const char *rhs; - const char *op; - void *lhsFree; - void *rhsFree; - Boolean lhsQuoted; - Boolean rhsQuoted; - double left, right; + Token t = TOK_ERROR; + const char *lhs, *op, *rhs; + void *lhsFree, *rhsFree; + Boolean lhsQuoted, rhsQuoted; - t = TOK_ERROR; rhs = NULL; lhsFree = rhsFree = NULL; lhsQuoted = rhsQuoted = FALSE; @@ -530,69 +620,44 @@ compare_expression(Boolean doEval) * Parse the variable spec and skip over it, saving its * value in lhs. */ - lhs = CondGetString(doEval, &lhsQuoted, &lhsFree, lhsStrict); + lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhsFree); if (!lhs) goto done; - /* - * Skip whitespace to get to the operator - */ - while (isspace((unsigned char)*condExpr)) - condExpr++; + CondParser_SkipWhitespace(par); /* * Make sure the operator is a valid one. If it isn't a * known relational operator, pretend we got a * != 0 comparison. */ - op = condExpr; - switch (*condExpr) { + op = par->p; + switch (par->p[0]) { case '!': case '=': case '<': case '>': - if (condExpr[1] == '=') { - condExpr += 2; + if (par->p[1] == '=') { + par->p += 2; } else { - condExpr += 1; + par->p++; } break; default: - if (!doEval) { - t = TOK_FALSE; - goto done; - } - /* For .ifxxx "..." check for non-empty string. */ - if (lhsQuoted) { - t = lhs[0] != 0; - goto done; - } - /* For .ifxxx <number> compare against zero */ - if (CondCvtArg(lhs, &left)) { - t = left != 0.0; - goto done; - } - /* For .if ${...} check for non-empty string (defProc is ifdef). */ - if (if_info->form[0] == 0) { - t = lhs[0] != 0; - goto done; - } - /* Otherwise action default test ... */ - t = if_info->defProc(strlen(lhs), lhs) != if_info->doNot; + t = doEval ? EvalNotEmpty(par, lhs, lhsQuoted) : TOK_FALSE; goto done; } - while (isspace((unsigned char)*condExpr)) - condExpr++; + CondParser_SkipWhitespace(par); - if (*condExpr == '\0') { - Parse_Error(PARSE_WARNING, - "Missing right-hand-side of operator"); + if (par->p[0] == '\0') { + Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator"); + /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ goto done; } - rhs = CondGetString(doEval, &rhsQuoted, &rhsFree, FALSE); - if (!rhs) + rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhsFree); + if (rhs == NULL) goto done; if (!doEval) { @@ -600,73 +665,7 @@ compare_expression(Boolean doEval) goto done; } - if (rhsQuoted || lhsQuoted) { - do_string_compare: - if (((*op != '!') && (*op != '=')) || (op[1] != '=')) { - Parse_Error(PARSE_WARNING, - "String comparison operator should be either == or !="); - goto done; - } - - if (DEBUG(COND)) { - fprintf(debug_file, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", - lhs, rhs, op); - } - /* - * Null-terminate rhs and perform the comparison. - * t is set to the result. - */ - if (*op == '=') { - t = strcmp(lhs, rhs) == 0; - } else { - t = strcmp(lhs, rhs) != 0; - } - } else { - /* - * rhs is either a float or an integer. Convert both the - * lhs and the rhs to a double and compare the two. - */ - - if (!CondCvtArg(lhs, &left) || !CondCvtArg(rhs, &right)) - goto do_string_compare; - - if (DEBUG(COND)) { - fprintf(debug_file, "left = %f, right = %f, op = %.2s\n", left, - right, op); - } - switch (op[0]) { - case '!': - if (op[1] != '=') { - Parse_Error(PARSE_WARNING, - "Unknown operator"); - goto done; - } - t = (left != right); - break; - case '=': - if (op[1] != '=') { - Parse_Error(PARSE_WARNING, - "Unknown operator"); - goto done; - } - t = (left == right); - break; - case '<': - if (op[1] == '=') { - t = (left <= right); - } else { - t = (left < right); - } - break; - case '>': - if (op[1] == '=') { - t = (left >= right); - } else { - t = (left > right); - } - break; - } - } + t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted); done: free(lhsFree); @@ -674,104 +673,96 @@ done: return t; } -static int -get_mpt_arg(Boolean doEval, const char **linePtr, char **argPtr, - const char *func MAKE_ATTR_UNUSED) +static size_t +ParseEmptyArg(const char **linePtr, Boolean doEval, + const char *func MAKE_ATTR_UNUSED, char **argPtr) { - /* - * Use Var_Parse to parse the spec in parens and return - * TOK_TRUE if the resulting string is empty. - */ - int length; void *val_freeIt; const char *val; - const char *cp = *linePtr; + size_t magic_res; /* We do all the work here and return the result as the length */ *argPtr = NULL; - val = Var_Parse(cp - 1, VAR_CMD, doEval ? VARE_WANTRES : 0, &length, - &val_freeIt); - /* - * Advance *linePtr to beyond the closing ). Note that - * we subtract one because 'length' is calculated from 'cp - 1'. - */ - *linePtr = cp - 1 + length; + (*linePtr)--; /* Make (*linePtr)[1] point to the '('. */ + (void)Var_Parse(linePtr, VAR_CMDLINE, doEval ? VARE_WANTRES : 0, + &val, &val_freeIt); + /* TODO: handle errors */ + /* If successful, *linePtr points beyond the closing ')' now. */ if (val == var_Error) { free(val_freeIt); - return -1; + return (size_t)-1; } /* A variable is empty when it just contains spaces... 4/15/92, christos */ - while (isspace((unsigned char)val[0])) - val++; + cpp_skip_whitespace(&val); /* * For consistency with the other functions we can't generate the * true/false here. */ - length = *val ? 2 : 1; + magic_res = *val != '\0' ? 2 : 1; free(val_freeIt); - return length; + return magic_res; } static Boolean -CondDoEmpty(int arglen, const char *arg MAKE_ATTR_UNUSED) +FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) { + /* Magic values ahead, see ParseEmptyArg. */ return arglen == 1; } static Token -compare_function(Boolean doEval) +CondParser_Func(CondParser *par, Boolean doEval) { static const struct fn_def { const char *fn_name; size_t fn_name_len; - int (*fn_getarg)(Boolean, const char **, char **, const char *); - Boolean (*fn_proc)(int, const char *); + size_t (*fn_parse)(const char **, Boolean, const char *, char **); + Boolean (*fn_eval)(size_t, const char *); } fn_defs[] = { - { "defined", 7, CondGetArg, CondDoDefined }, - { "make", 4, CondGetArg, CondDoMake }, - { "exists", 6, CondGetArg, CondDoExists }, - { "empty", 5, get_mpt_arg, CondDoEmpty }, - { "target", 6, CondGetArg, CondDoTarget }, - { "commands", 8, CondGetArg, CondDoCommands }, + { "defined", 7, ParseFuncArg, FuncDefined }, + { "make", 4, ParseFuncArg, FuncMake }, + { "exists", 6, ParseFuncArg, FuncExists }, + { "empty", 5, ParseEmptyArg, FuncEmpty }, + { "target", 6, ParseFuncArg, FuncTarget }, + { "commands", 8, ParseFuncArg, FuncCommands }, { NULL, 0, NULL, NULL }, }; const struct fn_def *fn_def; Token t; char *arg = NULL; - int arglen; - const char *cp = condExpr; + size_t arglen; + const char *cp = par->p; const char *cp1; for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) { - if (!istoken(cp, fn_def->fn_name, fn_def->fn_name_len)) + if (!is_token(cp, fn_def->fn_name, fn_def->fn_name_len)) continue; cp += fn_def->fn_name_len; /* There can only be whitespace before the '(' */ - while (isspace((unsigned char)*cp)) - cp++; + cpp_skip_whitespace(&cp); if (*cp != '(') break; - arglen = fn_def->fn_getarg(doEval, &cp, &arg, fn_def->fn_name); - if (arglen <= 0) { - condExpr = cp; - return arglen < 0 ? TOK_ERROR : TOK_FALSE; + arglen = fn_def->fn_parse(&cp, doEval, fn_def->fn_name, &arg); + if (arglen == 0 || arglen == (size_t)-1) { + par->p = cp; + return arglen == 0 ? TOK_FALSE : TOK_ERROR; } /* Evaluate the argument using the required function. */ - t = !doEval || fn_def->fn_proc(arglen, arg); + t = !doEval || fn_def->fn_eval(arglen, arg); free(arg); - condExpr = cp; + par->p = cp; return t; } /* Push anything numeric through the compare expression */ - cp = condExpr; - if (isdigit((unsigned char)cp[0]) || strchr("+-", cp[0])) - return compare_expression(doEval); + cp = par->p; + if (ch_isdigit(cp[0]) || strchr("+-", cp[0])) + return CondParser_Comparison(par, doEval); /* * Most likely we have a naked token to apply the default function to. @@ -781,65 +772,66 @@ compare_function(Boolean doEval) * would be invalid if we did "defined(a)" - so instead treat as an * expression. */ - arglen = CondGetArg(doEval, &cp, &arg, NULL); - for (cp1 = cp; isspace((unsigned char)*cp1); cp1++) - continue; + arglen = ParseFuncArg(&cp, doEval, NULL, &arg); + cp1 = cp; + cpp_skip_whitespace(&cp1); if (*cp1 == '=' || *cp1 == '!') - return compare_expression(doEval); - condExpr = cp; + 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 + * This path always treats .if as .ifdef. To get here, the character * after .if must have been taken literally, so the argument cannot * be empty - even if it contained a variable expansion. */ - t = !doEval || if_info->defProc(arglen, arg) != if_info->doNot; + t = !doEval || par->if_info->defProc(arglen, arg) == !par->if_info->doNot; free(arg); return t; } +/* Return the next token or comparison result from the parser. */ static Token -CondToken(Boolean doEval) +CondParser_Token(CondParser *par, Boolean doEval) { Token t; - t = condPushBack; + t = par->curr; if (t != TOK_NONE) { - condPushBack = TOK_NONE; + par->curr = TOK_NONE; return t; } - while (*condExpr == ' ' || *condExpr == '\t') { - condExpr++; + while (par->p[0] == ' ' || par->p[0] == '\t') { + par->p++; } - switch (*condExpr) { + switch (par->p[0]) { case '(': - condExpr++; + par->p++; return TOK_LPAREN; case ')': - condExpr++; + par->p++; return TOK_RPAREN; case '|': - if (condExpr[1] == '|') { - condExpr++; + par->p++; + if (par->p[0] == '|') { + par->p++; } - condExpr++; return TOK_OR; case '&': - if (condExpr[1] == '&') { - condExpr++; + par->p++; + if (par->p[0] == '&') { + par->p++; } - condExpr++; return TOK_AND; case '!': - condExpr++; + par->p++; return TOK_NOT; case '#': @@ -849,36 +841,28 @@ CondToken(Boolean doEval) case '"': case '$': - return compare_expression(doEval); + return CondParser_Comparison(par, doEval); default: - return compare_function(doEval); + return CondParser_Func(par, doEval); } } -/*- - *----------------------------------------------------------------------- - * CondT -- - * Parse a single term in the expression. This consists of a terminal - * symbol or TOK_NOT and a terminal symbol (not including the binary - * operators): - * T -> defined(variable) | make(target) | exists(file) | symbol - * T -> ! T | ( E ) +/* Parse a single term in the expression. This consists of a terminal symbol + * or TOK_NOT and a term (not including the binary operators): + * + * T -> defined(variable) | make(target) | exists(file) | symbol + * T -> ! T | ( E ) * * Results: * TOK_TRUE, TOK_FALSE or TOK_ERROR. - * - * Side Effects: - * Tokens are consumed. - * - *----------------------------------------------------------------------- */ static Token -CondT(Boolean doEval) +CondParser_Term(CondParser *par, Boolean doEval) { Token t; - t = CondToken(doEval); + t = CondParser_Token(par, doEval); if (t == TOK_EOF) { /* @@ -890,14 +874,14 @@ CondT(Boolean doEval) /* * T -> ( E ) */ - t = CondE(doEval); + t = CondParser_Expr(par, doEval); if (t != TOK_ERROR) { - if (CondToken(doEval) != TOK_RPAREN) { + if (CondParser_Token(par, doEval) != TOK_RPAREN) { t = TOK_ERROR; } } } else if (t == TOK_NOT) { - t = CondT(doEval); + t = CondParser_Term(par, doEval); if (t == TOK_TRUE) { t = TOK_FALSE; } else if (t == TOK_FALSE) { @@ -907,74 +891,61 @@ CondT(Boolean doEval) return t; } -/*- - *----------------------------------------------------------------------- - * CondF -- - * Parse a conjunctive factor (nice name, wot?) - * F -> T && F | T +/* Parse a conjunctive factor (nice name, wot?) + * + * F -> T && F | T * * Results: * TOK_TRUE, TOK_FALSE or TOK_ERROR - * - * Side Effects: - * Tokens are consumed. - * - *----------------------------------------------------------------------- */ static Token -CondF(Boolean doEval) +CondParser_Factor(CondParser *par, Boolean doEval) { Token l, o; - l = CondT(doEval); + l = CondParser_Term(par, doEval); if (l != TOK_ERROR) { - o = CondToken(doEval); + o = CondParser_Token(par, doEval); if (o == TOK_AND) { /* * F -> T && F * - * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we have to - * parse the r.h.s. anyway (to throw it away). - * If T is TOK_TRUE, the result is the r.h.s., be it an TOK_ERROR or no. + * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we + * have to parse the r.h.s. anyway (to throw it away). + * If T is TOK_TRUE, the result is the r.h.s., be it a TOK_ERROR + * or not. */ if (l == TOK_TRUE) { - l = CondF(doEval); + l = CondParser_Factor(par, doEval); } else { - (void)CondF(FALSE); + (void)CondParser_Factor(par, FALSE); } } else { /* * F -> T */ - CondPushBack(o); + CondParser_PushBack(par, o); } } return l; } -/*- - *----------------------------------------------------------------------- - * CondE -- - * Main expression production. - * E -> F || E | F +/* Main expression production. + * + * E -> F || E | F * * Results: * TOK_TRUE, TOK_FALSE or TOK_ERROR. - * - * Side Effects: - * Tokens are, of course, consumed. - * - *----------------------------------------------------------------------- */ static Token -CondE(Boolean doEval) +CondParser_Expr(CondParser *par, Boolean doEval) { Token l, o; - l = CondF(doEval); + l = CondParser_Factor(par, doEval); if (l != TOK_ERROR) { - o = CondToken(doEval); + o = CondParser_Token(par, doEval); if (o == TOK_OR) { /* @@ -986,76 +957,60 @@ CondE(Boolean doEval) * again if l is TOK_TRUE, we parse the r.h.s. to throw it away. */ if (l == TOK_FALSE) { - l = CondE(doEval); + l = CondParser_Expr(par, doEval); } else { - (void)CondE(FALSE); + (void)CondParser_Expr(par, FALSE); } } else { /* * E -> F */ - CondPushBack(o); + CondParser_PushBack(par, o); } } return l; } static CondEvalResult -do_Cond_EvalExpression(Boolean *value) +CondParser_Eval(CondParser *par, Boolean *value) { + Token res; - switch (CondE(TRUE)) { - case TOK_TRUE: - if (CondToken(TRUE) == TOK_EOF) { - *value = TRUE; - return COND_PARSE; - } - break; - case TOK_FALSE: - if (CondToken(TRUE) == TOK_EOF) { - *value = FALSE; - return COND_PARSE; - } - break; - default: - case TOK_ERROR: - break; - } + DEBUG1(COND, "CondParser_Eval: %s\n", par->p); + + res = CondParser_Expr(par, TRUE); + if (res != TOK_FALSE && res != TOK_TRUE) + return COND_INVALID; + + if (CondParser_Token(par, TRUE /* XXX: Why TRUE? */) != TOK_EOF) + return COND_INVALID; - return COND_INVALID; + *value = res == TOK_TRUE; + return COND_PARSE; } -/*- - *----------------------------------------------------------------------- - * Cond_EvalExpression -- - * Evaluate an expression in the passed line. The expression - * consists of &&, ||, !, make(target), defined(variable) - * and parenthetical groupings thereof. +/* 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. + * COND_INVALID if not a valid conditional. * * (*value) is set to the boolean value of the condition - * - * Side Effects: - * Any effects from evaluating the variables. - *----------------------------------------------------------------------- */ -CondEvalResult -Cond_EvalExpression(const struct If *info, char *line, Boolean *value, - int eprint, Boolean strictLHS) +static CondEvalResult +CondEvalExpression(const struct If *info, const char *cond, Boolean *value, + Boolean eprint, Boolean strictLHS) { static const struct If *dflt_info; - const struct If *sv_if_info = if_info; - const char *sv_condExpr = condExpr; - Token sv_condPushBack = condPushBack; + CondParser par; int rval; lhsStrict = strictLHS; - while (*line == ' ' || *line == '\t') - line++; + while (*cond == ' ' || *cond == '\t') + cond++; if (info == NULL && (info = dflt_info) == NULL) { /* Scan for the entry for .if - it can't be first */ @@ -1066,49 +1021,48 @@ Cond_EvalExpression(const struct If *info, char *line, Boolean *value, } assert(info != NULL); - if_info = info; - condExpr = line; - condPushBack = TOK_NONE; + par.if_info = info; + par.p = cond; + par.curr = TOK_NONE; + par.printedError = FALSE; - rval = do_Cond_EvalExpression(value); + rval = CondParser_Eval(&par, value); - if (rval == COND_INVALID && eprint) - Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", line); - - if_info = sv_if_info; - condExpr = sv_condExpr; - condPushBack = sv_condPushBack; + if (rval == COND_INVALID && eprint && !par.printedError) + Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); return rval; } +CondEvalResult +Cond_EvalCondition(const char *cond, Boolean *out_value) +{ + return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE); +} -/*- - *----------------------------------------------------------------------- - * Cond_Eval -- - * Evaluate the conditional in the passed line. The line - * looks like this: - * .<cond-type> <expr> - * where <cond-type> is any of if, ifmake, ifnmake, ifdef, - * ifndef, elif, elifmake, elifnmake, elifdef, elifndef - * and <expr> consists of &&, ||, !, make(target), defined(variable) - * and parenthetical groupings thereof. - * - * Input: - * line Line to parse - * - * Results: - * COND_PARSE if should parse lines after the conditional - * COND_SKIP if should skip lines after the conditional - * COND_INVALID if not a valid conditional. +/* Evaluate the conditional in the passed line. The line looks like this: + * .<cond-type> <expr> + * In this line, <cond-type> is any of if, ifmake, ifnmake, ifdef, ifndef, + * elif, elifmake, elifnmake, elifdef, elifndef. + * In this line, <expr> consists of &&, ||, !, function(arg), comparisons + * and parenthetical groupings thereof. * * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order * to detect spurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF), * otherwise .else could be treated as '.elif 1'. - *----------------------------------------------------------------------- + * + * Results: + * COND_PARSE to continue parsing the lines after the conditional + * (when .if or .else returns TRUE) + * COND_SKIP to skip the lines after the conditional + * (when .if or .elif returns 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_Eval(char *line) +Cond_EvalLine(const char *line) { enum { MAXIF = 128 }; /* maximum depth of .if'ing */ enum { MAXIF_BUMP = 32 }; /* how much to grow by */ @@ -1125,10 +1079,8 @@ Cond_Eval(char *line) const struct If *ifp; Boolean isElif; Boolean value; - int level; /* Level at which to report errors. */ enum if_states state; - level = PARSE_FATAL; if (!cond_state) { cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state)); cond_state[0] = IF_ACTIVE; @@ -1140,11 +1092,11 @@ Cond_Eval(char *line) /* Find what type of if we're dealing with. */ if (line[0] == 'e') { if (line[1] != 'l') { - if (!istoken(line + 1, "ndif", 4)) + if (!is_token(line + 1, "ndif", 4)) return COND_INVALID; /* End of conditional section */ if (cond_depth == cond_min_depth) { - Parse_Error(level, "if-less endif"); + Parse_Error(PARSE_FATAL, "if-less endif"); return COND_PARSE; } /* Return state for previous conditional */ @@ -1155,10 +1107,10 @@ Cond_Eval(char *line) /* Quite likely this is 'else' or 'elif' */ line += 2; - if (istoken(line, "se", 2)) { + if (is_token(line, "se", 2)) { /* It is else... */ if (cond_depth == cond_min_depth) { - Parse_Error(level, "if-less else"); + Parse_Error(PARSE_FATAL, "if-less else"); return COND_PARSE; } @@ -1197,7 +1149,7 @@ Cond_Eval(char *line) for (ifp = ifs;; ifp++) { if (ifp->form == NULL) return COND_INVALID; - if (istoken(ifp->form, line, ifp->formlen)) { + if (is_token(ifp->form, line, ifp->formlen)) { line += ifp->formlen; break; } @@ -1207,7 +1159,7 @@ Cond_Eval(char *line) if (isElif) { if (cond_depth == cond_min_depth) { - Parse_Error(level, "if-less elif"); + Parse_Error(PARSE_FATAL, "if-less elif"); return COND_PARSE; } state = cond_state[cond_depth]; @@ -1242,8 +1194,8 @@ Cond_Eval(char *line) } } - /* And evaluate the conditional expresssion */ - if (Cond_EvalExpression(ifp, line, &value, 1, TRUE) == COND_INVALID) { + /* And evaluate the conditional expression */ + if (CondEvalExpression(ifp, line, &value, TRUE, TRUE) == COND_INVALID) { /* Syntax error in conditional, error message already output. */ /* Skip everything to matching .endif */ cond_state[cond_depth] = SKIP_TO_ELSE; @@ -1261,10 +1213,10 @@ Cond_Eval(char *line) void Cond_restore_depth(unsigned int saved_depth) { - int open_conds = cond_depth - cond_min_depth; + unsigned int open_conds = cond_depth - cond_min_depth; if (open_conds != 0 || saved_depth > cond_depth) { - Parse_Error(PARSE_FATAL, "%d open conditional%s", open_conds, + Parse_Error(PARSE_FATAL, "%u open conditional%s", open_conds, open_conds == 1 ? "" : "s"); cond_depth = cond_min_depth; } @@ -1275,7 +1227,7 @@ Cond_restore_depth(unsigned int saved_depth) unsigned int Cond_save_depth(void) { - int depth = cond_min_depth; + unsigned int depth = cond_min_depth; cond_min_depth = cond_depth; return depth; diff --git a/configure b/configure index 7c338bc141c8..814157f4e75f 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for bmake 20200710. +# Generated by GNU Autoconf 2.69 for bmake 20201018. # # Report bugs to <sjg@NetBSD.org>. # @@ -580,8 +580,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='bmake' PACKAGE_TARNAME='bmake' -PACKAGE_VERSION='20200710' -PACKAGE_STRING='bmake 20200710' +PACKAGE_VERSION='20201018' +PACKAGE_STRING='bmake 20201018' PACKAGE_BUGREPORT='sjg@NetBSD.org' PACKAGE_URL='' @@ -1254,7 +1254,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures bmake 20200710 to adapt to many kinds of systems. +\`configure' configures bmake 20201018 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1315,7 +1315,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of bmake 20200710:";; + short | recursive ) echo "Configuration of bmake 20201018:";; esac cat <<\_ACEOF @@ -1323,16 +1323,16 @@ Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] - --disable-pwd-override disable \$PWD overriding getcwd() + --disable-pwd-override disable $PWD overriding getcwd() --disable-check-make-chdir disable make trying to guess - when it should automatically cd \${.CURDIR} + when it should automatically cd ${.CURDIR} Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-defshell=SHELL use SHELL by default - must be sh compatible, use sh or ksh to pick the internal definitions - --without-makefile dissable use of generated makefile - --without-meta dissable use of meta-mode + --without-makefile disable use of generated makefile + --without-meta disable use of meta-mode --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev --with-machine=MACHINE explicitly set MACHINE --with-force-machine=MACHINE set FORCE_MACHINE @@ -1421,7 +1421,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -bmake configure 20200710 +bmake configure 20201018 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2001,7 +2001,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by bmake $as_me 20200710, which was +It was created by bmake $as_me 20201018, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -4543,7 +4543,6 @@ if test $bmake_path_max -gt 1024; then fi echo "Using: BMAKE_PATH_MAX=$bmake_path_max" >&6 -$ac_includes_default { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sys/wait.h that is POSIX.1 compatible" >&5 $as_echo_n "checking for sys/wait.h that is POSIX.1 compatible... " >&6; } if ${ac_cv_header_sys_wait_h+:} false; then : @@ -6665,7 +6664,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by bmake $as_me 20200710, which was +This file was extended by bmake $as_me 20201018, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -6727,7 +6726,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -bmake config.status 20200710 +bmake config.status 20201018 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.in b/configure.in index 5b45329ea3d1..384c403e544a 100644 --- a/configure.in +++ b/configure.in @@ -1,11 +1,11 @@ dnl dnl RCSid: -dnl $Id: configure.in,v 1.66 2020/07/10 16:34:38 sjg Exp $ +dnl $Id: configure.in,v 1.67 2020/10/19 19:47:50 sjg Exp $ dnl dnl Process this file with autoconf to produce a configure script dnl AC_PREREQ(2.50) -AC_INIT([bmake], [20200710], [sjg@NetBSD.org]) +AC_INIT([bmake], [20201018], [sjg@NetBSD.org]) AC_CONFIG_HEADERS(config.h) dnl make srcdir absolute @@ -38,7 +38,7 @@ CYGWIN*|MINGW*) use_makefile=no;; *) use_makefile=yes;; esac AC_ARG_WITH(makefile, -[ --without-makefile dissable use of generated makefile], +[ --without-makefile disable use of generated makefile], [case "${withval}" in yes|no) use_makefile=${withval};; *) AC_MSG_ERROR(bad value ${withval} given for makefile) ;; @@ -46,7 +46,7 @@ esac]) dnl use_meta=yes AC_ARG_WITH(meta, -[ --without-meta dissable use of meta-mode], +[ --without-meta disable use of meta-mode], [case "${withval}" in yes|no) use_meta=${withval};; *) AC_MSG_ERROR(bad value ${withval} given for meta) ;; @@ -128,7 +128,6 @@ dnl AC_C_CROSS dnl dnl Checks for header files. -AC_INCLUDES_DEFAULT AC_HEADER_SYS_WAIT AC_HEADER_DIRENT dnl Keep this list sorted @@ -352,7 +351,7 @@ dnl dnl And this can be handy to do with out. dnl AC_ARG_ENABLE(pwd-override, -[ --disable-pwd-override disable \$PWD overriding getcwd()], +[ --disable-pwd-override disable $PWD overriding getcwd()], [case "${enableval}" in yes) ;; no) CPPFLAGS="$CPPFLAGS -DNO_PWD_OVERRIDE" ;; @@ -363,7 +362,7 @@ dnl Just for grins dnl AC_ARG_ENABLE(check-make-chdir, [ --disable-check-make-chdir disable make trying to guess - when it should automatically cd \${.CURDIR}], + when it should automatically cd ${.CURDIR}], [case "${enableval}" in yes) ;; no) CPPFLAGS="$CPPFLAGS -DNO_CHECK_MAKE_CHDIR" ;; @@ -1,4 +1,4 @@ -/* $NetBSD: dir.c,v 1.135 2020/09/02 04:32:13 rillig Exp $ */ +/* $NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,70 +69,58 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: dir.c,v 1.135 2020/09/02 04:32:13 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)dir.c 8.2 (Berkeley) 1/2/94"; -#else -__RCSID("$NetBSD: dir.c,v 1.135 2020/09/02 04:32:13 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * dir.c -- - * Directory searching using wildcards and/or normal names... - * Used both for source wildcarding in the Makefile and for finding - * implicit sources. +/* 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_Init Initialize the module. * - * Dir_InitCur Set the cur Path. + * Dir_InitCur Set the cur CachedDir. * - * Dir_InitDot Set the dot Path. + * Dir_InitDot Set the dot CachedDir. * - * Dir_End Cleanup the module. + * Dir_End Clean up the module. * - * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath. + * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath. * - * Dir_HasWildcards Returns TRUE if the name given it needs to - * be wildcard-expanded. + * Dir_HasWildcards + * Returns TRUE if the name given it needs to + * be wildcard-expanded. * - * Dir_Expand Given a pattern and a path, return a Lst of names - * which match the pattern on the search path. + * Dir_Expand Given a pattern and a path, return a Lst of names + * which match the pattern on 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_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_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_MTime Return the modification time of a node. The file - * is searched for along the default search path. - * The path and mtime fields of the node are filled - * in. + * Dir_MTime Return the modification time of a node. The file + * is searched for along the default search path. + * The path and mtime fields of the node are filled in. * - * Dir_AddDir Add a directory to a search path. + * Dir_AddDir Add a directory to a search path. * - * Dir_MakeFlags 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_MakeFlags 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. - * Dir_ClearPath Resets a search path to the empty list. + * 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. + * + * Dir_ClearPath Resets a search path to the empty list. * * For debugging: - * Dir_PrintDirectories Print stats about the directory cache. + * Dir_PrintDirectories + * Print stats about the directory cache. */ #include <sys/types.h> @@ -140,99 +128,155 @@ __RCSID("$NetBSD: dir.c,v 1.135 2020/09/02 04:32:13 rillig Exp $"); #include <dirent.h> #include <errno.h> -#include <stdio.h> #include "make.h" #include "dir.h" #include "job.h" +/* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ +MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $"); + +#define DIR_DEBUG0(text) DEBUG0(DIR, text) +#define DIR_DEBUG1(fmt, arg1) DEBUG1(DIR, fmt, arg1) +#define DIR_DEBUG2(fmt, arg1, arg2) DEBUG2(DIR, fmt, arg1, arg2) + +/* A search path is a list of CachedDir structures. A CachedDir has in it the + * name of the directory and the names of all the files in the directory. + * This is used to cut down on the number of system calls necessary to find + * implicit dependents and their like. Since these searches are made before + * any actions are taken, we need not worry about the directory changing due + * to creation commands. If this hampers the style of some makefiles, they + * must be changed. + * + * All previously-read directories are kept in openDirs, which is checked + * first before a directory is opened. + * + * The need for the caching of whole directories is brought about by the + * multi-level transformation code in suff.c, which tends to search for far + * more files than regular make does. In the initial implementation, the + * amount of time spent performing "stat" calls was truly astronomical. + * The problem with caching at the start is, of course, that pmake doesn't + * then detect changes to these directories during the course of the make. + * Three possibilities suggest themselves: + * + * 1) just use stat to test for a file's existence. As mentioned above, + * this is very inefficient due to the number of checks engendered by + * the multi-level transformation code. + * + * 2) use readdir() and company to search the directories, keeping them + * open between checks. I have tried this and while it didn't slow down + * the process too much, it could severely affect the amount of + * parallelism available as each directory open would take another file + * descriptor out of play for handling I/O for another job. Given that + * it is only recently that UNIX OS's have taken to allowing more than + * 20 or 32 file descriptors for a process, this doesn't seem acceptable + * to me. + * + * 3) record the mtime of the directory in the CachedDir structure and + * verify the directory hasn't changed since the contents were cached. + * This will catch the creation or deletion of files, but not the + * updating of files. However, since it is the creation and deletion + * that is the problem, this could be a good thing to do. Unfortunately, + * if the directory (say ".") were fairly large and changed fairly + * frequently, the constant reloading could seriously degrade + * performance. It might be good in such cases to keep track of the + * number of reloadings and if the number goes over a (small) limit, + * resort to using stat in its place. + * + * An additional thing to consider is that pmake is used primarily to create + * C programs and until recently pcc-based compilers refused to allow you to + * specify where the resulting object file should be placed. This forced all + * objects to be created in the current directory. This isn't meant as a full + * excuse, just an explanation of some of the reasons for the caching used + * here. + * + * One more note: the location of a target's file is only performed on the + * downward traversal of the graph and then only for terminal nodes in the + * graph. This could be construed as wrong in some cases, but prevents + * inadvertent modification of files when the "installed" directory for a + * file is provided in the search path. + * + * Another data structure maintained by this module is an mtime cache used + * when the searching of cached directories fails to find a file. In the past, + * Dir_FindFile would simply perform an access() call in such a case to + * determine if the file could be found using just the name given. When this + * hit, however, all that was gained was the knowledge that the file existed. + * Given that an access() is essentially a stat() without the copyout() call, + * and that the same filesystem overhead would have to be incurred in + * Dir_MTime, it made sense to replace the access() with a stat() and record + * the mtime in a cache for when Dir_MTime was actually called. + */ -#define DIR_DEBUG0(fmt) \ - if (!DEBUG(DIR)) (void) 0; else fprintf(debug_file, fmt) +typedef List CachedDirList; +typedef ListNode CachedDirListNode; -#define DIR_DEBUG1(fmt, arg1) \ - if (!DEBUG(DIR)) (void) 0; else fprintf(debug_file, fmt, arg1) +typedef ListNode SearchPathNode; -#define DIR_DEBUG2(fmt, arg1, arg2) \ - if (!DEBUG(DIR)) (void) 0; else fprintf(debug_file, fmt, arg1, arg2) +SearchPath *dirSearchPath; /* main search path */ +/* A list of cached directories, with fast lookup by directory name. */ +typedef struct OpenDirs { + CachedDirList *list; + HashTable /* of CachedDirListNode */ table; +} OpenDirs; -/* - * A search path consists of a Lst of Path structures. A Path structure - * has in it the name of the directory and a hash table 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. - * - * A list of all previously-read directories is kept in the - * openDirectories Lst. This list 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 hashing at the start is, - * of course, that pmake doesn't then detect changes to these directories - * during the course of the make. Three possibilities suggest themselves: - * - * 1) just use stat to test for a file's existence. As mentioned - * above, this is very inefficient due to the number of checks - * engendered by the multi-level transformation code. - * 2) use readdir() and company to search the directories, keeping - * them open between checks. I have tried this and while it - * didn't slow down the process too much, it could severely - * affect the amount of parallelism available as each directory - * open would take another file descriptor out of play for - * handling I/O for another job. Given that it is only recently - * that UNIX OS's have taken to allowing more than 20 or 32 - * file descriptors for a process, this doesn't seem acceptable - * to me. - * 3) record the mtime of the directory in the Path structure and - * verify the directory hasn't changed since the contents were - * hashed. 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 - * rehashing could seriously degrade performance. It might be - * good in such cases to keep track of the number of rehashes - * and if the number goes over a (small) limit, resort to using - * stat in its place. - * - * An additional thing to consider is that pmake is used primarily - * to create C programs and until recently pcc-based compilers refused - * to allow you to specify where the resulting object file should be - * placed. This forced all objects to be created in the current - * directory. This isn't meant as a full excuse, just an explanation of - * some of the reasons for the caching used here. - * - * One more note: the location of a target's file is only performed - * on the downward traversal of the graph and then only for terminal - * nodes in the graph. This could be construed as wrong in some cases, - * but prevents inadvertent modification of files when the "installed" - * directory for a file is provided in the search path. - * - * Another data structure maintained by this module is an mtime - * cache used when the searching of cached directories fails to find - * a file. In the past, Dir_FindFile would simply perform an access() - * call in such a case to determine if the file could be found using - * just the name given. When this hit, however, all that was gained - * was the knowledge that the file existed. Given that an access() is - * essentially a stat() without the copyout() call, and that the same - * filesystem overhead would have to be incurred in Dir_MTime, it made - * sense to replace the access() with a stat() and record the mtime - * in a cache for when Dir_MTime was actually called. - */ +static void +OpenDirs_Init(OpenDirs *odirs) +{ + odirs->list = Lst_New(); + HashTable_Init(&odirs->table); +} + +#ifdef CLEANUP +static void +OpenDirs_Done(OpenDirs *odirs) +{ + CachedDirListNode *ln = odirs->list->first; + while (ln != NULL) { + CachedDirListNode *next = ln->next; + CachedDir *dir = ln->datum; + Dir_Destroy(dir); /* removes the dir from odirs->list */ + ln = next; + } + Lst_Free(odirs->list); + HashTable_Done(&odirs->table); +} +#endif + +static CachedDir * +OpenDirs_Find(OpenDirs *odirs, const char *name) +{ + CachedDirListNode *ln = HashTable_FindValue(&odirs->table, name); + return ln != NULL ? ln->datum : NULL; +} + +static void +OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir) +{ + HashEntry *he = HashTable_FindEntry(&odirs->table, cdir->name); + if (he != NULL) + return; + he = HashTable_CreateEntry(&odirs->table, cdir->name, NULL); + Lst_Append(odirs->list, cdir); + HashEntry_Set(he, odirs->list->last); +} -Lst dirSearchPath; /* main search path */ +static void +OpenDirs_Remove(OpenDirs *odirs, const char *name) +{ + HashEntry *he = HashTable_FindEntry(&odirs->table, name); + CachedDirListNode *ln; + if (he == NULL) + return; + ln = HashEntry_Get(he); + HashTable_DeleteEntry(&odirs->table, he); + Lst_Remove(odirs->list, ln); +} -static Lst openDirectories; /* the list of all open directories */ +static OpenDirs openDirs; /* the list of all open directories */ /* - * Variables for gathering statistics on the efficiency of the hashing + * Variables for gathering statistics on the efficiency of the cashing * mechanism. */ static int hits; /* Found in directory cache */ @@ -240,9 +284,9 @@ static int misses; /* Sad, but not evil misses */ static int nearmisses; /* Found under search path */ static int bigmisses; /* Sought by itself */ -static Path *dot; /* contents of current directory */ -static Path *cur; /* contents of current directory, if not dot */ -static Path *dotLast; /* a fake path entry indicating we need to +static CachedDir *dot; /* contents of current directory */ +static CachedDir *cur; /* contents of current directory, if not dot */ +static CachedDir *dotLast; /* a fake path entry indicating we need to * look for . last */ /* Results of doing a last-resort stat in Dir_FindFile -- if we have to go to @@ -252,19 +296,9 @@ static Path *dotLast; /* a fake path entry indicating we need to * 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 Hash_Table mtimes; - -static Hash_Table lmtimes; /* same as mtimes but for lstat */ - -static void DirExpandCurly(const char *, const char *, Lst, Lst); -static void DirExpandInt(const char *, Lst, Lst); -static int DirPrintWord(void *, void *); -static int DirPrintDir(void *, void *); -static char *DirLookup(Path *, const char *, const char *, Boolean); -static char *DirLookupSubdir(Path *, const char *); -static char *DirFindDot(Boolean, const char *, const char *); -static char *DirLookupAbs(Path *, const char *, const char *); +static HashTable mtimes; +static HashTable lmtimes; /* same as mtimes but for lstat */ /* * We use stat(2) a lot, cache the results. @@ -277,17 +311,17 @@ struct cache_st { }; /* minimize changes below */ -typedef enum { +typedef enum CachedStatsFlags { CST_LSTAT = 0x01, /* call lstat(2) instead of stat(2) */ CST_UPDATE = 0x02 /* ignore existing cached entry */ } CachedStatsFlags; /* Returns 0 and the result of stat(2) or lstat(2) in *mst, or -1 on error. */ static int -cached_stats(Hash_Table *htp, const char *pathname, struct make_stat *mst, +cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst, CachedStatsFlags flags) { - Hash_Entry *entry; + HashEntry *entry; struct stat sys_st; struct cache_st *cst; int rc; @@ -295,10 +329,10 @@ cached_stats(Hash_Table *htp, const char *pathname, struct make_stat *mst, if (!pathname || !pathname[0]) return -1; - entry = Hash_FindEntry(htp, pathname); + entry = HashTable_FindEntry(htp, pathname); if (entry && !(flags & CST_UPDATE)) { - cst = Hash_GetValue(entry); + cst = HashEntry_Get(entry); mst->mst_mode = cst->mode; mst->mst_mtime = (flags & CST_LSTAT) ? cst->lmtime : cst->mtime; @@ -322,12 +356,12 @@ cached_stats(Hash_Table *htp, const char *pathname, struct make_stat *mst, mst->mst_mtime = sys_st.st_mtime; if (entry == NULL) - entry = Hash_CreateEntry(htp, pathname, NULL); - if (Hash_GetValue(entry) == NULL) { - Hash_SetValue(entry, bmake_malloc(sizeof(*cst))); - memset(Hash_GetValue(entry), 0, sizeof(*cst)); + entry = HashTable_CreateEntry(htp, pathname, NULL); + if (HashEntry_Get(entry) == NULL) { + HashEntry_Set(entry, bmake_malloc(sizeof(*cst))); + memset(HashEntry_Get(entry), 0, sizeof(*cst)); } - cst = Hash_GetValue(entry); + cst = HashEntry_Get(entry); if (flags & CST_LSTAT) { cst->lmtime = sys_st.st_mtime; } else { @@ -352,14 +386,14 @@ cached_lstat(const char *pathname, struct make_stat *st) return cached_stats(&lmtimes, pathname, st, CST_LSTAT); } -/* Initialize things for this module. */ +/* Initialize the directories module. */ void Dir_Init(void) { - dirSearchPath = Lst_Init(); - openDirectories = Lst_Init(); - Hash_InitTable(&mtimes, 0); - Hash_InitTable(&lmtimes, 0); + dirSearchPath = Lst_New(); + OpenDirs_Init(&openDirs); + HashTable_Init(&mtimes); + HashTable_Init(&lmtimes); } void @@ -367,11 +401,11 @@ Dir_InitDir(const char *cdname) { Dir_InitCur(cdname); - dotLast = bmake_malloc(sizeof(Path)); + dotLast = bmake_malloc(sizeof(CachedDir)); dotLast->refCount = 1; dotLast->hits = 0; dotLast->name = bmake_strdup(".DOTLAST"); - Hash_InitTable(&dotLast->files, -1); + HashTable_Init(&dotLast->files); } /* @@ -380,23 +414,23 @@ Dir_InitDir(const char *cdname) void Dir_InitCur(const char *cdname) { - Path *p; + CachedDir *dir; if (cdname != NULL) { /* * Our build directory is not the same as our source directory. * Keep this one around too. */ - if ((p = Dir_AddDir(NULL, cdname))) { - p->refCount += 1; - if (cur && cur != p) { + if ((dir = Dir_AddDir(NULL, cdname))) { + dir->refCount++; + if (cur && cur != dir) { /* - * We've been here before, cleanup. + * We've been here before, clean up. */ - cur->refCount -= 1; + cur->refCount--; Dir_Destroy(cur); } - cur = p; + cur = dir; } } } @@ -407,11 +441,8 @@ void Dir_InitDot(void) { if (dot != NULL) { - LstNode ln; - - /* Remove old entry from openDirectories, but do not destroy. */ - ln = Lst_FindDatum(openDirectories, dot); - Lst_Remove(openDirectories, ln); + /* Remove old entry from openDirs, but do not destroy. */ + OpenDirs_Remove(&openDirs, dot->name); } dot = Dir_AddDir(NULL, "."); @@ -425,28 +456,27 @@ Dir_InitDot(void) * We always need to have dot around, so we increment its reference count * to make sure it's not destroyed. */ - dot->refCount += 1; + dot->refCount++; Dir_SetPATH(); /* initialize */ } -/* Clean up things for this module. */ +/* Clean up the directories module. */ void Dir_End(void) { #ifdef CLEANUP if (cur) { - cur->refCount -= 1; + cur->refCount--; Dir_Destroy(cur); } - dot->refCount -= 1; - dotLast->refCount -= 1; + dot->refCount--; + dotLast->refCount--; Dir_Destroy(dotLast); Dir_Destroy(dot); Dir_ClearPath(dirSearchPath); Lst_Free(dirSearchPath); - Dir_ClearPath(openDirectories); - Lst_Free(openDirectories); - Hash_DeleteTable(&mtimes); + OpenDirs_Done(&openDirs); + HashTable_Done(&mtimes); #endif } @@ -458,16 +488,14 @@ Dir_End(void) void Dir_SetPATH(void) { - LstNode ln; /* a list element */ - Path *p; + CachedDirListNode *ln; Boolean hasLastDot = FALSE; /* true if we should search dot last */ Var_Delete(".PATH", VAR_GLOBAL); - Lst_Open(dirSearchPath); - if ((ln = Lst_First(dirSearchPath)) != NULL) { - p = LstNode_Datum(ln); - if (p == dotLast) { + if ((ln = dirSearchPath->first) != NULL) { + CachedDir *dir = ln->datum; + if (dir == dotLast) { hasLastDot = TRUE; Var_Append(".PATH", dotLast->name, VAR_GLOBAL); } @@ -480,13 +508,13 @@ Dir_SetPATH(void) Var_Append(".PATH", cur->name, VAR_GLOBAL); } - while ((ln = Lst_Next(dirSearchPath)) != NULL) { - p = LstNode_Datum(ln); - if (p == dotLast) + for (ln = dirSearchPath->first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (dir == dotLast) continue; - if (p == dot && hasLastDot) + if (dir == dot && hasLastDot) continue; - Var_Append(".PATH", p->name, VAR_GLOBAL); + Var_Append(".PATH", dir->name, VAR_GLOBAL); } if (hasLastDot) { @@ -495,40 +523,26 @@ Dir_SetPATH(void) if (cur) Var_Append(".PATH", cur->name, VAR_GLOBAL); } - Lst_Close(dirSearchPath); -} - -/* See if the Path structure describes the same directory as the - * given one by comparing their names. Called from Dir_AddDir via - * Lst_Find when searching the list of open directories. */ -static Boolean -DirFindName(const void *p, const void *desiredName) -{ - return strcmp(((const Path *)p)->name, desiredName) == 0; } -/* See if the given name has any wildcard characters in it. Be careful not to - * expand unmatching brackets or braces. +/* 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! * - * Input: - * name name to check - * - * Results: - * returns TRUE if the word should be expanded, FALSE otherwise + * Return TRUE if the word should be expanded, FALSE otherwise. */ Boolean Dir_HasWildcards(const char *name) { - const char *cp; + const char *p; Boolean wild = FALSE; int braces = 0, brackets = 0; - for (cp = name; *cp; cp++) { - switch (*cp) { + for (p = name; *p != '\0'; p++) { + switch (*p) { case '{': braces++; wild = TRUE; @@ -554,51 +568,49 @@ Dir_HasWildcards(const char *name) return wild && brackets == 0 && braces == 0; } -/*- - *----------------------------------------------------------------------- - * DirMatchFiles -- - * Given a pattern and a Path structure, see if any files - * match the pattern and add their names to the 'expansions' list if - * any do. This is incomplete -- it doesn't take care of patterns like - * src / *src / *.c properly (just *.c on any of the directories), but it - * will do for now. +/* See if any files match the pattern and add their names to the 'expansions' + * list if they do. + * + * This is incomplete -- wildcards are only expanded in the final path + * component, but not in directories like src/lib*c/file*.c, but it + * will do for now (now being 1993 until at least 2020). To expand these, + * use the ':sh' variable modifier such as in ${:!echo src/lib*c/file*.c!}. * * Input: * pattern Pattern to look for - * p Directory to search + * dir Directory to search * expansion Place to store the results - * - * Side Effects: - * File names are added to the expansions lst. The directory will be - * fully hashed when this is done. - *----------------------------------------------------------------------- */ static void -DirMatchFiles(const char *pattern, Path *p, Lst expansions) +DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions) { - Hash_Search search; /* Index into the directory's table */ - Hash_Entry *entry; /* Current entry in the table */ - Boolean isDot; /* TRUE if the directory being searched is . */ + const char *dirName = dir->name; + Boolean isDot = dirName[0] == '.' && dirName[1] == '\0'; + HashIter hi; + + HashIter_Init(&hi, &dir->files); + while (HashIter_Next(&hi) != NULL) { + const char *base = hi.entry->key; - isDot = (*p->name == '.' && p->name[1] == '\0'); + if (!Str_Match(base, pattern)) + continue; - for (entry = Hash_EnumFirst(&p->files, &search); - entry != NULL; - entry = Hash_EnumNext(&search)) - { /* - * See if the file matches the given pattern. Note we follow the UNIX - * convention that dot files will only be found if the pattern - * begins with a dot (note also that as a side effect of the hashing - * scheme, .* won't match . or .. since they aren't hashed). + * Follow the UNIX convention that dot files are only found if the + * pattern begins with a dot. The pattern '.*' does not match '.' or + * '..' since these are not included in the directory cache. + * + * This means that the pattern '[a-z.]*' does not find '.file', which + * is consistent with bash, NetBSD sh and csh. */ - if (Str_Match(entry->name, pattern) && - ((entry->name[0] != '.') || - (pattern[0] == '.'))) + if (base[0] == '.' && pattern[0] != '.') + continue; + { - Lst_Append(expansions, - (isDot ? bmake_strdup(entry->name) : - str_concat3(p->name, "/", entry->name))); + char *fullName = isDot + ? bmake_strdup(base) + : str_concat3(dirName, "/", base); + Lst_Append(expansions, fullName); } } } @@ -667,30 +679,24 @@ concat3(const char *a, size_t a_len, const char *b, size_t b_len, return s; } -/*- - *----------------------------------------------------------------------- - * DirExpandCurly -- - * Expand curly braces like the C shell. Does this recursively. - * Note the special case: if after the piece of the curly brace is - * done there are no wildcard characters in the result, the result is - * placed on the list WITHOUT CHECKING FOR ITS EXISTENCE. +/* 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 - * - * Results: - * None. - * - * Side Effects: - * The given list is filled with the expansions... - * - *----------------------------------------------------------------------- */ static void -DirExpandCurly(const char *word, const char *brace, Lst path, Lst expansions) +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; @@ -731,72 +737,40 @@ DirExpandCurly(const char *word, const char *brace, Lst path, Lst expansions) } -/*- - *----------------------------------------------------------------------- - * DirExpandInt -- - * Internal expand routine. Passes through the directories in the - * path one by one, calling DirMatchFiles for each. NOTE: This still - * doesn't handle patterns in directories... - * - * Input: - * word Word to expand - * path Path on which to look - * expansions Place to store the result - * - * Results: - * None. - * - * Side Effects: - * Things are added to the expansions list. - * - *----------------------------------------------------------------------- - */ +/* Expand the word in each of the directories from the path. */ static void -DirExpandInt(const char *word, Lst path, Lst expansions) +DirExpandPath(const char *word, SearchPath *path, StringList *expansions) { - LstNode ln; /* Current node */ - - Lst_Open(path); - while ((ln = Lst_Next(path)) != NULL) { - Path *p = LstNode_Datum(ln); - DirMatchFiles(word, p, expansions); + SearchPathNode *ln; + for (ln = path->first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + DirMatchFiles(word, dir, expansions); } - Lst_Close(path); } -/* Print a word in the list of expansions. - * Callback for Dir_Expand when DEBUG(DIR), via Lst_ForEach. */ -static int -DirPrintWord(void *word, void *dummy MAKE_ATTR_UNUSED) +static void +PrintExpansions(StringList *expansions) { - fprintf(debug_file, "%s ", (char *)word); - - return 0; + 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"); } -/*- - *----------------------------------------------------------------------- - * Dir_Expand -- - * Expand the given word into a list of words by globbing it looking - * in the directories on the given search path. +/* Expand the given word into a list of words by globbing it, looking in the + * directories on the given search path. * * Input: * word the word to expand - * path the list of directories in which to find the - * resulting files + * path the directories in which to find the files * expansions the list on which to place the results - * - * Results: - * A list of words consisting of the files which exist along the search - * path matching the given pattern. - * - * Side Effects: - * Directories may be opened. Who knows? - * Undefined behavior if the word is really in read-only memory. - *----------------------------------------------------------------------- */ void -Dir_Expand(const char *word, Lst path, Lst expansions) +Dir_Expand(const char *word, SearchPath *path, StringList *expansions) { const char *cp; @@ -816,17 +790,12 @@ Dir_Expand(const char *word, Lst path, Lst expansions) * in the string. */ for (cp = word; *cp; cp++) { - if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') { + if (*cp == '?' || *cp == '[' || *cp == '*') { break; } } - if (*cp == '{') { - /* - * This one will be fun. - */ - DirExpandCurly(word, cp, path, expansions); - return; - } else if (*cp != '\0') { + + if (*cp != '\0') { /* * Back up to the start of the component */ @@ -834,16 +803,13 @@ Dir_Expand(const char *word, Lst path, Lst expansions) cp--; } if (cp != word) { - char sc; - char *dirpath; + char *prefix = bmake_strsedup(word, cp + 1); /* * If the glob isn't in the first component, try and find * all the components up to the one with a wildcard. */ - sc = cp[1]; - ((char *)UNCONST(cp))[1] = '\0'; - dirpath = Dir_FindFile(word, path); - ((char *)UNCONST(cp))[1] = sc; + char *dirpath = Dir_FindFile(prefix, path); + free(prefix); /* * dirpath is null if can't find the leading component * XXX: Dir_FindFile won't find internal components. @@ -855,22 +821,22 @@ Dir_Expand(const char *word, Lst path, Lst expansions) char *dp = &dirpath[strlen(dirpath) - 1]; if (*dp == '/') *dp = '\0'; - path = Lst_Init(); + path = Lst_New(); (void)Dir_AddDir(path, dirpath); - DirExpandInt(cp + 1, path, expansions); + DirExpandPath(cp + 1, path, expansions); Lst_Free(path); } } else { /* * Start the search from the local directory */ - DirExpandInt(word, path, expansions); + DirExpandPath(word, path, expansions); } } else { /* * Return the file -- this should never happen. */ - DirExpandInt(word, path, expansions); + DirExpandPath(word, path, expansions); } } else { /* @@ -881,108 +847,63 @@ Dir_Expand(const char *word, Lst path, Lst expansions) /* * Then the files in every other directory on the path. */ - DirExpandInt(word, path, expansions); + DirExpandPath(word, path, expansions); } } - if (DEBUG(DIR)) { - Lst_ForEach(expansions, DirPrintWord, NULL); - fprintf(debug_file, "\n"); - } + if (DEBUG(DIR)) + PrintExpansions(expansions); } -/*- - *----------------------------------------------------------------------- - * DirLookup -- - * Find if the file with the given name exists in the given path. - * - * Results: - * The path to the file or NULL. This path is guaranteed to be in a - * different part of memory than name and so may be safely free'd. - * - * Side Effects: - * None. - *----------------------------------------------------------------------- - */ +/* 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(Path *p, const char *name MAKE_ATTR_UNUSED, const char *cp, - Boolean hasSlash MAKE_ATTR_UNUSED) +DirLookup(CachedDir *dir, const char *base) { char *file; /* the current filename to check */ - DIR_DEBUG1(" %s ...\n", p->name); + DIR_DEBUG1(" %s ...\n", dir->name); - if (Hash_FindEntry(&p->files, cp) == NULL) + if (HashTable_FindEntry(&dir->files, base) == NULL) return NULL; - file = str_concat3(p->name, "/", cp); + file = str_concat3(dir->name, "/", base); DIR_DEBUG1(" returning %s\n", file); - p->hits += 1; - hits += 1; + dir->hits++; + hits++; return file; } -/*- - *----------------------------------------------------------------------- - * DirLookupSubdir -- - * Find if the file with the given name exists in the given path. - * - * Results: - * The path to the file or NULL. This path is guaranteed to be in a - * different part of memory than name and so may be safely free'd. - * - * Side Effects: - * If the file is found, it is added in the modification times hash - * table. - *----------------------------------------------------------------------- - */ +/* 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(Path *p, const char *name) +DirLookupSubdir(CachedDir *dir, const char *name) { struct make_stat mst; - char *file; /* the current filename to check */ - - if (p != dot) { - file = str_concat3(p->name, "/", name); - } else { - /* - * Checking in dot -- DON'T put a leading ./ on the thing. - */ - file = bmake_strdup(name); - } + char *file = dir == dot ? bmake_strdup(name) + : str_concat3(dir->name, "/", name); DIR_DEBUG1("checking %s ...\n", file); if (cached_stat(file, &mst) == 0) { - nearmisses += 1; + nearmisses++; return file; } free(file); return NULL; } -/*- - *----------------------------------------------------------------------- - * DirLookupAbs -- - * Find if the file with the given name exists in the given path. - * - * Results: - * The path to the file, the empty string or NULL. If the file is - * the empty string, the search should be terminated. - * This path is guaranteed to be in a different part of memory - * than name and so may be safely free'd. - * - * Side Effects: - * None. - *----------------------------------------------------------------------- +/* 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(Path *p, const char *name, const char *cp) +DirLookupAbs(CachedDir *dir, const char *name, const char *cp) { - char *p1; /* pointer into p->name */ - const char *p2; /* pointer into name */ + const char *dnp; /* pointer into dir->name */ + const char *np; /* pointer into name */ - DIR_DEBUG1(" %s ...\n", p->name); + DIR_DEBUG1(" %s ...\n", dir->name); /* * If the file has a leading path component and that component @@ -990,88 +911,68 @@ DirLookupAbs(Path *p, const char *name, const char *cp) * 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 (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) { + for (dnp = dir->name, np = name; *dnp != '\0' && *dnp == *np; dnp++, np++) continue; - } - if (*p1 != '\0' || p2 != cp - 1) { + if (*dnp != '\0' || np != cp - 1) return NULL; - } - if (Hash_FindEntry(&p->files, cp) == NULL) { + if (HashTable_FindEntry(&dir->files, cp) == NULL) { DIR_DEBUG0(" must be here but isn't -- returning\n"); - /* Return empty string: terminates search */ - return bmake_strdup(""); + return bmake_strdup(""); /* to terminate the search */ } - p->hits += 1; - hits += 1; + dir->hits++; + hits++; DIR_DEBUG1(" returning %s\n", name); return bmake_strdup(name); } -/*- - *----------------------------------------------------------------------- - * DirFindDot -- - * Find the file given on "." or curdir - * - * Results: - * The path to the file or NULL. This path is guaranteed to be in a - * different part of memory than name and so may be safely free'd. - * - * Side Effects: - * Hit counts change - *----------------------------------------------------------------------- - */ +/* Find the file given on "." or curdir. + * Return the freshly allocated path to the file, or NULL. */ static char * -DirFindDot(Boolean hasSlash MAKE_ATTR_UNUSED, const char *name, const char *cp) +DirFindDot(const char *name, const char *base) { - if (Hash_FindEntry(&dot->files, cp) != NULL) { + if (HashTable_FindEntry(&dot->files, base) != NULL) { DIR_DEBUG0(" in '.'\n"); - hits += 1; - dot->hits += 1; + hits++; + dot->hits++; return bmake_strdup(name); } - if (cur && Hash_FindEntry(&cur->files, cp) != NULL) { + + if (cur != NULL && HashTable_FindEntry(&cur->files, base) != NULL) { DIR_DEBUG1(" in ${.CURDIR} = %s\n", cur->name); - hits += 1; - cur->hits += 1; - return str_concat3(cur->name, "/", cp); + hits++; + cur->hits++; + return str_concat3(cur->name, "/", base); } return NULL; } -/*- - *----------------------------------------------------------------------- - * Dir_FindFile -- - * Find the file with the given name along the given search path. +/* 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 Lst of directories to search + * path the directories to search, or NULL * * Results: - * The path to the file or NULL. This path is guaranteed to be in a - * different part of memory than name and so may be safely free'd. - * - * Side Effects: - * If the file is found in a directory which 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. - *----------------------------------------------------------------------- + * The freshly allocated path to the file, or NULL. */ char * -Dir_FindFile(const char *name, Lst path) +Dir_FindFile(const char *name, SearchPath *path) { - LstNode ln; /* a list element */ + SearchPathNode *ln; char *file; /* the current filename to check */ - Path *p; /* current path member */ - const char *cp; /* Terminal name of file */ - Boolean hasLastDot = FALSE; /* true we should search dot last */ + const char *base; /* Terminal name of file */ + Boolean hasLastDot = FALSE; /* true if we should search dot last */ Boolean hasSlash; /* true if 'name' contains a / */ struct make_stat mst; /* Buffer for stat, if necessary */ const char *trailing_dot = "."; @@ -1080,27 +981,26 @@ Dir_FindFile(const char *name, Lst path) * Find the final component of the name and note whether it has a * slash in it (the name, I mean) */ - cp = strrchr(name, '/'); - if (cp) { + base = strrchr(name, '/'); + if (base) { hasSlash = TRUE; - cp += 1; + base++; } else { hasSlash = FALSE; - cp = name; + base = name; } DIR_DEBUG1("Searching for %s ...", name); if (path == NULL) { DIR_DEBUG0("couldn't open path, file not found\n"); - misses += 1; + misses++; return NULL; } - Lst_Open(path); - if ((ln = Lst_First(path)) != NULL) { - p = LstNode_Datum(ln); - if (p == dotLast) { + if ((ln = path->first) != NULL) { + CachedDir *dir = ln->datum; + if (dir == dotLast) { hasLastDot = TRUE; DIR_DEBUG0("[dot last]..."); } @@ -1112,7 +1012,7 @@ Dir_FindFile(const char *name, Lst path) * directory component is exactly `./', consult the cached contents * of each of the directories on the search path. */ - if (!hasSlash || (cp - name == 2 && *name == '.')) { + if (!hasSlash || (base - name == 2 && *name == '.')) { /* * We look through all the directories on the path seeking one which * contains the final component of the given name. If such a beast @@ -1127,27 +1027,20 @@ Dir_FindFile(const char *name, Lst path) * This is so there are no conflicts between what the user * specifies (fish.c) and what pmake finds (./fish.c). */ - if (!hasLastDot && (file = DirFindDot(hasSlash, name, cp)) != NULL) { - Lst_Close(path); + if (!hasLastDot && (file = DirFindDot(name, base)) != NULL) return file; - } - while ((ln = Lst_Next(path)) != NULL) { - p = LstNode_Datum(ln); - if (p == dotLast) + for (; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (dir == dotLast) continue; - if ((file = DirLookup(p, name, cp, hasSlash)) != NULL) { - Lst_Close(path); + if ((file = DirLookup(dir, base)) != NULL) return file; - } } - if (hasLastDot && (file = DirFindDot(hasSlash, name, cp)) != NULL) { - Lst_Close(path); + if (hasLastDot && (file = DirFindDot(name, base)) != NULL) return file; - } } - Lst_Close(path); /* * We didn't find the file on any directory in the search path. @@ -1165,13 +1058,13 @@ Dir_FindFile(const char *name, Lst path) */ if (!hasSlash) { DIR_DEBUG0(" failed.\n"); - misses += 1; + misses++; return NULL; } - if (*cp == '\0') { + if (*base == '\0') { /* we were given a trailing "/" */ - cp = trailing_dot; + base = trailing_dot; } if (name[0] != '/') { @@ -1189,22 +1082,18 @@ Dir_FindFile(const char *name, Lst path) return file; } - Lst_Open(path); - while ((ln = Lst_Next(path)) != NULL) { - p = LstNode_Datum(ln); - if (p == dotLast) + for (ln = path->first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (dir == dotLast) continue; - if (p == dot) { + if (dir == dot) { if (checkedDot) continue; checkedDot = TRUE; } - if ((file = DirLookupSubdir(p, name)) != NULL) { - Lst_Close(path); + if ((file = DirLookupSubdir(dir, name)) != NULL) return file; - } } - Lst_Close(path); if (hasLastDot) { if (dot && !checkedDot) { @@ -1239,7 +1128,7 @@ Dir_FindFile(const char *name, Lst path) DIR_DEBUG0(" Trying exact path matches...\n"); if (!hasLastDot && cur && - ((file = DirLookupAbs(cur, name, cp)) != NULL)) { + ((file = DirLookupAbs(cur, name, base)) != NULL)) { if (file[0] == '\0') { free(file); return NULL; @@ -1247,13 +1136,11 @@ Dir_FindFile(const char *name, Lst path) return file; } - Lst_Open(path); - while ((ln = Lst_Next(path)) != NULL) { - p = LstNode_Datum(ln); - if (p == dotLast) + for (ln = path->first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (dir == dotLast) continue; - if ((file = DirLookupAbs(p, name, cp)) != NULL) { - Lst_Close(path); + if ((file = DirLookupAbs(dir, name, base)) != NULL) { if (file[0] == '\0') { free(file); return NULL; @@ -1261,10 +1148,9 @@ Dir_FindFile(const char *name, Lst path) return file; } } - Lst_Close(path); if (hasLastDot && cur && - ((file = DirLookupAbs(cur, name, cp)) != NULL)) { + ((file = DirLookupAbs(cur, name, base)) != NULL)) { if (file[0] == '\0') { free(file); return NULL; @@ -1291,23 +1177,23 @@ Dir_FindFile(const char *name, Lst path) * b/c we added it here. This is not good... */ #ifdef notdef - if (cp == traling_dot) { - cp = strrchr(name, '/'); - cp += 1; + if (base == trailing_dot) { + base = strrchr(name, '/'); + base++; } - cp[-1] = '\0'; + base[-1] = '\0'; (void)Dir_AddDir(path, name); - cp[-1] = '/'; + base[-1] = '/'; - bigmisses += 1; + bigmisses++; ln = Lst_Last(path); if (ln == NULL) { return NULL; } else { - p = LstNode_Datum(ln); + dir = LstNode_Datum(ln); } - if (Hash_FindEntry(&p->files, cp) != NULL) { + if (Hash_FindEntry(&dir->files, base) != NULL) { return bmake_strdup(name); } else { return NULL; @@ -1315,7 +1201,7 @@ Dir_FindFile(const char *name, Lst path) #else /* !notdef */ DIR_DEBUG1(" Looking for \"%s\" ...\n", name); - bigmisses += 1; + bigmisses++; if (cached_stat(name, &mst) == 0) { return bmake_strdup(name); } @@ -1326,43 +1212,32 @@ Dir_FindFile(const char *name, Lst path) } -/*- - *----------------------------------------------------------------------- - * Dir_FindHereOrAbove -- - * search for a path starting at a given directory and then working - * our way up towards the root. +/* 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 path we are looking for - * result the result of a successful search is placed here - * result_len the length of the result buffer - * (typically MAXPATHLEN + 1) + * search_path the relative path we are looking for * * Results: - * 0 on failure, 1 on success [in which case the found path is put - * in the result buffer]. - * - * Side Effects: - *----------------------------------------------------------------------- + * The found path, or NULL. */ -Boolean -Dir_FindHereOrAbove(const char *here, const char *search_path, - char *result, int result_len) +char * +Dir_FindHereOrAbove(const char *here, const char *search_path) { struct make_stat mst; - char dirbase[MAXPATHLEN + 1], *dirbase_end; - char try[MAXPATHLEN + 1], *try_end; + char *dirbase, *dirbase_end; + char *try, *try_end; /* copy out our starting point */ - snprintf(dirbase, sizeof(dirbase), "%s", here); + dirbase = bmake_strdup(here); dirbase_end = dirbase + strlen(dirbase); /* loop until we determine a result */ - while (TRUE) { + for (;;) { /* try and stat(2) it ... */ - snprintf(try, sizeof(try), "%s/%s", dirbase, search_path); + try = str_concat3(dirbase, "/", search_path); if (cached_stat(try, &mst) != -1) { /* * success! if we found a file, chop off @@ -1376,9 +1251,10 @@ Dir_FindHereOrAbove(const char *here, const char *search_path, *try_end = '\0'; /* chop! */ } - snprintf(result, result_len, "%s", try); - return TRUE; + free(dirbase); + return try; } + free(try); /* * nope, we didn't find it. if we used up dirbase we've @@ -1393,10 +1269,10 @@ Dir_FindHereOrAbove(const char *here, const char *search_path, while (dirbase_end > dirbase && *dirbase_end != '/') dirbase_end--; *dirbase_end = '\0'; /* chop! */ + } - } /* while (TRUE) */ - - return FALSE; + free(dirbase); + return NULL; } /*- @@ -1417,7 +1293,7 @@ Dir_FindHereOrAbove(const char *here, const char *search_path, * found one for it, the full name is placed in the path slot. *----------------------------------------------------------------------- */ -int +time_t Dir_MTime(GNode *gn, Boolean recheck) { char *fullName; /* the full pathname of name */ @@ -1482,72 +1358,63 @@ Dir_MTime(GNode *gn, Boolean recheck) } } - if (fullName && gn->path == NULL) { + if (fullName != NULL && gn->path == NULL) gn->path = fullName; - } gn->mtime = mst.mst_mtime; return gn->mtime; } -/*- - *----------------------------------------------------------------------- - * Dir_AddDir -- - * Add the given name to the end of the given path. The order of - * the arguments is backwards so ParseDoDependency can do a - * Lst_ForEach of its list of paths... - * - * Input: - * path the path to which the directory should be - * added - * XXX: Why would this ever be NULL, and what does - * that mean? - * name the name of the directory to add +/* Read the list of filenames in the directory and store the result + * in openDirectories. * - * Results: - * none + * If a path is given, append the directory to that path. * - * Side Effects: - * A structure is added to the list and the directory is - * read and hashed. - *----------------------------------------------------------------------- + * Input: + * path The path to which the directory should be + * added, or NULL to only add the directory to + * openDirectories + * name The name of the directory to add. + * The name is not normalized in any way. */ -Path * -Dir_AddDir(Lst path, const char *name) +CachedDir * +Dir_AddDir(SearchPath *path, const char *name) { - LstNode ln = NULL; /* node in case Path structure is found */ - Path *p = NULL; /* pointer to new Path structure */ - DIR *d; /* for reading directory */ - struct dirent *dp; /* entry in directory */ + CachedDir *dir = NULL; /* the added directory */ + DIR *d; + struct dirent *dp; if (path != NULL && strcmp(name, ".DOTLAST") == 0) { - ln = Lst_Find(path, DirFindName, name); - if (ln != NULL) - return LstNode_Datum(ln); + SearchPathNode *ln; + + for (ln = path->first; ln != NULL; ln = ln->next) { + CachedDir *pathDir = ln->datum; + if (strcmp(pathDir->name, name) == 0) + return pathDir; + } dotLast->refCount++; Lst_Prepend(path, dotLast); } if (path != NULL) - ln = Lst_Find(openDirectories, DirFindName, name); - if (ln != NULL) { - p = LstNode_Datum(ln); - if (Lst_FindDatum(path, p) == NULL) { - p->refCount += 1; - Lst_Append(path, p); + dir = OpenDirs_Find(&openDirs, name); + if (dir != NULL) { + if (Lst_FindDatum(path, dir) == NULL) { + dir->refCount++; + Lst_Append(path, dir); } - return p; + return dir; } DIR_DEBUG1("Caching %s ...", name); if ((d = opendir(name)) != NULL) { - p = bmake_malloc(sizeof(Path)); - p->name = bmake_strdup(name); - p->hits = 0; - p->refCount = 1; - Hash_InitTable(&p->files, -1); + dir = bmake_malloc(sizeof(CachedDir)); + dir->name = bmake_strdup(name); + dir->hits = 0; + dir->refCount = 1; + HashTable_Init(&dir->files); while ((dp = readdir(d)) != NULL) { #if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */ @@ -1560,33 +1427,30 @@ Dir_AddDir(Lst path, const char *name) continue; } #endif /* sun && d_ino */ - (void)Hash_CreateEntry(&p->files, dp->d_name, NULL); + (void)HashTable_CreateEntry(&dir->files, dp->d_name, NULL); } (void)closedir(d); - Lst_Append(openDirectories, p); + OpenDirs_Add(&openDirs, dir); if (path != NULL) - Lst_Append(path, p); + Lst_Append(path, dir); } DIR_DEBUG0("done\n"); - return p; + return dir; } -/*- - *----------------------------------------------------------------------- - * Dir_CopyDir -- - * Callback function for duplicating a search path via Lst_Copy. - * Ups the reference count for the directory. - * - * Results: - * Returns the Path it was given. - *----------------------------------------------------------------------- - */ -void * -Dir_CopyDir(void *p) +/* Return a copy of dirSearchPath, incrementing the reference counts for + * the contained directories. */ +SearchPath * +Dir_CopyDirSearchPath(void) { - ((Path *)p)->refCount += 1; - - return p; + SearchPath *path = Lst_New(); + SearchPathNode *ln; + for (ln = dirSearchPath->first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + dir->refCount++; + Lst_Append(path, dir); + } + return path; } /*- @@ -1611,119 +1475,70 @@ Dir_CopyDir(void *p) *----------------------------------------------------------------------- */ char * -Dir_MakeFlags(const char *flag, Lst path) +Dir_MakeFlags(const char *flag, SearchPath *path) { Buffer buf; - LstNode ln; /* the node of the current directory */ + SearchPathNode *ln; Buf_Init(&buf, 0); if (path != NULL) { - Lst_Open(path); - while ((ln = Lst_Next(path)) != NULL) { - Path *p = LstNode_Datum(ln); + for (ln = path->first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; Buf_AddStr(&buf, " "); Buf_AddStr(&buf, flag); - Buf_AddStr(&buf, p->name); + Buf_AddStr(&buf, dir->name); } - Lst_Close(path); } return Buf_Destroy(&buf, FALSE); } -/*- - *----------------------------------------------------------------------- - * Dir_Destroy -- - * Nuke a directory descriptor, if possible. Callback procedure - * for the suffixes module when destroying a search path. +/* Nuke a directory descriptor, if possible. Callback procedure for the + * suffixes module when destroying a search path. * * Input: - * pp The directory descriptor to nuke - * - * Results: - * None. - * - * Side Effects: - * If no other path references this directory (refCount == 0), - * the Path and all its data are freed. - * - *----------------------------------------------------------------------- + * dirp The directory descriptor to nuke */ void -Dir_Destroy(void *pp) +Dir_Destroy(void *dirp) { - Path *p = (Path *)pp; - p->refCount -= 1; - - if (p->refCount == 0) { - LstNode ln; + CachedDir *dir = dirp; + dir->refCount--; - ln = Lst_FindDatum(openDirectories, p); - Lst_Remove(openDirectories, ln); + if (dir->refCount == 0) { + OpenDirs_Remove(&openDirs, dir->name); - Hash_DeleteTable(&p->files); - free(p->name); - free(p); + HashTable_Done(&dir->files); + free(dir->name); + free(dir); } } -/*- - *----------------------------------------------------------------------- - * Dir_ClearPath -- - * Clear out all elements of the given search path. This is different - * from destroying the list, notice. - * - * Input: - * path Path to clear - * - * Results: - * None. - * - * Side Effects: - * The path is set to the empty list. - * - *----------------------------------------------------------------------- - */ +/* Clear out all elements from the given search path. + * The path is set to the empty list but is not destroyed. */ void -Dir_ClearPath(Lst path) +Dir_ClearPath(SearchPath *path) { while (!Lst_IsEmpty(path)) { - Path *p = Lst_Dequeue(path); - Dir_Destroy(p); + CachedDir *dir = Lst_Dequeue(path); + Dir_Destroy(dir); } } -/*- - *----------------------------------------------------------------------- - * Dir_Concat -- - * Concatenate two paths, adding the second to the end of the first. - * Makes sure to avoid duplicates. - * - * Input: - * path1 Dest - * path2 Source - * - * Results: - * None - * - * Side Effects: - * Reference counts for added dirs are upped. - * - *----------------------------------------------------------------------- - */ +/* Concatenate two paths, adding the second to the end of the first, + * skipping duplicates. */ void -Dir_Concat(Lst path1, Lst path2) +Dir_Concat(SearchPath *dst, SearchPath *src) { - LstNode ln; - Path *p; - - for (ln = Lst_First(path2); ln != NULL; ln = LstNode_Next(ln)) { - p = LstNode_Datum(ln); - if (Lst_FindDatum(path1, p) == NULL) { - p->refCount += 1; - Lst_Append(path1, p); + SearchPathNode *ln; + + for (ln = src->first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (Lst_FindDatum(dst, dir) == NULL) { + dir->refCount++; + Lst_Append(dst, dir); } } } @@ -1738,33 +1553,27 @@ percentage(int num, int den) void Dir_PrintDirectories(void) { - LstNode ln; - - fprintf(debug_file, "#*** Directory Cache:\n"); - fprintf(debug_file, - "# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n", - hits, misses, nearmisses, bigmisses, - percentage(hits, hits + bigmisses + nearmisses)); - fprintf(debug_file, "# %-20s referenced\thits\n", "directory"); - - Lst_Open(openDirectories); - while ((ln = Lst_Next(openDirectories)) != NULL) { - Path *p = LstNode_Datum(ln); - fprintf(debug_file, "# %-20s %10d\t%4d\n", p->name, p->refCount, - p->hits); + CachedDirListNode *ln; + + debug_printf("#*** Directory Cache:\n"); + debug_printf("# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n", + hits, misses, nearmisses, bigmisses, + percentage(hits, hits + bigmisses + nearmisses)); + debug_printf("# %-20s referenced\thits\n", "directory"); + + for (ln = openDirs.list->first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + debug_printf("# %-20s %10d\t%4d\n", dir->name, dir->refCount, + dir->hits); } - Lst_Close(openDirectories); -} - -static int -DirPrintDir(void *p, void *dummy MAKE_ATTR_UNUSED) -{ - fprintf(debug_file, "%s ", ((Path *)p)->name); - return 0; } void -Dir_PrintPath(Lst path) +Dir_PrintPath(SearchPath *path) { - Lst_ForEach(path, DirPrintDir, NULL); + SearchPathNode *node; + for (node = path->first; node != NULL; node = node->next) { + const CachedDir *dir = node->datum; + debug_printf("%s ", dir->name); + } } @@ -1,4 +1,4 @@ -/* $NetBSD: dir.h,v 1.23 2020/09/02 04:08:54 rillig Exp $ */ +/* $NetBSD: dir.h,v 1.32 2020/10/25 10:00:20 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -75,20 +75,21 @@ #ifndef MAKE_DIR_H #define MAKE_DIR_H -/* A cache of a directory, remembering all the files that exist in that - * directory. */ -typedef struct { - char *name; /* Name of directory */ - int refCount; /* Number of paths with this directory */ - int hits; /* the number of times a file in this +/* A cache for the filenames in a directory. */ +typedef struct CachedDir { + char *name; /* Name of directory, either absolute or + * relative to the current directory. + * The name is not normalized in any way, + * that is, "." and "./." are different. + * + * Not sure what happens when .CURDIR is + * assigned a new value; see Parse_DoVar. */ + int refCount; /* Number of SearchPaths with this directory */ + int hits; /* The number of times a file in this * directory has been found */ - Hash_Table files; /* Hash set of files in directory */ -} Path; - -struct make_stat { - time_t mst_mtime; - mode_t mst_mode; -}; + HashTable files; /* Hash set of files in directory; + * all values are NULL. */ +} CachedDir; void Dir_Init(void); void Dir_InitDir(const char *); @@ -97,18 +98,24 @@ void Dir_InitDot(void); void Dir_End(void); void Dir_SetPATH(void); Boolean Dir_HasWildcards(const char *); -void Dir_Expand(const char *, Lst, Lst); -char *Dir_FindFile(const char *, Lst); -Boolean Dir_FindHereOrAbove(const char *, const char *, char *, int); -int Dir_MTime(GNode *, Boolean); -Path *Dir_AddDir(Lst, const char *); -char *Dir_MakeFlags(const char *, Lst); -void Dir_ClearPath(Lst); -void Dir_Concat(Lst, Lst); +void Dir_Expand(const char *, SearchPath *, StringList *); +char *Dir_FindFile(const char *, SearchPath *); +char *Dir_FindHereOrAbove(const char *, const char *); +time_t Dir_MTime(GNode *, Boolean); +CachedDir *Dir_AddDir(SearchPath *, const char *); +char *Dir_MakeFlags(const char *, SearchPath *); +void Dir_ClearPath(SearchPath *); +void Dir_Concat(SearchPath *, SearchPath *); void Dir_PrintDirectories(void); -void Dir_PrintPath(Lst); +void Dir_PrintPath(SearchPath *); void Dir_Destroy(void *); -void *Dir_CopyDir(void *); +SearchPath *Dir_CopyDirSearchPath(void); + +/* Stripped-down variant of struct stat. */ +struct make_stat { + time_t mst_mtime; + mode_t mst_mode; +}; int cached_lstat(const char *, struct make_stat *); int cached_stat(const char *, struct make_stat *); @@ -1,4 +1,4 @@ -/* $NetBSD: enum.c,v 1.6 2020/09/01 20:34:51 rillig Exp $ */ +/* $NetBSD: enum.c,v 1.12 2020/10/05 19:27:47 rillig Exp $ */ /* Copyright (c) 2020 Roland Illig <rillig@NetBSD.org> @@ -27,20 +27,9 @@ POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: enum.c,v 1.6 2020/09/01 20:34:51 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -__RCSID("$NetBSD: enum.c,v 1.6 2020/09/01 20:34:51 rillig Exp $"); -#endif -#endif +#include "make.h" -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#include "enum.h" +MAKE_RCSID("$NetBSD: enum.c,v 1.12 2020/10/05 19:27:47 rillig Exp $"); /* Convert a bitset into a string representation, showing the names of the * individual bits. @@ -59,7 +48,7 @@ Enum_FlagsToString(char *buf, size_t buf_size, size_t name_len; if ((value & spec->es_value) != spec->es_value) - continue; + continue; value &= ~spec->es_value; assert(buf_size >= sep_len + 1); @@ -93,8 +82,8 @@ const char * Enum_ValueToString(int value, const EnumToStringSpec *spec) { for (; spec->es_name[0] != '\0'; spec++) { - if (value == spec->es_value) - return spec->es_name; + if (value == spec->es_value) + return spec->es_name; } abort(/* unknown enum value */); } @@ -1,4 +1,4 @@ -/* $NetBSD: enum.h,v 1.9 2020/09/01 20:34:51 rillig Exp $ */ +/* $NetBSD: enum.h,v 1.12 2020/09/25 15:54:50 rillig Exp $ */ /* Copyright (c) 2020 Roland Illig <rillig@NetBSD.org> @@ -34,7 +34,7 @@ #include <stddef.h> -typedef struct { +typedef struct EnumToStringSpec { int es_value; const char *es_name; } EnumToStringSpec; @@ -108,15 +108,37 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *); enum { typnam ## _ ## ToStringSize = sizeof joined } /* 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__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 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_2(v1, v2), \ - ENUM__JOIN_STR_1(v3))) + 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. */ @@ -156,8 +178,8 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *); 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, \ + 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), \ @@ -179,8 +201,8 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *); 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, \ + 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)), \ @@ -188,6 +210,6 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *); 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))) + v25, v26, v27, v28, v29, v30, v31, v32))) #endif diff --git a/filemon/filemon.h b/filemon/filemon.h index 5a1231d935ec..fcf37a3ab54b 100644 --- a/filemon/filemon.h +++ b/filemon/filemon.h @@ -1,4 +1,4 @@ -/* $NetBSD: filemon.h,v 1.2 2020/01/22 22:10:36 sjg Exp $ */ +/* $NetBSD: filemon.h,v 1.3 2020/10/18 11:49:47 rillig Exp $ */ /*- * Copyright (c) 2019 The NetBSD Foundation, Inc. @@ -29,8 +29,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef FILEMON_H_ -#define FILEMON_H_ +#ifndef MAKE_FILEMON_H +#define MAKE_FILEMON_H #include <sys/types.h> @@ -50,4 +50,4 @@ int filemon_setpid_child(const struct filemon *, pid_t); int filemon_readfd(const struct filemon *); int filemon_process(struct filemon *); -#endif /* FILEMON_H_ */ +#endif /* MAKE_FILEMON_H */ diff --git a/filemon/filemon_ktrace.c b/filemon/filemon_ktrace.c index 0a532d68f4f2..4d2a5450c093 100644 --- a/filemon/filemon_ktrace.c +++ b/filemon/filemon_ktrace.c @@ -1,4 +1,4 @@ -/* $NetBSD: filemon_ktrace.c,v 1.2 2020/01/19 20:22:57 riastradh Exp $ */ +/* $NetBSD: filemon_ktrace.c,v 1.3 2020/10/18 11:54:43 rillig Exp $ */ /*- * Copyright (c) 2019 The NetBSD Foundation, Inc. @@ -478,7 +478,7 @@ filemon_dispatch(struct filemon *F) */ /* XXX What to do if syscall code doesn't match? */ if (S->i == S->npath && S->syscode == ret->ktr_code) - (*S->show)(F, S, ret); + S->show(F, S, ret); /* Free the state now that it is no longer active. */ for (i = 0; i < S->i; i++) @@ -771,7 +771,7 @@ filemon_sys_exit(struct filemon *F, const struct filemon_key *key, const struct ktr_syscall *call) { const register_t *args = (const void *)&call[1]; - int status = args[0]; + int status = (int)args[0]; if (F->out) { fprintf(F->out, "X %jd %d\n", (intmax_t)key->pid, status); @@ -806,7 +806,7 @@ filemon_sys_open(struct filemon *F, const struct filemon_key *key, if (call->ktr_argsize < 2) return NULL; - flags = args[1]; + flags = (int)args[1]; if ((flags & O_RDWR) == O_RDWR) return syscall_enter(F, key, call, 1, &show_open_readwrite); @@ -827,8 +827,8 @@ filemon_sys_openat(struct filemon *F, const struct filemon_key *key, if (call->ktr_argsize < 3) return NULL; - fd = args[0]; - flags = args[2]; + fd = (int)args[0]; + flags = (int)args[2]; if (fd == AT_CWD) { if ((flags & O_RDWR) == O_RDWR) @@ -1,4 +1,4 @@ -/* $NetBSD: for.c,v 1.67 2020/08/30 19:56:02 rillig Exp $ */ +/* $NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $ */ /* * Copyright (c) 1992, The Regents of the University of California. @@ -29,88 +29,136 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: for.c,v 1.67 2020/08/30 19:56:02 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)for.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: for.c,v 1.67 2020/08/30 19:56:02 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - /*- - * for.c -- - * Functions to handle loops in a makefile. + * Handling of .for/.endfor loops in a makefile. * - * Interface: - * For_Eval Evaluate the loop in the passed line. - * For_Run Run accumulated loop + * For loops are of the form: * - */ - -#include "make.h" -#include "strlist.h" - -#define FOR_SUB_ESCAPE_CHAR 1 -#define FOR_SUB_ESCAPE_BRACE 2 -#define FOR_SUB_ESCAPE_PAREN 4 - -/* - * For statements are of the form: - * - * .for <variable> in <varlist> + * .for <varname...> in <value...> * ... * .endfor * - * The trick is to look for the matching end inside for for loop - * To do that, we count the current nesting level of the for loops. - * and the .endfor statements, accumulating all the statements between - * the initial .for loop and the matching .endfor; - * then we evaluate the for loop for each variable in the varlist. + * When a .for line is parsed, all following lines are accumulated into a + * buffer, up to but excluding the corresponding .endfor line. To find the + * corresponding .endfor, the number of nested .for and .endfor directives + * are counted. + * + * During parsing, any nested .for loops are just passed through; they get + * handled recursively in For_Eval when the enclosing .for loop is evaluated + * in For_Run. * - * Note that any nested fors are just passed through; they get handled - * recursively in For_Eval when we're expanding the enclosing for in - * For_Run. + * When the .for loop has been parsed completely, the variable expressions + * for the iteration variables are replaced with expressions of the form + * ${:Uvalue}, and then this modified body is "included" as a special file. + * + * Interface: + * For_Eval Evaluate the loop in the passed line. + * + * For_Run Run accumulated loop */ +#include "make.h" + +/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ +MAKE_RCSID("$NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $"); + +/* The .for loop substitutes the items as ${:U<value>...}, which means + * that characters that break this syntax must be backslash-escaped. */ +typedef enum ForEscapes { + FOR_SUB_ESCAPE_CHAR = 0x0001, + FOR_SUB_ESCAPE_BRACE = 0x0002, + FOR_SUB_ESCAPE_PAREN = 0x0004 +} ForEscapes; + static int forLevel = 0; /* Nesting level */ +/* One of the variables to the left of the "in" in a .for loop. */ +typedef struct ForVar { + char *name; + size_t len; +} ForVar; + /* * State of a for loop. */ -typedef struct { - Buffer buf; /* Body of loop */ - strlist_t vars; /* Iteration variables */ - strlist_t items; /* Substitution items */ - char *parse_buf; - int short_var; - int sub_next; +typedef struct For { + Buffer body; /* Unexpanded body of the loop */ + Vector /* of ForVar */ vars; /* Iteration variables */ + Words items; /* Substitution items */ + Buffer curBody; /* Expanded body of the current iteration */ + /* Is any of the names 1 character long? If so, when the variable values + * are substituted, the parser must handle $V expressions as well, not + * only ${V} and $(V). */ + Boolean short_var; + unsigned int sub_next; /* Where to continue iterating */ } For; static For *accumFor; /* Loop being accumulated */ +static void +ForAddVar(For *f, const char *name, size_t len) +{ + ForVar *var = Vector_Push(&f->vars); + var->name = bmake_strldup(name, len); + var->len = len; +} static void -For_Free(For *arg) +For_Free(For *f) { - Buf_Destroy(&arg->buf, TRUE); - strlist_clean(&arg->vars); - strlist_clean(&arg->items); - free(arg->parse_buf); + Buf_Destroy(&f->body, TRUE); - free(arg); + while (f->vars.len > 0) { + ForVar *var = Vector_Pop(&f->vars); + free(var->name); + } + Vector_Done(&f->vars); + + Words_Free(f->items); + Buf_Destroy(&f->curBody, TRUE); + + free(f); } -/*- - *----------------------------------------------------------------------- - * For_Eval -- - * Evaluate the for loop in the passed line. The line - * looks like this: - * .for <variable> in <varlist> +static ForEscapes +GetEscapes(const char *word) +{ + const char *p; + ForEscapes escapes = 0; + + for (p = word; *p != '\0'; p++) { + switch (*p) { + case ':': + case '$': + case '\\': + escapes |= FOR_SUB_ESCAPE_CHAR; + break; + case ')': + escapes |= FOR_SUB_ESCAPE_PAREN; + break; + case '}': + escapes |= FOR_SUB_ESCAPE_BRACE; + break; + } + } + return escapes; +} + +static Boolean +IsFor(const char *p) +{ + return p[0] == 'f' && p[1] == 'o' && p[2] == 'r' && ch_isspace(p[3]); +} + +static Boolean +IsEndfor(const char *p) +{ + return p[0] == 'e' && strncmp(p, "endfor", 6) == 0 && + (p[6] == '\0' || ch_isspace(p[6])); +} + +/* Evaluate the for loop in the passed line. The line looks like this: + * .for <varname...> in <value...> * * Input: * line Line to parse @@ -119,178 +167,130 @@ For_Free(For *arg) * 0: Not a .for statement, parse the line * 1: We found a for loop * -1: A .for statement with a bad syntax error, discard. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- */ int -For_Eval(char *line) +For_Eval(const char *line) { - For *new_for; - char *ptr = line, *sub; - size_t len; - int escapes; - unsigned char ch; - Words words; + For *f; + const char *p; - /* Skip the '.' and any following whitespace */ - for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++) - continue; + p = line + 1; /* skip the '.' */ + cpp_skip_whitespace(&p); - /* - * If we are not in a for loop quickly determine if the statement is - * a for. - */ - if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' || - !isspace((unsigned char)ptr[3])) { - if (ptr[0] == 'e' && strncmp(ptr + 1, "ndfor", 5) == 0) { + if (!IsFor(p)) { + if (IsEndfor(p)) { Parse_Error(PARSE_FATAL, "for-less endfor"); return -1; } return 0; } - ptr += 3; + p += 3; /* * we found a for loop, and now we are going to parse it. */ - new_for = bmake_malloc(sizeof *new_for); - memset(new_for, 0, sizeof *new_for); + f = bmake_malloc(sizeof *f); + Buf_Init(&f->body, 0); + Vector_Init(&f->vars, sizeof(ForVar)); + f->items.words = NULL; + f->items.freeIt = NULL; + Buf_Init(&f->curBody, 0); + f->short_var = FALSE; + f->sub_next = 0; /* Grab the variables. Terminate on "in". */ - for (;; ptr += len) { - while (*ptr && isspace((unsigned char)*ptr)) - ptr++; - if (*ptr == '\0') { + for (;;) { + size_t len; + + cpp_skip_whitespace(&p); + if (*p == '\0') { Parse_Error(PARSE_FATAL, "missing `in' in for"); - For_Free(new_for); + For_Free(f); return -1; } - for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++) + + /* 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 && ptr[0] == 'i' && ptr[1] == 'n') { - ptr += 2; + + if (len == 2 && p[0] == 'i' && p[1] == 'n') { + p += 2; break; } if (len == 1) - new_for->short_var = 1; - strlist_add_str(&new_for->vars, bmake_strldup(ptr, len), len); + f->short_var = TRUE; + + ForAddVar(f, p, len); + p += len; } - if (strlist_num(&new_for->vars) == 0) { + if (f->vars.len == 0) { Parse_Error(PARSE_FATAL, "no iteration variables in for"); - For_Free(new_for); + For_Free(f); return -1; } - while (*ptr && isspace((unsigned char)*ptr)) - ptr++; - - /* - * Make a list with the remaining words - * The values are substituted as ${:U<value>...} so we must \ escape - * characters that break that syntax. - * Variables are fully expanded - so it is safe for escape $. - * We can't do the escapes here - because we don't know whether - * we are substuting into ${...} or $(...). - */ - sub = Var_Subst(ptr, VAR_GLOBAL, VARE_WANTRES); - - /* - * Split into words allowing for quoted strings. - */ - words = Str_Words(sub, FALSE); - - free(sub); + cpp_skip_whitespace(&p); { - size_t n; - - for (n = 0; n < words.len; n++) { - ptr = words.words[n]; - if (!*ptr) - continue; - escapes = 0; - while ((ch = *ptr++)) { - switch (ch) { - case ':': - case '$': - case '\\': - escapes |= FOR_SUB_ESCAPE_CHAR; - break; - case ')': - escapes |= FOR_SUB_ESCAPE_PAREN; - break; - case /*{*/ '}': - escapes |= FOR_SUB_ESCAPE_BRACE; - break; - } - } - /* - * We have to dup words[n] to maintain the semantics of - * strlist. - */ - strlist_add_str(&new_for->items, bmake_strdup(words.words[n]), - escapes); - } + char *items; + (void)Var_Subst(p, VAR_GLOBAL, VARE_WANTRES, &items); + /* TODO: handle errors */ + f->items = Str_Words(items, FALSE); + free(items); + + if (f->items.len == 1 && f->items.words[0][0] == '\0') + f->items.len = 0; /* .for var in ${:U} */ + } - Words_Free(words); + { + size_t nitems, nvars; - if ((len = strlist_num(&new_for->items)) > 0 && - len % (n = strlist_num(&new_for->vars))) { + if ((nitems = f->items.len) > 0 && nitems % (nvars = f->vars.len)) { Parse_Error(PARSE_FATAL, "Wrong number of words (%zu) in .for substitution list" - " with %zu vars", len, n); + " with %zu variables", nitems, nvars); /* * Return 'success' so that the body of the .for loop is * accumulated. * Remove all items so that the loop doesn't iterate. */ - strlist_clean(&new_for->items); + f->items.len = 0; } } - Buf_Init(&new_for->buf, 0); - accumFor = new_for; + accumFor = f; forLevel = 1; return 1; } /* * Add another line to a .for loop. - * Returns 0 when the matching .endfor is reached. + * Returns FALSE when the matching .endfor is reached. */ - -int -For_Accum(char *line) +Boolean +For_Accum(const char *line) { - char *ptr = line; + const char *ptr = line; if (*ptr == '.') { + ptr++; + cpp_skip_whitespace(&ptr); - for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++) - continue; - - if (strncmp(ptr, "endfor", 6) == 0 && - (isspace((unsigned char)ptr[6]) || !ptr[6])) { - if (DEBUG(FOR)) - (void)fprintf(debug_file, "For: end for %d\n", forLevel); + if (IsEndfor(ptr)) { + DEBUG1(FOR, "For: end for %d\n", forLevel); if (--forLevel <= 0) - return 0; - } else if (strncmp(ptr, "for", 3) == 0 && - isspace((unsigned char)ptr[3])) { + return FALSE; + } else if (IsFor(ptr)) { forLevel++; - if (DEBUG(FOR)) - (void)fprintf(debug_file, "For: new loop %d\n", forLevel); + DEBUG1(FOR, "For: new loop %d\n", forLevel); } } - Buf_AddStr(&accumFor->buf, line); - Buf_AddByte(&accumFor->buf, '\n'); - return 1; + Buf_AddStr(&accumFor->body, line); + Buf_AddByte(&accumFor->body, '\n'); + return TRUE; } @@ -326,23 +326,25 @@ for_var_len(const char *var) return 0; } +/* While expanding the body of a .for loop, write the item in the ${:U...} + * expression, escaping characters as needed. See ApplyModifier_Defined. */ static void -for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech) +Buf_AddEscaped(Buffer *cmds, const char *item, char ech) { + ForEscapes escapes = GetEscapes(item); char ch; - const char *item = strlist_str(items, item_no); - /* If there were no escapes, or the only escape is the other variable * terminator, then just substitute the full string */ - if (!(strlist_info(items, item_no) & - (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) { + if (!(escapes & (ech == ')' ? ~(unsigned)FOR_SUB_ESCAPE_BRACE + : ~(unsigned)FOR_SUB_ESCAPE_PAREN))) { Buf_AddStr(cmds, item); return; } - /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */ - while ((ch = *item++) != 0) { + /* Escape ':', '$', '\\' and 'ech' - these will be removed later by + * :U processing, see ApplyModifier_Defined. */ + while ((ch = *item++) != '\0') { if (ch == '$') { size_t len = for_var_len(item); if (len != 0) { @@ -357,111 +359,141 @@ for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech) } } +/* While expanding the body of a .for loop, replace expressions like + * ${i}, ${i:...}, $(i) or $(i:...) with their ${:U...} expansion. */ +static void +SubstVarLong(For *f, const char **pp, const char **inout_mark, char ech) +{ + size_t i; + const char *p = *pp; + + for (i = 0; i < f->vars.len; i++) { + ForVar *forVar = Vector_Get(&f->vars, i); + char *var = forVar->name; + size_t vlen = forVar->len; + + /* XXX: undefined behavior for p if vlen is longer than p? */ + if (memcmp(p, var, vlen) != 0) + continue; + /* XXX: why test for backslash here? */ + if (p[vlen] != ':' && p[vlen] != ech && p[vlen] != '\\') + continue; + + /* Found a variable match. Replace with :U<value> */ + Buf_AddBytesBetween(&f->curBody, *inout_mark, p); + Buf_AddStr(&f->curBody, ":U"); + Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], ech); + + p += vlen; + *inout_mark = p; + break; + } + + *pp = p; +} + +/* While expanding the body of a .for loop, replace single-character + * variable expressions like $i with their ${:U...} expansion. */ +static void +SubstVarShort(For *f, char const ch, const char **pp, const char **inout_mark) +{ + const char *p = *pp; + size_t i; + + /* Probably a single character name, ignore $$ and stupid ones. */ + if (!f->short_var || strchr("}):$", ch) != NULL) { + p++; + *pp = p; + return; + } + + for (i = 0; i < f->vars.len; i++) { + ForVar *var = Vector_Get(&f->vars, i); + const char *varname = var->name; + if (varname[0] != ch || varname[1] != '\0') + continue; + + /* Found a variable match. Replace with ${:U<value>} */ + Buf_AddBytesBetween(&f->curBody, *inout_mark, p); + Buf_AddStr(&f->curBody, "{:U"); + Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], '}'); + Buf_AddByte(&f->curBody, '}'); + + *inout_mark = ++p; + break; + } + + *pp = p; +} + +/* + * Scan the for loop body and replace references to the loop variables + * with variable references that expand to the required text. + * + * Using variable expansions ensures that the .for loop can't generate + * syntax, and that the later parsing will still see a variable. + * We assume that the null variable will never be defined. + * + * The detection of substitutions of the loop control variable is naive. + * Many of the modifiers use \ to escape $ (not $) so it is possible + * to contrive a makefile where an unwanted substitution happens. + */ static char * -For_Iterate(void *v_arg, size_t *ret_len) +ForIterate(void *v_arg, size_t *out_len) { - For *arg = v_arg; - int i; - char *var; - char *cp; - char *cmd_cp; - char *body_end; - char ch; - Buffer cmds; - size_t cmd_len; + For *f = v_arg; + const char *p; + const char *mark; /* where the last replacement left off */ + const char *body_end; + char *cmds_str; - if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) { + if (f->sub_next + f->vars.len > f->items.len) { /* No more iterations */ - For_Free(arg); + For_Free(f); return NULL; } - free(arg->parse_buf); - arg->parse_buf = NULL; + Buf_Empty(&f->curBody); - /* - * Scan the for loop body and replace references to the loop variables - * with variable references that expand to the required text. - * Using variable expansions ensures that the .for loop can't generate - * syntax, and that the later parsing will still see a variable. - * We assume that the null variable will never be defined. - * - * The detection of substitions of the loop control variable is naive. - * Many of the modifiers use \ to escape $ (not $) so it is possible - * to contrive a makefile where an unwanted substitution happens. - */ - - cmd_cp = Buf_GetAll(&arg->buf, &cmd_len); - body_end = cmd_cp + cmd_len; - Buf_Init(&cmds, cmd_len + 256); - for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) { - char ech; - ch = *++cp; + mark = Buf_GetAll(&f->body, NULL); + body_end = mark + Buf_Len(&f->body); + for (p = mark; (p = strchr(p, '$')) != NULL;) { + char ch, ech; + ch = *++p; if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) { - cp++; + p++; /* Check variable name against the .for loop variables */ - STRLIST_FOREACH(var, &arg->vars, i) { - size_t vlen = strlist_info(&arg->vars, i); - if (memcmp(cp, var, vlen) != 0) - continue; - if (cp[vlen] != ':' && cp[vlen] != ech && cp[vlen] != '\\') - continue; - /* Found a variable match. Replace with :U<value> */ - Buf_AddBytesBetween(&cmds, cmd_cp, cp); - Buf_AddStr(&cmds, ":U"); - cp += vlen; - cmd_cp = cp; - for_substitute(&cmds, &arg->items, arg->sub_next + i, ech); - break; - } + SubstVarLong(f, &p, &mark, ech); continue; } - if (ch == 0) + if (ch == '\0') break; - /* Probably a single character name, ignore $$ and stupid ones. {*/ - if (!arg->short_var || strchr("}):$", ch) != NULL) { - cp++; - continue; - } - STRLIST_FOREACH(var, &arg->vars, i) { - if (var[0] != ch || var[1] != 0) - continue; - /* Found a variable match. Replace with ${:U<value>} */ - Buf_AddBytesBetween(&cmds, cmd_cp, cp); - Buf_AddStr(&cmds, "{:U"); - cmd_cp = ++cp; - for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}'); - Buf_AddByte(&cmds, '}'); - break; - } + + SubstVarShort(f, ch, &p, &mark); } - Buf_AddBytesBetween(&cmds, cmd_cp, body_end); + Buf_AddBytesBetween(&f->curBody, mark, body_end); - cp = Buf_Destroy(&cmds, FALSE); - if (DEBUG(FOR)) - (void)fprintf(debug_file, "For: loop body:\n%s", cp); + *out_len = Buf_Len(&f->curBody); + cmds_str = Buf_GetAll(&f->curBody, NULL); + DEBUG1(FOR, "For: loop body:\n%s", cmds_str); - arg->sub_next += strlist_num(&arg->vars); + f->sub_next += f->vars.len; - arg->parse_buf = cp; - *ret_len = strlen(cp); - return cp; + return cmds_str; } /* Run the for loop, imitating the actions of an include file. */ void For_Run(int lineno) { - For *arg; - - arg = accumFor; + For *f = accumFor; accumFor = NULL; - if (strlist_num(&arg->items) == 0) { + if (f->items.len == 0) { /* Nothing to expand - possibly due to an earlier syntax error. */ - For_Free(arg); + For_Free(f); return; } - Parse_SetInput(NULL, lineno, -1, For_Iterate, arg); + Parse_SetInput(NULL, lineno, -1, ForIterate, f); } @@ -1,4 +1,4 @@ -/* $NetBSD: hash.c,v 1.29 2020/09/01 21:11:31 rillig Exp $ */ +/* $NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,336 +69,242 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: hash.c,v 1.29 2020/09/01 21:11:31 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)hash.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: hash.c,v 1.29 2020/09/01 21:11:31 rillig Exp $"); -#endif -#endif /* not lint */ -#endif +/* Hash tables with string keys. */ -/* hash.c -- - * - * This module contains routines to manipulate a hash table. - * See hash.h for a definition of the structure of the hash - * table. Hash tables grow automatically as the amount of - * information increases. - */ #include "make.h" -/* - * Forward references to local procedures that are used before they're - * defined: - */ - -static void RebuildTable(Hash_Table *); +/* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */ +MAKE_RCSID("$NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $"); /* - * The following defines the ratio of # entries to # buckets - * at which we rebuild the table to make it larger. + * The ratio of # entries to # buckets at which we rebuild the table to + * make it larger. */ - #define rebuildLimit 3 -/* The hash function(s) */ +/* This hash function matches Gosling's emacs and java.lang.String. */ +static unsigned int +hash(const char *key, size_t *out_keylen) +{ + unsigned int h = 0; + const char *p = key; + while (*p != '\0') + h = (h << 5) - h + (unsigned char)*p++; + if (out_keylen != NULL) + *out_keylen = (size_t)(p - key); + return h; +} + +unsigned int +Hash_Hash(const char *key) +{ + return hash(key, NULL); +} -#ifndef HASH -/* The default: this one matches Gosling's emacs */ -#define HASH(h, key, p) do { \ - for (h = 0, p = key; *p;) \ - h = (h << 5) - h + *p++; \ - } while (0) +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 -/* Sets up the hash table. - * - * Input: - * t Structure to to hold the table. - * numBuckets How many buckets to create for starters. This - * number is rounded up to a power of two. If - * <= 0, a reasonable default is chosen. The - * table will grow in size later as needed. - */ + for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { + chainlen++; + if (e->key_hash == h && strcmp(e->key, key) == 0) + break; + } + + if (chainlen > t->maxchain) + t->maxchain = chainlen; + + return e; +} + +/* Set up the hash table. */ void -Hash_InitTable(Hash_Table *t, int numBuckets) +HashTable_Init(HashTable *t) { - int i; - struct Hash_Entry **hp; - - /* - * Round up the size to a power of two. - */ - if (numBuckets <= 0) - i = 16; - else { - for (i = 2; i < numBuckets; i <<= 1) - continue; - } + 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; - t->bucketsSize = i; - t->bucketsMask = i - 1; - t->buckets = hp = bmake_malloc(sizeof(*hp) * i); - while (--i >= 0) - *hp++ = NULL; } -/* Removes everything from the hash table and frees up the memory space it - * occupied (except for the space in the Hash_Table structure). */ +/* Remove everything from the hash table and frees up the memory. */ void -Hash_DeleteTable(Hash_Table *t) +HashTable_Done(HashTable *t) { - struct Hash_Entry **hp, *h, *nexth = NULL; - int i; - - for (hp = t->buckets, i = t->bucketsSize; --i >= 0;) { - for (h = *hp++; h != NULL; h = nexth) { - nexth = h->next; - free(h); + 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); - /* - * Set up the hash table to cause memory faults on any future access - * attempts until re-initialization. - */ +#ifdef CLEANUP t->buckets = NULL; +#endif } -/* Searches the hash table for an entry corresponding to the key. - * - * Input: - * t Hash table to search. - * key A hash key. - * - * Results: - * Returns a pointer to the entry for key, or NULL if the table contains - * no entry for the key. - */ -Hash_Entry * -Hash_FindEntry(Hash_Table *t, const char *key) +/* Find the entry corresponding to the key, or return NULL. */ +HashEntry * +HashTable_FindEntry(HashTable *t, const char *key) { - Hash_Entry *e; - unsigned h; - const char *p; - int chainlen; + unsigned int h = hash(key, NULL); + return HashTable_Find(t, h, key); +} - if (t == NULL || t->buckets == NULL) { - return NULL; - } - HASH(h, key, p); - p = key; - chainlen = 0; -#ifdef DEBUG_HASH_LOOKUP - if (DEBUG(HASH)) - fprintf(debug_file, "%s: %p h=%x key=%s\n", __func__, - t, h, key); -#endif - for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { - chainlen++; - if (e->namehash == h && strcmp(e->name, p) == 0) - break; - } - if (chainlen > t->maxchain) - t->maxchain = chainlen; - return e; +/* 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; } -/* Searches the hash table for an entry corresponding to the key. - * If no entry is found, then one is created. - * - * Input: - * t Hash table to search. - * key A hash key. - * newPtr Filled with TRUE if new entry created, - * FALSE otherwise. - */ -Hash_Entry * -Hash_CreateEntry(Hash_Table *t, const char *key, Boolean *newPtr) +/* 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) { - Hash_Entry *e; - unsigned h; - const char *p; - int keylen; - int chainlen; - struct Hash_Entry **hp; - - /* - * Hash the key. As a side effect, save the length (strlen) of the - * key in case we need to create the entry. - */ - HASH(h, key, p); - keylen = p - key; - p = key; - chainlen = 0; -#ifdef DEBUG_HASH_LOOKUP - if (DEBUG(HASH)) - fprintf(debug_file, "%s: %p h=%x key=%s\n", __func__, - t, h, key); -#endif - for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { - chainlen++; - if (e->namehash == h && strcmp(e->name, p) == 0) { - if (newPtr != NULL) - *newPtr = FALSE; - break; + HashEntry *he = HashTable_Find(t, h, key); + return he != NULL ? he->value : NULL; +} + +/* Make the hash table larger. Any bucket numbers from the old table become + * invalid; the hash codes stay valid though. */ +static void +HashTable_Enlarge(HashTable *t) +{ + unsigned int oldSize = t->bucketsSize; + HashEntry **oldBuckets = t->buckets; + unsigned int newSize = 2 * oldSize; + unsigned int newMask = newSize - 1; + HashEntry **newBuckets = bmake_malloc(sizeof(*newBuckets) * newSize); + 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; } } - if (chainlen > t->maxchain) - t->maxchain = chainlen; - if (e) - return e; - - /* - * The desired entry isn't there. Before allocating a new entry, - * expand the table if necessary (and this changes the resulting - * bucket chain). - */ + + free(oldBuckets); + + t->bucketsSize = newSize; + t->bucketsMask = newMask; + t->buckets = newBuckets; + DEBUG5(HASH, "%s: %p size=%d entries=%d maxchain=%d\n", + __func__, t, t->bucketsSize, t->numEntries, t->maxchain); + t->maxchain = 0; +} + +/* Find or create an entry corresponding to the key. + * Return in out_isNew whether a new entry has been created. */ +HashEntry * +HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew) +{ + size_t keylen; + unsigned int h = hash(key, &keylen); + HashEntry *he = HashTable_Find(t, h, key); + + if (he != NULL) { + if (out_isNew != NULL) + *out_isNew = FALSE; + return he; + } + if (t->numEntries >= rebuildLimit * t->bucketsSize) - RebuildTable(t); - e = bmake_malloc(sizeof(*e) + keylen); - hp = &t->buckets[h & t->bucketsMask]; - e->next = *hp; - *hp = e; - Hash_SetValue(e, NULL); - e->namehash = h; - (void)strcpy(e->name, p); + 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 (newPtr != NULL) - *newPtr = TRUE; - return e; + if (out_isNew != NULL) + *out_isNew = TRUE; + return he; } -/* Delete the given hash table entry and free memory associated with it. */ +/* Delete the entry from the table and free the associated memory. */ void -Hash_DeleteEntry(Hash_Table *t, Hash_Entry *e) +HashTable_DeleteEntry(HashTable *t, HashEntry *he) { - Hash_Entry **hp, *p; - - if (e == NULL) - return; - for (hp = &t->buckets[e->namehash & t->bucketsMask]; - (p = *hp) != NULL; hp = &p->next) { - if (p == e) { - *hp = p->next; + 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; } } - (void)write(2, "bad call to Hash_DeleteEntry\n", 29); abort(); } -/* Sets things up for enumerating all entries in the hash table. - * - * Input: - * t Table to be searched. - * searchPtr Area in which to keep state about search. - * - * Results: - * The return value is the address of the first entry in - * the hash table, or NULL if the table is empty. - */ -Hash_Entry * -Hash_EnumFirst(Hash_Table *t, Hash_Search *searchPtr) +/* Set things up for iterating over all entries in the hash table. */ +void +HashIter_Init(HashIter *hi, HashTable *t) { - searchPtr->table = t; - searchPtr->nextBucket = 0; - searchPtr->entry = NULL; - return Hash_EnumNext(searchPtr); + hi->table = t; + hi->nextBucket = 0; + hi->entry = NULL; } -/* Returns the next entry in the hash table, or NULL if the end of the table - * is reached. - * - * Input: - * searchPtr Area used to keep state about search. - */ -Hash_Entry * -Hash_EnumNext(Hash_Search *searchPtr) +/* Return the next entry in the hash table, or NULL if the end of the table + * is reached. */ +HashEntry * +HashIter_Next(HashIter *hi) { - Hash_Entry *e; - Hash_Table *t = searchPtr->table; - - /* - * The entry field points to the most recently returned - * entry, or is NULL if we are starting up. If not NULL, we have - * to start at the next one in the chain. - */ - e = searchPtr->entry; - if (e != NULL) - e = e->next; - /* - * If the chain ran out, or if we are starting up, we need to - * find the next nonempty chain. - */ - while (e == NULL) { - if (searchPtr->nextBucket >= t->bucketsSize) - return NULL; - e = t->buckets[searchPtr->nextBucket++]; - } - searchPtr->entry = e; - return e; -} + HashTable *t = hi->table; + HashEntry *he = hi->entry; + HashEntry **buckets = t->buckets; + unsigned int bucketsSize = t->bucketsSize; -/* Makes a new hash table that is larger than the old one. The entire hash - * table is moved, so any bucket numbers from the old table become invalid. */ -static void -RebuildTable(Hash_Table *t) -{ - Hash_Entry *e, *next = NULL, **hp, **xp; - int i, mask; - Hash_Entry **oldhp; - int oldsize; - - oldhp = t->buckets; - oldsize = i = t->bucketsSize; - i <<= 1; - t->bucketsSize = i; - t->bucketsMask = mask = i - 1; - t->buckets = hp = bmake_malloc(sizeof(*hp) * i); - while (--i >= 0) - *hp++ = NULL; - for (hp = oldhp, i = oldsize; --i >= 0;) { - for (e = *hp++; e != NULL; e = next) { - next = e->next; - xp = &t->buckets[e->namehash & mask]; - e->next = *xp; - *xp = e; - } - } - free(oldhp); - if (DEBUG(HASH)) - fprintf(debug_file, "%s: %p size=%d entries=%d maxchain=%d\n", - __func__, t, t->bucketsSize, t->numEntries, t->maxchain); - t->maxchain = 0; -} + if (he != NULL) + he = he->next; /* skip the most recently returned entry */ -void -Hash_ForEach(Hash_Table *t, void (*action)(void *, void *), void *data) -{ - Hash_Search search; - Hash_Entry *e; - - for (e = Hash_EnumFirst(t, &search); - e != NULL; - e = Hash_EnumNext(&search)) - action(Hash_GetValue(e), data); + while (he == NULL) { /* find the next nonempty chain */ + if (hi->nextBucket >= bucketsSize) + return NULL; + he = buckets[hi->nextBucket++]; + } + hi->entry = he; + return he; } void -Hash_DebugStats(Hash_Table *t, const char *name) +HashTable_DebugStats(HashTable *t, const char *name) { - if (DEBUG(HASH)) - fprintf(debug_file, "Hash_Table %s: size=%d numEntries=%d maxchain=%d\n", - name, t->bucketsSize, t->numEntries, t->maxchain); + DEBUG4(HASH, "HashTable %s: size=%u numEntries=%u maxchain=%u\n", + name, t->bucketsSize, t->numEntries, t->maxchain); } @@ -1,4 +1,4 @@ -/* $NetBSD: hash.h,v 1.21 2020/09/01 21:11:31 rillig Exp $ */ +/* $NetBSD: hash.h,v 1.31 2020/10/25 19:19:07 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -78,54 +78,54 @@ #define MAKE_HASH_H /* A single key-value entry in the hash table. */ -typedef struct Hash_Entry { - struct Hash_Entry *next; /* Used to link together all the entries +typedef struct HashEntry { + struct HashEntry *next; /* Used to link together all the entries * associated with the same bucket. */ - void *value; - unsigned namehash; /* hash value of key */ - char name[1]; /* key string, variable length */ -} Hash_Entry; + 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 Hash_Table { - Hash_Entry **buckets; /* Pointers to Hash_Entry, one +typedef struct HashTable { + HashEntry **buckets; /* Pointers to HashEntry, one * for each bucket in the table. */ - int bucketsSize; - int numEntries; /* Number of entries in the table. */ - int bucketsMask; /* Used to select the bucket for a hash. */ - int maxchain; /* max length of chain detected */ -} Hash_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; -/* - * The following structure is used by the searching routines - * to record where we are in the search. - */ -typedef struct Hash_Search { - Hash_Table *table; /* Table being searched. */ - int nextBucket; /* Next bucket to check (after current). */ - Hash_Entry *entry; /* Next entry to check in current bucket. */ -} Hash_Search; +/* State of an iteration over all entries in a table. */ +typedef struct HashIter { + HashTable *table; /* Table being searched. */ + unsigned int nextBucket; /* Next bucket to check (after current). */ + HashEntry *entry; /* Next entry to check in current bucket. */ +} HashIter; -static inline void * MAKE_ATTR_UNUSED -Hash_GetValue(Hash_Entry *h) +static inline MAKE_ATTR_UNUSED void * +HashEntry_Get(HashEntry *h) { return h->value; } -static inline void MAKE_ATTR_UNUSED -Hash_SetValue(Hash_Entry *h, void *datum) +static inline MAKE_ATTR_UNUSED void +HashEntry_Set(HashEntry *h, void *datum) { h->value = datum; } -void Hash_InitTable(Hash_Table *, int); -void Hash_DeleteTable(Hash_Table *); -Hash_Entry *Hash_FindEntry(Hash_Table *, const char *); -Hash_Entry *Hash_CreateEntry(Hash_Table *, const char *, Boolean *); -void Hash_DeleteEntry(Hash_Table *, Hash_Entry *); -Hash_Entry *Hash_EnumFirst(Hash_Table *, Hash_Search *); -Hash_Entry *Hash_EnumNext(Hash_Search *); -void Hash_ForEach(Hash_Table *, void (*)(void *, void *), void *); -void Hash_DebugStats(Hash_Table *, const char *); +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 *); +void HashTable_DeleteEntry(HashTable *, HashEntry *); +void HashTable_DebugStats(HashTable *, const char *); + +void HashIter_Init(HashIter *, HashTable *); +HashEntry *HashIter_Next(HashIter *); #endif /* MAKE_HASH_H */ @@ -1,4 +1,4 @@ -/* $NetBSD: job.c,v 1.227 2020/08/30 19:56:02 rillig Exp $ */ +/* $NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,68 +69,58 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: job.c,v 1.227 2020/08/30 19:56:02 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)job.c 8.2 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: job.c,v 1.227 2020/08/30 19:56:02 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - /*- * job.c -- * handle the creation etc. of our child processes. * * Interface: - * Job_Make Start the creation of the given target. - * - * Job_CatchChildren Check for and handle the termination of any - * children. This must be called reasonably - * frequently to keep the whole make going at - * a decent clip, since job table entries aren't - * removed until their process is caught this way. - * - * Job_CatchOutput Print any output our children have produced. - * Should also be called fairly frequently to - * keep the user informed of what's going on. - * If no output is waiting, it will block for - * a time given by the SEL_* constants, below, - * or until output is ready. - * - * Job_Init Called to initialize this module. in addition, - * any commands attached to the .BEGIN target - * are executed before this function returns. - * Hence, the makefile must have been parsed - * before this function is called. - * - * Job_End Cleanup any memory used. - * - * Job_ParseShell Given the line following a .SHELL target, parse - * the line as a shell specification. Returns - * FALSE if the spec was incorrect. - * - * Job_Finish Perform any final processing which needs doing. - * This includes the execution of any commands - * which have been/were attached to the .END - * target. It should only be called when the - * job table is empty. - * - * Job_AbortAll Abort all currently running jobs. It doesn't - * handle output or do anything for the jobs, - * just kills them. It should only be called in - * an emergency, as it were. - * - * Job_CheckCommands Verify that the commands for a target are - * ok. Provide them if necessary and possible. - * - * Job_Touch Update a target without really updating it. - * - * Job_Wait Wait for all currently-running jobs to finish. + * Job_Init Called to initialize this module. In addition, + * any commands attached to the .BEGIN target + * are executed before this function returns. + * Hence, the makefiles must have been parsed + * before this function is called. + * + * Job_End Clean up any memory used. + * + * Job_Make Start the creation of the given target. + * + * Job_CatchChildren + * Check for and handle the termination of any + * children. This must be called reasonably + * frequently to keep the whole make going at + * a decent clip, since job table entries aren't + * removed until their process is caught this way. + * + * Job_CatchOutput + * Print any output our children have produced. + * Should also be called fairly frequently to + * keep the user informed of what's going on. + * If no output is waiting, it will block for + * a time given by the SEL_* constants, below, + * or until output is ready. + * + * Job_ParseShell Given the line following a .SHELL target, parse + * the line as a shell specification. Returns + * FALSE if the spec was incorrect. + * + * Job_Finish Perform any final processing which needs doing. + * This includes the execution of any commands + * which have been/were attached to the .END + * target. It should only be called when the + * job table is empty. + * + * Job_AbortAll Abort all currently running jobs. It doesn't + * handle output or do anything for the jobs, + * just kills them. It should only be called in + * an emergency. + * + * Job_CheckCommands + * Verify that the commands for a target are + * ok. Provide them if necessary and possible. + * + * Job_Touch Update a target without really updating it. + * + * Job_Wait Wait for all currently-running jobs to finish. */ #ifdef HAVE_CONFIG_H @@ -164,61 +154,118 @@ __RCSID("$NetBSD: job.c,v 1.227 2020/08/30 19:56:02 rillig Exp $"); #include "job.h" #include "pathnames.h" #include "trace.h" -# define STATIC static + +/* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $"); + +/* A shell defines how the commands are run. All commands for a target are + * written into a single file, which is then given to the shell to execute + * the commands from it. The commands are written to the file using a few + * templates for echo control and error control. + * + * The name of the shell is the basename for the predefined shells, such as + * "sh", "csh", "bash". For custom shells, it is the full pathname, and its + * basename is used to select the type of shell; the longest match wins. + * So /usr/pkg/bin/bash has type sh, /usr/local/bin/tcsh has type csh. + * + * The echoing of command lines is controlled using hasEchoCtl, echoOff, + * echoOn, noPrint and noPrintLen. When echoOff is executed by the shell, it + * still outputs something, but this something is not interesting, therefore + * it is filtered out using noPrint and noPrintLen. + * + * The error checking for individual commands is controlled using hasErrCtl, + * errOnOrEcho, errOffOrExecIgnore and errExit. + * + * If a shell doesn't have error control, errOnOrEcho becomes a printf template + * for echoing the command, should echoing be on; errOffOrExecIgnore becomes + * another printf template for executing the command while ignoring the return + * status. Finally errExit is a printf template for running the command and + * causing the shell to exit on error. If any of these strings are empty when + * hasErrCtl is FALSE, the command will be executed anyway as is, and if it + * causes an error, so be it. Any templates setup to echo the command will + * escape any '$ ` \ "' characters in the command string to avoid common + * problems with echo "%s\n" as a template. + * + * The command-line flags "echo" and "exit" also control the behavior. The + * "echo" flag causes the shell to start echoing commands right away. The + * "exit" flag causes the shell to exit when an error is detected in one of + * the commands. + */ +typedef struct Shell { + + /* The name of the shell. For Bourne and C shells, this is used only to + * find the shell description when used as the single source of a .SHELL + * target. For user-defined shells, this is the full path of the shell. */ + const char *name; + + Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */ + const char *echoOff; /* command to turn off echo */ + const char *echoOn; /* command to turn it back on again */ + const char *noPrint; /* text to skip when printing output from + * shell. This is usually the same as echoOff */ + size_t noPrintLen; /* length of noPrint command */ + + Boolean hasErrCtl; /* set if can control error checking for + * individual commands */ + /* XXX: split into errOn and echoCmd */ + const char *errOnOrEcho; /* template to turn on error checking */ + /* XXX: split into errOff and execIgnore */ + const char *errOffOrExecIgnore; /* template to turn off error checking */ + const char *errExit; /* template to use for testing exit code */ + + /* string literal that results in a newline character when it appears + * outside of any 'quote' or "quote" characters */ + const char *newline; + char commentChar; /* character used by shell for comment lines */ + + /* + * command-line flags + */ + const char *echo; /* echo commands */ + const char *exit; /* exit on error */ +} Shell; /* * error handling variables */ -static int errors = 0; /* number of errors reported */ -static int aborting = 0; /* why is the make aborting? */ -#define ABORT_ERROR 1 /* Because of an error */ -#define ABORT_INTERRUPT 2 /* Because it was interrupted */ -#define ABORT_WAIT 3 /* Waiting for jobs to finish */ -#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ +static int errors = 0; /* number of errors reported */ +typedef enum AbortReason { /* why is the make aborting? */ + ABORT_NONE, + ABORT_ERROR, /* Because of an error */ + ABORT_INTERRUPT, /* Because it was interrupted */ + ABORT_WAIT /* Waiting for jobs to finish */ +} AbortReason; +static AbortReason aborting = ABORT_NONE; +#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ /* * this tracks the number of tokens currently "out" to build jobs. */ int jobTokensRunning = 0; -/* - * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file - * is a char! So when we go above 127 we turn negative! - */ -#define FILENO(a) ((unsigned) fileno(a)) - -/* - * post-make command processing. The node postCommands is really just the - * .END target but we keep it around to avoid having to search for it - * all the time. - */ -static GNode *postCommands = NULL; - /* node containing commands to execute when - * everything else is done */ -static int numCommands; /* The number of commands actually printed - * for a target. Should this number be - * 0, no shell will be executed. */ +/* The number of commands actually printed to the shell commands file for + * the current job. Should this number be 0, no shell will be executed. */ +static int numCommands; -/* - * Return values from JobStart. - */ -#define JOB_RUNNING 0 /* Job is running */ -#define JOB_ERROR 1 /* Error in starting the job */ -#define JOB_FINISHED 2 /* The job is already finished */ +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 prefedined shells as the default shell. + * 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. + * set defined here, or add additional shells. */ #ifdef DEFSHELL_CUSTOM @@ -246,11 +293,20 @@ static Shell shells[] = { * sh-compatible shells. */ { - DEFSHELL_CUSTOM, - FALSE, "", "", "", 0, - FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', - "", - "", + DEFSHELL_CUSTOM, /* .name */ + FALSE, /* .hasEchoCtl */ + "", /* .echoOff */ + "", /* .echoOn */ + "", /* .noPrint */ + 0, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "echo \"%s\"\n", /* .errOnOrEcho */ + "%s\n", /* .errOffOrExecIgnore */ + "{ %s \n} || exit $?\n", /* .errExit */ + "'\n'", /* .newline */ + '#', /* .commentChar */ + "", /* .echo */ + "", /* .exit */ }, #endif /* DEFSHELL_CUSTOM */ /* @@ -258,25 +314,43 @@ static Shell shells[] = { * sun UNIX anyway, one can even control error checking. */ { - "sh", - FALSE, "", "", "", 0, - FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', + "sh", /* .name */ + FALSE, /* .hasEchoCtl */ + "", /* .echoOff */ + "", /* .echoOn */ + "", /* .noPrint */ + 0, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "echo \"%s\"\n", /* .errOnOrEcho */ + "%s\n", /* .errOffOrExecIgnore */ + "{ %s \n} || exit $?\n", /* .errExit */ + "'\n'", /* .newline */ + '#', /* .commentChar*/ #if defined(MAKE_NATIVE) && defined(__NetBSD__) - "q", + "q", /* .echo */ #else - "", + "", /* .echo */ #endif - "", + "", /* .exit */ }, /* * KSH description. */ { - "ksh", - TRUE, "set +v", "set -v", "set +v", 6, - FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', - "v", - "", + "ksh", /* .name */ + TRUE, /* .hasEchoCtl */ + "set +v", /* .echoOff */ + "set -v", /* .echoOn */ + "set +v", /* .noPrint */ + 6, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "echo \"%s\"\n", /* .errOnOrEcho */ + "%s\n", /* .errOffOrExecIgnore */ + "{ %s \n} || exit $?\n", /* .errExit */ + "'\n'", /* .newline */ + '#', /* .commentChar */ + "v", /* .echo */ + "", /* .exit */ }, /* * CSH description. The csh can do echo control by playing @@ -284,36 +358,36 @@ static Shell shells[] = { * however, it is unable to do error control nicely. */ { - "csh", - TRUE, "unset verbose", "set verbose", "unset verbose", 10, - FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#', - "v", "e", -}, - /* - * UNKNOWN. - */ -{ - NULL, - FALSE, NULL, NULL, NULL, 0, - FALSE, NULL, NULL, NULL, NULL, 0, - NULL, NULL, + "csh", /* .name */ + TRUE, /* .hasEchoCtl */ + "unset verbose", /* .echoOff */ + "set verbose", /* .echoOn */ + "unset verbose", /* .noPrint */ + 13, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "echo \"%s\"\n", /* .errOnOrEcho */ + /* XXX: Mismatch between errOn and execIgnore */ + "csh -c \"%s || exit 0\"\n", /* .errOffOrExecIgnore */ + "", /* .errExit */ + "'\\\n'", /* .newline */ + '#', /* .commentChar */ + "v", /* .echo */ + "e", /* .exit */ } }; -static Shell *commandShell = &shells[DEFSHELL_INDEX]; /* this is the shell to - * which we pass all - * commands in the Makefile. - * It is set by the - * Job_ParseShell function */ -const char *shellPath = NULL, /* full pathname of - * executable image */ - *shellName = NULL; /* last component of shell */ + +/* This is the shell to which we pass all commands in the Makefile. + * It is set by the Job_ParseShell function. */ +static Shell *commandShell = &shells[DEFSHELL_INDEX]; +const char *shellPath = NULL; /* full pathname of executable image */ +const char *shellName = NULL; /* last component of shellPath */ char *shellErrFlag = NULL; static char *shellArgv = NULL; /* Custom shell args */ -STATIC Job *job_table; /* The structures that describe them */ -STATIC Job *job_table_end; /* job_table + maxJobs */ -static int wantToken; /* we want a token */ +static Job *job_table; /* The structures that describe them */ +static Job *job_table_end; /* job_table + maxJobs */ +static unsigned int wantToken; /* we want a token */ static int lurking_children = 0; static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */ @@ -323,12 +397,12 @@ static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */ */ static struct pollfd *fds = NULL; static Job **jobfds = NULL; -static int nfds = 0; +static nfds_t nfds = 0; static void watchfd(Job *); static void clearfd(Job *); static int readyfd(Job *); -STATIC GNode *lastNode; /* The node for which output was most recently +static GNode *lastNode; /* The node for which output was most recently * produced. */ static char *targPrefix = NULL; /* What we print at the start of TARG_FMT */ static Job tokenWaitJob; /* token wait pseudo-job */ @@ -337,32 +411,18 @@ static Job childExitJob; /* child exit pseudo-job */ #define CHILD_EXIT "." #define DO_JOB_RESUME "R" -static const int npseudojobs = 2; /* number of pseudo-jobs */ +enum { npseudojobs = 2 }; /* number of pseudo-jobs */ #define TARG_FMT "%s %s ---\n" /* Default format */ #define MESSAGE(fp, gn) \ - if (maxJobs != 1 && targPrefix && *targPrefix) \ + if (opts.maxJobs != 1 && targPrefix && *targPrefix) \ (void)fprintf(fp, TARG_FMT, targPrefix, gn->name) static sigset_t caught_signals; /* Set of signals we handle */ -static void JobChildSig(int); -static void JobContinueSig(int); -static Job *JobFindPid(int, int, Boolean); -static int JobPrintCommand(void *, void *); -static int JobSaveCommand(void *, void *); -static void JobClose(Job *); -static void JobExec(Job *, char **); -static void JobMakeArgv(Job *, char **); -static int JobStart(GNode *, int); -static char *JobOutput(Job *, char *, char *, int); static void JobDoOutput(Job *, Boolean); -static Shell *JobMatchShell(const char *); static void JobInterrupt(int, int) MAKE_ATTR_DEAD; static void JobRestartJobs(void); -static void JobTokenAdd(void); -static void JobSigLock(sigset_t *); -static void JobSigUnlock(sigset_t *); static void JobSigReset(void); static unsigned @@ -380,9 +440,9 @@ job_table_dump(const char *where) { Job *job; - fprintf(debug_file, "job table @ %s\n", where); + debug_printf("job table @ %s\n", where); for (job = job_table; job < job_table_end; job++) { - fprintf(debug_file, "job %d, status %d, flags %d, pid %d\n", + debug_printf("job %d, status %d, flags %d, pid %d\n", (int)(job - job_table), job->job_state, job->flags, job->pid); } } @@ -394,12 +454,20 @@ job_table_dump(const char *where) static void JobDeleteTarget(GNode *gn) { - if ((gn->type & (OP_JOIN|OP_PHONY)) == 0 && !Targ_Precious(gn)) { - char *file = (gn->path == NULL ? gn->name : gn->path); - if (!noExecute && eunlink(file) != -1) { - Error("*** %s removed", file); - } - } + 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); } /* @@ -425,23 +493,27 @@ static void JobCreatePipe(Job *job, int minfd) { int i, fd, flags; + int pipe_fds[2]; - if (pipe(job->jobPipe) == -1) + 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(job->jobPipe[i], F_DUPFD, minfd); - if (fd != -1) { - close(job->jobPipe[i]); - job->jobPipe[i] = fd; - } + /* 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->jobPipe[0], F_SETFD, FD_CLOEXEC) == -1) + if (fcntl(job->inPipe, F_SETFD, FD_CLOEXEC) == -1) Punt("Cannot set close-on-exec: %s", strerror(errno)); - if (fcntl(job->jobPipe[1], F_SETFD, FD_CLOEXEC) == -1) + if (fcntl(job->outPipe, F_SETFD, FD_CLOEXEC) == -1) Punt("Cannot set close-on-exec: %s", strerror(errno)); /* @@ -450,65 +522,34 @@ JobCreatePipe(Job *job, int minfd) * race for the token when a new one becomes available, so the read * from the pipe should not block. */ - flags = fcntl(job->jobPipe[0], F_GETFL, 0); + flags = fcntl(job->inPipe, F_GETFL, 0); if (flags == -1) Punt("Cannot get flags: %s", strerror(errno)); flags |= O_NONBLOCK; - if (fcntl(job->jobPipe[0], F_SETFL, flags) == -1) + if (fcntl(job->inPipe, F_SETFL, flags) == -1) Punt("Cannot set flags: %s", strerror(errno)); } -/*- - *----------------------------------------------------------------------- - * JobCondPassSig -- - * Pass a signal to a job - * - * Input: - * signop Signal to send it - * - * Side Effects: - * None, except the job may bite it. - * - *----------------------------------------------------------------------- - */ +/* Pass the signal to each running job. */ static void JobCondPassSig(int signo) { Job *job; - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "JobCondPassSig(%d) called.\n", signo); - } + DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo); for (job = job_table; job < job_table_end; job++) { if (job->job_state != JOB_ST_RUNNING) continue; - if (DEBUG(JOB)) { - (void)fprintf(debug_file, - "JobCondPassSig passing signal %d to child %d.\n", - signo, job->pid); - } + DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n", + signo, job->pid); KILLPG(job->pid, signo); } } -/*- - *----------------------------------------------------------------------- - * JobChldSig -- - * SIGCHLD handler. - * - * Input: - * signo The signal number we've received +/* SIGCHLD handler. * - * Results: - * None. - * - * Side Effects: - * Sends a token on the child exit pipe to wake us up from - * select()/poll(). - * - *----------------------------------------------------------------------- - */ + * Sends a token on the child exit pipe to wake us up from select()/poll(). */ static void JobChildSig(int signo MAKE_ATTR_UNUSED) { @@ -517,27 +558,12 @@ JobChildSig(int signo MAKE_ATTR_UNUSED) } -/*- - *----------------------------------------------------------------------- - * JobContinueSig -- - * Resume all stopped jobs. - * - * Input: - * signo The signal number we've received - * - * Results: - * None. - * - * Side Effects: - * Jobs start running again. - * - *----------------------------------------------------------------------- - */ +/* Resume all stopped jobs. */ static void JobContinueSig(int signo MAKE_ATTR_UNUSED) { /* - * Defer sending to SIGCONT to our stopped children until we return + * Defer sending SIGCONT to our stopped children until we return * from the signal handler. */ while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 && @@ -545,22 +571,8 @@ JobContinueSig(int signo MAKE_ATTR_UNUSED) continue; } -/*- - *----------------------------------------------------------------------- - * JobPassSig -- - * Pass a signal on to all jobs, then resend to ourselves. - * - * Input: - * signo The signal number we've received - * - * Results: - * None. - * - * Side Effects: - * We die by the same signal. - * - *----------------------------------------------------------------------- - */ +/* 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) { @@ -568,6 +580,8 @@ JobPassSig_int(int 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) { @@ -602,10 +616,8 @@ JobPassSig_suspend(int signo) act.sa_flags = 0; (void)sigaction(signo, &act, NULL); - if (DEBUG(JOB)) { - (void)fprintf(debug_file, - "JobPassSig passing signal %d to self.\n", signo); - } + if (DEBUG(JOB)) + debug_printf("JobPassSig passing signal %d to self.\n", signo); (void)kill(getpid(), signo); @@ -622,7 +634,7 @@ JobPassSig_suspend(int signo) * events will have happened by then - and that the waitpid() will * collect the child 'suspended' events. * For correct sequencing we just need to ensure we process the - * waitpid() before passign on the SIGCONT. + * waitpid() before passing on the SIGCONT. * * In any case nothing else is needed here. */ @@ -633,26 +645,8 @@ JobPassSig_suspend(int signo) (void)sigprocmask(SIG_SETMASK, &omask, NULL); } -/*- - *----------------------------------------------------------------------- - * JobFindPid -- - * Compare the pid of the job with the given pid and return 0 if they - * are equal. This function is called from Job_CatchChildren - * to find the job descriptor of the finished job. - * - * Input: - * job job to examine - * pid process id desired - * - * Results: - * Job with matching pid - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ static Job * -JobFindPid(int pid, int status, Boolean isJobs) +JobFindPid(int pid, JobState status, Boolean isJobs) { Job *job; @@ -665,6 +659,52 @@ JobFindPid(int pid, int status, Boolean isJobs) return NULL; } +/* Parse leading '@', '-' and '+', which control the exact execution mode. */ +static void +ParseRunOptions( + char **pp, + Boolean *out_shutUp, Boolean *out_errOff, Boolean *out_runAlways) +{ + char *p = *pp; + *out_shutUp = FALSE; + *out_errOff = FALSE; + *out_runAlways = FALSE; + + for (;;) { + if (*p == '@') + *out_shutUp = !DEBUG(LOUD); + else if (*p == '-') + *out_errOff = TRUE; + else if (*p == '+') + *out_runAlways = TRUE; + else + break; + p++; + } + + pp_skip_whitespace(&p); + + *pp = p; +} + +/* Escape a string for a double-quoted string literal in sh, csh and ksh. */ +static char * +EscapeShellDblQuot(const char *cmd) +{ + size_t i, j; + + /* Worst that could happen is every char needs escaping. */ + char *esc = bmake_malloc(strlen(cmd) * 2 + 1); + for (i = 0, j = 0; cmd[i] != '\0'; i++, j++) { + if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || cmd[i] == '"') + esc[j++] = '\\'; + esc[j] = cmd[i]; + } + esc[j] = '\0'; + + return esc; +} + /*- *----------------------------------------------------------------------- * JobPrintCommand -- @@ -679,93 +719,58 @@ JobFindPid(int pid, int status, Boolean isJobs) * made and return non-zero to signal that the end of the commands * was reached. These commands are later attached to the postCommands * node and executed by Job_End when all things are done. - * This function is called from JobStart via Lst_ForEach. - * - * Input: - * cmdp command string to print - * jobp job for which to print it - * - * Results: - * Always 0, unless the command was "..." * * Side Effects: * If the command begins with a '-' and the shell has no error control, * the JOB_IGNERR flag is set in the job descriptor. - * If the command is "..." and we're not ignoring such things, - * tailCmds is set to the successor node of the cmd. * numCommands is incremented if the command is actually printed. *----------------------------------------------------------------------- */ -static int -JobPrintCommand(void *cmdp, void *jobp) +static void +JobPrintCommand(Job *job, char *cmd) { - Boolean noSpecials; /* true if we shouldn't worry about - * inserting special commands into - * the input stream. */ - Boolean shutUp = FALSE; /* true if we put a no echo command - * into the command file */ - Boolean errOff = FALSE; /* true if we turned error checking - * off before printing the command - * and need to turn it back on */ - const char *cmdTemplate; /* Template to use when printing the - * command */ - char *cmdStart; /* Start of expanded command */ - char *escCmd = NULL; /* Command with quotes/backticks escaped */ - char *cmd = (char *)cmdp; - Job *job = (Job *)jobp; - - noSpecials = NoExecute(job->node); - - if (strcmp(cmd, "...") == 0) { - job->node->type |= OP_SAVE_CMDS; - if ((job->flags & JOB_IGNDOTS) == 0) { - LstNode dotsNode = Lst_FindDatum(job->node->commands, cmd); - job->tailCmds = dotsNode != NULL ? LstNode_Next(dotsNode) : NULL; - return 1; - } - return 0; - } + const char *const cmdp = cmd; + Boolean noSpecials; /* true if we shouldn't worry about + * inserting special commands into + * the input stream. */ + Boolean shutUp; /* true if we put a no echo command + * into the command file */ + Boolean errOff; /* true if we turned error checking + * off before printing the command + * and need to turn it back on */ + Boolean runAlways; + const char *cmdTemplate; /* Template to use when printing the + * command */ + char *cmdStart; /* Start of expanded command */ + char *escCmd = NULL; /* Command with quotes/backticks escaped */ + + noSpecials = !GNode_ShouldExecute(job->node); #define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \ - (void)fprintf(debug_file, fmt, arg); \ + debug_printf(fmt, arg); \ } \ (void)fprintf(job->cmdFILE, fmt, arg); \ (void)fflush(job->cmdFILE); - numCommands += 1; + numCommands++; - cmdStart = cmd = Var_Subst(cmd, job->node, VARE_WANTRES); + Var_Subst(cmd, job->node, VARE_WANTRES, &cmd); + /* TODO: handle errors */ + cmdStart = cmd; cmdTemplate = "%s\n"; - /* - * Check for leading @' and -'s to control echoing and error checking. - */ - while (*cmd == '@' || *cmd == '-' || (*cmd == '+')) { - switch (*cmd) { - case '@': - shutUp = DEBUG(LOUD) ? FALSE : TRUE; - break; - case '-': - errOff = TRUE; - break; - case '+': - if (noSpecials) { - /* - * We're not actually executing anything... - * but this one needs to be - use compat mode just for it. - */ - CompatRunCommand(cmdp, job->node); - free(cmdStart); - return 0; - } - break; - } - cmd++; - } + ParseRunOptions(&cmd, &shutUp, &errOff, &runAlways); - while (isspace((unsigned char) *cmd)) - cmd++; + if (runAlways && noSpecials) { + /* + * We're not actually executing anything... + * but this one needs to be - use compat mode just for it. + */ + Compat_RunCommand(cmdp, job->node); + free(cmdStart); + return; + } /* * If the shell doesn't have error control the alternate echo'ing will @@ -773,19 +778,8 @@ JobPrintCommand(void *cmdp, void *jobp) * and this will need the characters '$ ` \ "' escaped */ - if (!commandShell->hasErrCtl) { - int i, j; - - /* Worst that could happen is every char needs escaping. */ - escCmd = bmake_malloc((strlen(cmd) * 2) + 1); - for (i = 0, j = 0; cmd[i] != '\0'; i++, j++) { - if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || - cmd[i] == '"') - escCmd[j++] = '\\'; - escCmd[j] = cmd[i]; - } - escCmd[j] = '\0'; - } + if (!commandShell->hasErrCtl) + escCmd = EscapeShellDblQuot(cmd); if (shutUp) { if (!(job->flags & JOB_SILENT) && !noSpecials && @@ -811,19 +805,19 @@ JobPrintCommand(void *cmdp, void *jobp) if (!(job->flags & JOB_SILENT) && !shutUp && commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOff); - DBPRINTF("%s\n", commandShell->ignErr); + DBPRINTF("%s\n", commandShell->errOffOrExecIgnore); DBPRINTF("%s\n", commandShell->echoOn); } else { - DBPRINTF("%s\n", commandShell->ignErr); + DBPRINTF("%s\n", commandShell->errOffOrExecIgnore); } - } else if (commandShell->ignErr && - (*commandShell->ignErr != '\0')) + } else if (commandShell->errOffOrExecIgnore && + commandShell->errOffOrExecIgnore[0] != '\0') { /* * The shell has no error control, so we need to be * weird to get it to ignore any errors from the command. * If echoing is turned on, we turn it off and use the - * errCheck template to echo the command. Leave echoing + * errOnOrEcho template to echo the command. Leave echoing * off so the user doesn't see the weirdness we go through * to ignore errors. Set cmdTemplate to use the weirdness * instead of the simple "%s\n" template. @@ -833,18 +827,18 @@ JobPrintCommand(void *cmdp, void *jobp) if (commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOff); } - DBPRINTF(commandShell->errCheck, escCmd); + DBPRINTF(commandShell->errOnOrEcho, escCmd); shutUp = TRUE; } else { if (!shutUp) { - DBPRINTF(commandShell->errCheck, escCmd); + DBPRINTF(commandShell->errOnOrEcho, escCmd); } } - cmdTemplate = commandShell->ignErr; + cmdTemplate = commandShell->errOffOrExecIgnore; /* * The error ignoration (hee hee) is already taken care - * of by the ignErr template, so pretend error checking - * is still on. + * of by the errOffOrExecIgnore template, so pretend error + * checking is still on. */ errOff = FALSE; } else { @@ -857,25 +851,25 @@ JobPrintCommand(void *cmdp, void *jobp) /* * If errors are being checked and the shell doesn't have error control - * but does supply an errOut template, then setup commands to run + * but does supply an errExit template, then setup commands to run * through it. */ - if (!commandShell->hasErrCtl && commandShell->errOut && - (*commandShell->errOut != '\0')) { + if (!commandShell->hasErrCtl && commandShell->errExit && + commandShell->errExit[0] != '\0') { if (!(job->flags & JOB_SILENT) && !shutUp) { if (commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOff); } - DBPRINTF(commandShell->errCheck, escCmd); + DBPRINTF(commandShell->errOnOrEcho, escCmd); shutUp = TRUE; } /* If it's a comment line or blank, treat as an ignored error */ if ((escCmd[0] == commandShell->commentChar) || (escCmd[0] == 0)) - cmdTemplate = commandShell->ignErr; + cmdTemplate = commandShell->errOffOrExecIgnore; else - cmdTemplate = commandShell->errOut; + cmdTemplate = commandShell->errExit; errOff = FALSE; } } @@ -899,50 +893,56 @@ JobPrintCommand(void *cmdp, void *jobp) DBPRINTF("%s\n", commandShell->echoOff); shutUp = TRUE; } - DBPRINTF("%s\n", commandShell->errCheck); + DBPRINTF("%s\n", commandShell->errOnOrEcho); } if (shutUp && commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOn); } - return 0; } -/*- - *----------------------------------------------------------------------- - * JobSaveCommand -- - * Save a command to be executed when everything else is done. - * Callback function for JobFinish... - * - * Results: - * Always returns 0 +/* Print all commands to the shell file that is later executed. * - * Side Effects: - * The command is tacked onto the end of postCommands' commands list. - * - *----------------------------------------------------------------------- - */ -static int -JobSaveCommand(void *cmd, void *gn) + * The special command "..." stops printing and saves the remaining commands + * to be executed later. */ +static void +JobPrintCommands(Job *job) { - cmd = Var_Subst((char *)cmd, (GNode *)gn, VARE_WANTRES); - Lst_Append(postCommands->commands, cmd); - return 0; + StringListNode *ln; + + for (ln = job->node->commands->first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; + + if (strcmp(cmd, "...") == 0) { + job->node->type |= OP_SAVE_CMDS; + if ((job->flags & JOB_IGNDOTS) == 0) { + job->tailCmds = ln->next; + break; + } + } else + JobPrintCommand(job, ln->datum); + } } +/* Save the delayed commands, to be executed when everything else is done. */ +static void +JobSaveCommands(Job *job) +{ + StringListNode *node; + + for (node = job->tailCmds; node != NULL; node = node->next) { + const char *cmd = node->datum; + char *expanded_cmd; + /* XXX: This Var_Subst is only intended to expand the dynamic + * variables such as .TARGET, .IMPSRC. It is not intended to + * expand the other variables as well; see deptgt-end.mk. */ + (void)Var_Subst(cmd, job->node, VARE_WANTRES, &expanded_cmd); + /* TODO: handle errors */ + Lst_Append(Targ_GetEndNode()->commands, expanded_cmd); + } +} -/*- - *----------------------------------------------------------------------- - * JobClose -- - * Called to close both input and output pipes when a job is finished. - * - * Results: - * Nada - * - * Side Effects: - * The file descriptors associated with the job are closed. - * - *----------------------------------------------------------------------- - */ + +/* Called to close both input and output pipes when a job is finished. */ static void JobClose(Job *job) { @@ -969,9 +969,6 @@ JobClose(Job *job) * job job to finish * status sub-why job went away * - * Results: - * None - * * Side Effects: * Final commands for the job are placed on postCommands. * @@ -981,16 +978,13 @@ JobClose(Job *job) * to ABORT_ERROR so no more jobs will be started. *----------------------------------------------------------------------- */ -/*ARGSUSED*/ static void JobFinish (Job *job, WAIT_T status) { - Boolean done, return_job_token; + Boolean done, return_job_token; - if (DEBUG(JOB)) { - fprintf(debug_file, "Jobfinish: %d [%s], status %d\n", - job->pid, job->node->name, status); - } + DEBUG3(JOB, "JobFinish: %d [%s], status %d\n", + job->pid, job->node->name, status); if ((WIFEXITED(status) && (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) || @@ -1035,10 +1029,8 @@ JobFinish (Job *job, WAIT_T status) if (done) { if (WIFEXITED(status)) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "Process %d [%s] exited.\n", - job->pid, job->node->name); - } + DEBUG2(JOB, "Process %d [%s] exited.\n", + job->pid, job->node->name); if (WEXITSTATUS(status) != 0) { if (job->node != lastNode) { MESSAGE(stdout, job->node); @@ -1109,28 +1101,23 @@ JobFinish (Job *job, WAIT_T status) /* * As long as we aren't aborting and the job didn't return a non-zero * status that we shouldn't ignore, we call Make_Update to update - * the parents. In addition, any saved commands for the node are placed - * on the .END target. + * the parents. */ - if (job->tailCmds != NULL) { - Lst_ForEachFrom(job->node->commands, job->tailCmds, - JobSaveCommand, - job->node); - } + JobSaveCommands(job); job->node->made = MADE; if (!(job->flags & JOB_SPECIAL)) return_job_token = TRUE; Make_Update(job->node); job->job_state = JOB_ST_FREE; } else if (WAIT_STATUS(status)) { - errors += 1; + errors++; job->job_state = JOB_ST_FREE; } /* * Set aborting if any error. */ - if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) { + if (errors && !opts.keepgoing && (aborting != ABORT_INTERRUPT)) { /* * If we found any errors in this batch of children and the -k flag * wasn't given, we set the aborting flag so no more jobs get @@ -1150,28 +1137,14 @@ JobFinish (Job *job, WAIT_T status) } } -/*- - *----------------------------------------------------------------------- - * Job_Touch -- - * Touch the given target. Called by JobStart when the -t flag was - * given +/* Touch the given target. Called by JobStart when the -t flag was given. * - * Input: - * gn the node of the file to touch - * silent TRUE if should not print message - * - * Results: - * None - * - * Side Effects: - * The data modification of the file is changed. In addition, if the - * file did not exist, it is created. - *----------------------------------------------------------------------- - */ + * The modification date of the file is changed. + * If the file did not exist, it is created. */ void Job_Touch(GNode *gn, Boolean silent) { - int streamID; /* ID of stream opened to do the touch */ + int streamID; /* ID of stream opened to do the touch */ struct utimbuf times; /* Times for utime() call */ if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL| @@ -1183,12 +1156,12 @@ Job_Touch(GNode *gn, Boolean silent) return; } - if (!silent || NoExecute(gn)) { + if (!silent || !GNode_ShouldExecute(gn)) { (void)fprintf(stdout, "touch %s\n", gn->name); (void)fflush(stdout); } - if (NoExecute(gn)) { + if (!GNode_ShouldExecute(gn)) { return; } @@ -1197,7 +1170,7 @@ Job_Touch(GNode *gn, Boolean silent) } else if (gn->type & OP_LIB) { Arch_TouchLib(gn); } else { - char *file = gn->path ? gn->path : gn->name; + const char *file = GNode_Path(gn); times.actime = times.modtime = now; if (utime(file, ×) < 0){ @@ -1226,10 +1199,10 @@ Job_Touch(GNode *gn, Boolean silent) } } -/*- - *----------------------------------------------------------------------- - * Job_CheckCommands -- - * Make sure the given node has all the commands it needs. +/* 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 @@ -1237,106 +1210,96 @@ Job_Touch(GNode *gn, Boolean silent) * * Results: * TRUE if the commands list is/was ok. - * - * Side Effects: - * The node will have commands from the .DEFAULT rule added to it - * if it needs them. - *----------------------------------------------------------------------- */ Boolean Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) { - if (OP_NOP(gn->type) && Lst_IsEmpty(gn->commands) && - ((gn->type & OP_LIB) == 0 || Lst_IsEmpty(gn->children))) { + if (GNode_IsTarget(gn)) + return TRUE; + if (!Lst_IsEmpty(gn->commands)) + return TRUE; + if ((gn->type & OP_LIB) && !Lst_IsEmpty(gn->children)) + return TRUE; + + /* + * No commands. Look for .DEFAULT rule from which we might infer + * commands + */ + if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) && + (gn->type & OP_SPECIAL) == 0) { /* - * No commands. Look for .DEFAULT rule from which we might infer - * commands + * Make only looks for a .DEFAULT if the node was never the + * target of an operator, so that's what we do too. If + * a .DEFAULT was given, we substitute its commands for gn's + * commands and set the IMPSRC variable to be the target's name + * The DEFAULT node acts like a transformation rule, in that + * gn also inherits any attributes or sources attached to + * .DEFAULT itself. */ - if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) && - (gn->type & OP_SPECIAL) == 0) { - char *p1; - /* - * Make only looks for a .DEFAULT if the node was never the - * target of an operator, so that's what we do too. If - * a .DEFAULT was given, we substitute its commands for gn's - * commands and set the IMPSRC variable to be the target's name - * The DEFAULT node acts like a transformation rule, in that - * gn also inherits any attributes or sources attached to - * .DEFAULT itself. - */ - Make_HandleUse(DEFAULT, gn); - Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), gn); - bmake_free(p1); - } else if (Dir_MTime(gn, 0) == 0 && (gn->type & OP_SPECIAL) == 0) { - /* - * The node wasn't the target of an operator we have no .DEFAULT - * rule to go on and the target doesn't already exist. There's - * nothing more we can do for this branch. If the -k flag wasn't - * given, we stop in our tracks, otherwise we just don't update - * this node's parents so they never get examined. - */ - static const char msg[] = ": don't know how to make"; - - if (gn->flags & FROM_DEPEND) { - if (!Job_RunTarget(".STALE", gn->fname)) - fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n", - progname, gn->fname, gn->lineno, makeDependfile, - gn->name); - return TRUE; - } + Make_HandleUse(DEFAULT, gn); + Var_Set(IMPSRC, GNode_VarTarget(gn), gn); + return TRUE; + } - if (gn->type & OP_OPTIONAL) { - (void)fprintf(stdout, "%s%s %s (ignored)\n", progname, - msg, gn->name); - (void)fflush(stdout); - } else if (keepgoing) { - (void)fprintf(stdout, "%s%s %s (continuing)\n", progname, - msg, gn->name); - (void)fflush(stdout); - return FALSE; - } else { - (*abortProc)("%s%s %s. Stop", progname, msg, gn->name); - return FALSE; - } - } + if (Dir_MTime(gn, 0) != 0 || (gn->type & OP_SPECIAL)) + return TRUE; + + /* + * The node wasn't the target of an operator. We have no .DEFAULT + * rule to go on and the target doesn't already exist. There's + * nothing more we can do for this branch. If the -k flag wasn't + * given, we stop in our tracks, otherwise we just don't update + * this node's parents so they never get examined. + */ + + if (gn->flags & FROM_DEPEND) { + if (!Job_RunTarget(".STALE", gn->fname)) + fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n", + progname, gn->fname, gn->lineno, makeDependfile, + gn->name); + return TRUE; } - return TRUE; + + if (gn->type & OP_OPTIONAL) { + (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", + progname, gn->name, "ignored"); + (void)fflush(stdout); + return TRUE; + } + + if (opts.keepgoing) { + (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", + progname, gn->name, "continuing"); + (void)fflush(stdout); + return FALSE; + } + + abortProc("%s: don't know how to make %s. Stop", progname, gn->name); + return FALSE; } -/*- - *----------------------------------------------------------------------- - * JobExec -- - * Execute the shell for the given job. Called from JobStart +/* Execute the shell for the given job. * - * Input: - * job Job to execute - * - * Results: - * None. - * - * Side Effects: - * A shell is executed, outputs is altered and the Job structure added - * to the job table. - * - *----------------------------------------------------------------------- + * A shell is executed, its output is altered and the Job structure added + * to the job table. */ static void JobExec(Job *job, char **argv) { - int cpid; /* ID of new child */ + int cpid; /* ID of new child */ sigset_t mask; job->flags &= ~JOB_TRACED; if (DEBUG(JOB)) { - int i; + int i; - (void)fprintf(debug_file, "Running %s %sly\n", job->node->name, "local"); - (void)fprintf(debug_file, "\tCommand: "); + debug_printf("Running %s %sly\n", job->node->name, "local"); + debug_printf("\tCommand: "); for (i = 0; argv[i] != NULL; i++) { - (void)fprintf(debug_file, "%s ", argv[i]); + debug_printf("%s ", argv[i]); } - (void)fprintf(debug_file, "\n"); + debug_printf("\n"); } /* @@ -1384,55 +1347,40 @@ JobExec(Job *job, char **argv) * reset it to the beginning (again). Since the stream was marked * close-on-exec, we must clear that bit in the new input. */ - if (dup2(FILENO(job->cmdFILE), 0) == -1) { - execError("dup2", "job->cmdFILE"); - _exit(1); - } - if (fcntl(0, F_SETFD, 0) == -1) { - execError("fcntl clear close-on-exec", "stdin"); - _exit(1); - } - if (lseek(0, (off_t)0, SEEK_SET) == -1) { - execError("lseek to 0", "stdin"); - _exit(1); - } + if (dup2(fileno(job->cmdFILE), 0) == -1) + execDie("dup2", "job->cmdFILE"); + if (fcntl(0, F_SETFD, 0) == -1) + execDie("fcntl clear close-on-exec", "stdin"); + if (lseek(0, (off_t)0, SEEK_SET) == -1) + execDie("lseek to 0", "stdin"); if (job->node->type & (OP_MAKE | OP_SUBMAKE)) { /* * Pass job token pipe to submakes. */ - if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) { - execError("clear close-on-exec", "tokenWaitJob.inPipe"); - _exit(1); - } - if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) { - execError("clear close-on-exec", "tokenWaitJob.outPipe"); - _exit(1); - } + 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) { - execError("dup2", "job->outPipe"); - _exit(1); - } + 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) { - execError("clear close-on-exec", "stdout"); - _exit(1); - } - if (dup2(1, 2) == -1) { - execError("dup2", "1, 2"); - _exit(1); - } + 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 @@ -1453,8 +1401,7 @@ JobExec(Job *job, char **argv) Var_ExportVars(); (void)execv(shellPath, argv); - execError("exec", shellPath); - _exit(1); + execDie("exec", shellPath); } /* Parent, continuing after the child exec */ @@ -1485,36 +1432,25 @@ JobExec(Job *job, char **argv) * Now the job is actually running, add it to the table. */ if (DEBUG(JOB)) { - fprintf(debug_file, "JobExec(%s): pid %d added to jobs table\n", - job->node->name, job->pid); + debug_printf("JobExec(%s): pid %d added to jobs table\n", + job->node->name, job->pid); job_table_dump("job started"); } JobSigUnlock(&mask); } -/*- - *----------------------------------------------------------------------- - * JobMakeArgv -- - * Create the argv needed to execute the shell for a given job. - * - * - * Results: - * - * Side Effects: - * - *----------------------------------------------------------------------- - */ +/* 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 */ + int argc; + static char args[10]; /* For merged arguments */ argv[0] = UNCONST(shellName); argc = 1; - if ((commandShell->exit && (*commandShell->exit != '-')) || - (commandShell->echo && (*commandShell->echo != '-'))) + if ((commandShell->exit && commandShell->exit[0] != '-') || + (commandShell->echo && commandShell->echo[0] != '-')) { /* * At least one of the flags doesn't have a minus before it, so @@ -1571,14 +1507,14 @@ JobMakeArgv(Job *job, char **argv) * Also the return value is ignored by everyone. *----------------------------------------------------------------------- */ -static int +static JobStartResult JobStart(GNode *gn, int flags) { - Job *job; /* new job descriptor */ - char *argv[10]; /* Argument vector to shell */ - Boolean cmdsOK; /* true if the nodes commands were all right */ - Boolean noExec; /* Set true if we decide not to run the job */ - int tfd; /* File descriptor to the temp file */ + Job *job; /* new job descriptor */ + char *argv[10]; /* Argument vector to shell */ + Boolean cmdsOK; /* true if the nodes commands were all right */ + Boolean noExec; /* Set true if we decide not to run the job */ + int tfd; /* File descriptor to the temp file */ for (job = job_table; job < job_table_end; job++) { if (job->job_state == JOB_ST_FREE) @@ -1622,8 +1558,8 @@ JobStart(GNode *gn, int flags) * need to reopen it to feed it to the shell. If the -n flag *was* given, * we just set the file to be stdout. Cute, huh? */ - if (((gn->type & OP_MAKE) && !(noRecursiveExecute)) || - (!noExecute && !touchFlag)) { + if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) || + (!opts.noExecute && !opts.touchFlag)) { /* * tfile is the name of a file into which all shell commands are * put. It is removed before the child shell is executed, unless @@ -1650,7 +1586,7 @@ JobStart(GNode *gn, int flags) if (job->cmdFILE == NULL) { Punt("Could not fdopen %s", tfile); } - (void)fcntl(FILENO(job->cmdFILE), F_SETFD, FD_CLOEXEC); + (void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC); /* * Send the commands to the command file, flush all its buffers then * rewind and remove the thing. @@ -1669,7 +1605,7 @@ JobStart(GNode *gn, int flags) * We can do all the commands at once. hooray for sanity */ numCommands = 0; - Lst_ForEach(gn->commands, JobPrintCommand, job); + JobPrintCommands(job); /* * If we didn't print out any commands to the shell script, @@ -1680,7 +1616,7 @@ JobStart(GNode *gn, int flags) } free(tfile); - } else if (NoExecute(gn)) { + } else if (!GNode_ShouldExecute(gn)) { /* * Not executing anything -- just print all the commands to stdout * in one fell swoop. This will still set up job->tailCmds correctly. @@ -1695,9 +1631,8 @@ JobStart(GNode *gn, int flags) * not -- just let the user know they're bad and keep going. It * doesn't do any harm in this case and may do some good. */ - if (cmdsOK) { - Lst_ForEach(gn->commands, JobPrintCommand, job); - } + if (cmdsOK) + JobPrintCommands(job); /* * Don't execute the shell, thank you. */ @@ -1736,12 +1671,8 @@ JobStart(GNode *gn, int flags) * We only want to work our way up the graph if we aren't here because * the commands for the job were no good. */ - if (cmdsOK && aborting == 0) { - if (job->tailCmds != NULL) { - Lst_ForEachFrom(job->node->commands, job->tailCmds, - JobSaveCommand, - job->node); - } + if (cmdsOK && aborting == ABORT_NONE) { + JobSaveCommands(job); job->node->made = MADE; Make_Update(job->node); } @@ -1763,19 +1694,14 @@ JobStart(GNode *gn, int flags) } static char * -JobOutput(Job *job, char *cp, char *endp, int msg) +JobOutput(Job *job, char *cp, char *endp) { char *ecp; - if (commandShell->noPrint) { - ecp = Str_FindSubstring(cp, commandShell->noPrint); - while (ecp != NULL) { + if (commandShell->noPrint && commandShell->noPrint[0] != '\0') { + while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) { if (cp != ecp) { *ecp = '\0'; - if (!beSilent && msg && job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; - } /* * The only way there wouldn't be a newline after * this line is if it were the last in the buffer. @@ -1785,7 +1711,7 @@ JobOutput(Job *job, char *cp, char *endp, int msg) (void)fprintf(stdout, "%s", cp); (void)fflush(stdout); } - cp = ecp + commandShell->noPLen; + cp = ecp + commandShell->noPrintLen; if (cp != endp) { /* * Still more to print, look again after skipping @@ -1796,7 +1722,6 @@ JobOutput(Job *job, char *cp, char *endp, int msg) while (*cp == ' ' || *cp == '\t' || *cp == '\n') { cp++; } - ecp = Str_FindSubstring(cp, commandShell->noPrint); } else { return cp; } @@ -1816,7 +1741,7 @@ JobOutput(Job *job, char *cp, char *endp, int msg) * this makes up a line, we print it tagged by the job's identifier, * as necessary. * If output has been collected in a temporary file, we open the - * file and read it line by line, transfering it to our own + * file and read it line by line, transferring it to our own * output channel until the file is empty. At which point we * remove the temporary file. * In both cases, however, we keep our figurative eye out for the @@ -1830,22 +1755,19 @@ JobOutput(Job *job, char *cp, char *endp, int msg) * finish TRUE if this is the last time we'll be called * for this job * - * Results: - * None - * * Side Effects: * curPos may be shifted as may the contents of outBuf. *----------------------------------------------------------------------- */ -STATIC void +static void JobDoOutput(Job *job, Boolean finish) { - Boolean gotNL = FALSE; /* true if got a newline */ - Boolean fbuf; /* true if our buffer filled up */ - int nr; /* number of bytes read */ - int i; /* auxiliary index into outBuf */ - int max; /* limit for i (end of current data) */ - int nRead; /* (Temporary) number of bytes read */ + Boolean gotNL = FALSE; /* true if got a newline */ + Boolean fbuf; /* true if our buffer filled up */ + size_t nr; /* number of bytes read */ + size_t i; /* auxiliary index into outBuf */ + size_t max; /* limit for i (end of current data) */ + ssize_t nRead; /* (Temporary) number of bytes read */ /* * Read as many bytes as will fit in the buffer. @@ -1864,7 +1786,7 @@ end_loop: } nr = 0; } else { - nr = nRead; + nr = (size_t)nRead; } /* @@ -1887,7 +1809,7 @@ end_loop: * TRUE. */ max = job->curPos + nr; - for (i = job->curPos + nr - 1; i >= job->curPos; i--) { + for (i = job->curPos + nr - 1; i >= job->curPos && i != (size_t)-1; i--) { if (job->outBuf[i] == '\n') { gotNL = TRUE; break; @@ -1925,7 +1847,7 @@ end_loop: if (i >= job->curPos) { char *cp; - cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE); + cp = JobOutput(job, job->outBuf, &job->outBuf[i]); /* * There's still more in that thar buffer. This time, though, @@ -1933,7 +1855,7 @@ end_loop: * our own free will. */ if (*cp != '\0') { - if (!beSilent && job->node != lastNode) { + if (!opts.beSilent && job->node != lastNode) { MESSAGE(stdout, job->node); lastNode = job->node; } @@ -1975,13 +1897,16 @@ end_loop: static void JobRun(GNode *targ) { -#ifdef notyet +#if 0 /* - * Unfortunately it is too complicated to run .BEGIN, .END, - * and .INTERRUPT job in the parallel job module. This has - * the nice side effect that it avoids a lot of other problems. + * 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. */ - Lst lst = Lst_Init(); + List *lst = Lst_New(); Lst_Append(lst, targ); (void)Make_Run(lst); Lst_Destroy(lst, NULL); @@ -1998,33 +1923,20 @@ JobRun(GNode *targ) #endif } -/*- - *----------------------------------------------------------------------- - * Job_CatchChildren -- - * Handle the exit of a child. Called from Make_Make. - * - * Input: - * block TRUE if should block on the wait +/* Handle the exit of a child. Called from Make_Make. * - * Results: - * none. - * - * Side Effects: - * The job descriptor is removed from the list of children. + * 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 */ + int pid; /* pid of dead child */ + WAIT_T status; /* Exit/termination status */ /* * Don't even bother if we know there's no one around. @@ -2033,10 +1945,8 @@ Job_CatchChildren(void) return; while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "Process %d exited/stopped status %x.\n", pid, - WAIT_STATUS(status)); - } + DEBUG2(JOB, "Process %d exited/stopped status %x.\n", pid, + WAIT_STATUS(status)); JobReapChild(pid, status, TRUE); } } @@ -2048,7 +1958,7 @@ Job_CatchChildren(void) void JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs) { - Job *job; /* job descriptor for dead child */ + Job *job; /* job descriptor for dead child */ /* * Don't even bother if we know there's no one around. @@ -2065,10 +1975,7 @@ JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs) return; /* not ours */ } if (WIFSTOPPED(status)) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "Process %d (%s) stopped.\n", - job->pid, job->node->name); - } + DEBUG2(JOB, "Process %d (%s) stopped.\n", job->pid, job->node->name); if (!make_suspended) { switch (WSTOPSIG(status)) { case SIGTSTP: @@ -2093,28 +2000,16 @@ JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs) JobFinish(job, status); } -/*- - *----------------------------------------------------------------------- - * Job_CatchOutput -- - * Catch the output from our children, if we're using - * pipes do so. Otherwise just block time until we get a - * signal(most likely a SIGCHLD) since there's no point in - * just spinning when there's nothing to do and the reaping - * of a child can wait for a while. - * - * Results: - * None - * - * Side Effects: - * Output is read from pipes if we're piping. - * ----------------------------------------------------------------------- - */ +/* 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; - int i; + unsigned int i; (void)fflush(stdout); @@ -2148,9 +2043,9 @@ Job_CatchOutput(void) Job_CatchChildren(); if (nready == 0) - return; + return; - for (i = npseudojobs*nfds_per_job(); i < nfds; i++) { + for (i = npseudojobs * nfds_per_job(); i < nfds; i++) { if (!fds[i].revents) continue; job = jobfds[i]; @@ -2169,24 +2064,12 @@ Job_CatchOutput(void) } #endif if (--nready == 0) - return; + return; } } -/*- - *----------------------------------------------------------------------- - * Job_Make -- - * Start the creation of a target. Basically a front-end for - * JobStart used by the Make module. - * - * Results: - * None. - * - * Side Effects: - * Another job is started. - * - *----------------------------------------------------------------------- - */ +/* Start the creation of a target. Basically a front-end for JobStart used by + * the Make module. */ void Job_Make(GNode *gn) { @@ -2211,21 +2094,21 @@ Shell_Init(void) #endif shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName); } - Var_Set_with_flags(".SHELL", shellPath, VAR_CMD, VAR_SET_READONLY); + Var_Set_with_flags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY); if (commandShell->exit == NULL) { commandShell->exit = ""; } if (commandShell->echo == NULL) { commandShell->echo = ""; } - if (commandShell->hasErrCtl && *commandShell->exit) { + if (commandShell->hasErrCtl && commandShell->exit[0] != '\0') { if (shellErrFlag && strcmp(commandShell->exit, &shellErrFlag[1]) != 0) { free(shellErrFlag); shellErrFlag = NULL; } if (!shellErrFlag) { - int n = strlen(commandShell->exit) + 2; + size_t n = strlen(commandShell->exit) + 2; shellErrFlag = bmake_malloc(n); if (shellErrFlag) { @@ -2238,10 +2121,8 @@ Shell_Init(void) } } -/*- - * Returns the string literal that is used in the current command shell - * to produce a newline character. - */ +/* Return the string literal that is used in the current command shell + * to produce a newline character. */ const char * Shell_GetNewline(void) { @@ -2257,8 +2138,9 @@ Job_SetPrefix(void) Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL); } - targPrefix = Var_Subst("${" MAKE_JOB_PREFIX "}", - VAR_GLOBAL, VARE_WANTRES); + (void)Var_Subst("${" MAKE_JOB_PREFIX "}", + VAR_GLOBAL, VARE_WANTRES, &targPrefix); + /* TODO: handle errors */ } /* Initialize the process module. */ @@ -2267,15 +2149,15 @@ Job_Init(void) { Job_SetPrefix(); /* Allocate space for all the job info */ - job_table = bmake_malloc(maxJobs * sizeof *job_table); - memset(job_table, 0, maxJobs * sizeof *job_table); - job_table_end = job_table + maxJobs; + job_table = bmake_malloc((size_t)opts.maxJobs * sizeof *job_table); + memset(job_table, 0, (size_t)opts.maxJobs * sizeof *job_table); + job_table_end = job_table + opts.maxJobs; wantToken = 0; - aborting = 0; - errors = 0; + aborting = ABORT_NONE; + errors = 0; - lastNode = NULL; + lastNode = NULL; /* * There is a non-zero chance that we already have children. @@ -2300,9 +2182,9 @@ Job_Init(void) /* Preallocate enough for the maximum number of jobs. */ fds = bmake_malloc(sizeof(*fds) * - (npseudojobs + maxJobs) * nfds_per_job()); + (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); jobfds = bmake_malloc(sizeof(*jobfds) * - (npseudojobs + maxJobs) * nfds_per_job()); + (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); /* These are permanent entries and take slots 0 and 1 */ watchfd(&tokenWaitJob); @@ -2318,7 +2200,7 @@ Job_Init(void) #define ADDSIG(s,h) \ if (bmake_signal(s, SIG_IGN) != SIG_IGN) { \ sigaddset(&caught_signals, s); \ - (void)bmake_signal(s, h); \ + (void)bmake_signal(s, h); \ } /* @@ -2344,7 +2226,9 @@ Job_Init(void) #undef ADDSIG (void)Job_RunTarget(".BEGIN", NULL); - postCommands = Targ_FindNode(".END", TARG_CREATE); + /* Create the .END node now, even though no code in the unit tests + * depends on it. See also Targ_GetEndNode in Compat_Run. */ + (void)Targ_GetEndNode(); } static void JobSigReset(void) @@ -2369,11 +2253,12 @@ static void JobSigReset(void) /* Find a shell in 'shells' given its name, or return NULL. */ static Shell * -JobMatchShell(const char *name) +FindShellByName(const char *name) { - Shell *sh; + Shell *sh = shells; + const Shell *shellsEnd = sh + sizeof shells / sizeof shells[0]; - for (sh = shells; sh->name != NULL; sh++) { + for (sh = shells; sh < shellsEnd; sh++) { if (strcmp(name, sh->name) == 0) return sh; } @@ -2406,24 +2291,24 @@ JobMatchShell(const char *name) * provides the functionality it does in C. Each word consists of * keyword and value separated by an equal sign. There should be no * unnecessary spaces in the word. The keywords are as follows: - * name Name of shell. - * path Location of shell. - * quiet Command to turn off echoing. - * echo Command to turn echoing on - * filter Result of turning off echoing that shouldn't be - * printed. - * echoFlag Flag to turn echoing on at the start - * errFlag Flag to turn error checking on at the start - * hasErrCtl True if shell has error checking control - * newline String literal to represent a newline char - * check Command to turn on error checking if hasErrCtl - * is TRUE or template of command to echo a command - * for which error checking is off if hasErrCtl is - * FALSE. - * ignore Command to turn off error checking if hasErrCtl - * is TRUE or template of command to execute a - * command so as to ignore any errors it returns if - * hasErrCtl is FALSE. + * name Name of shell. + * path Location of shell. + * quiet Command to turn off echoing. + * echo Command to turn echoing on + * filter Result of turning off echoing that shouldn't be + * printed. + * echoFlag Flag to turn echoing on at the start + * errFlag Flag to turn error checking on at the start + * hasErrCtl True if shell has error checking control + * newline String literal to represent a newline char + * check Command to turn on error checking if hasErrCtl + * is TRUE or template of command to echo a command + * for which error checking is off if hasErrCtl is + * FALSE. + * ignore Command to turn off error checking if hasErrCtl + * is TRUE or template of command to execute a + * command so as to ignore any errors it returns if + * hasErrCtl is FALSE. * *----------------------------------------------------------------------- */ @@ -2439,9 +2324,7 @@ Job_ParseShell(char *line) Boolean fullSpec = FALSE; Shell *sh; - while (isspace((unsigned char)*line)) { - line++; - } + pp_skip_whitespace(&line); free(shellArgv); @@ -2461,50 +2344,50 @@ Job_ParseShell(char *line) shellArgv = path; for (path = NULL, argv = words; argc != 0; argc--, argv++) { - if (strncmp(*argv, "path=", 5) == 0) { - path = &argv[0][5]; - } else if (strncmp(*argv, "name=", 5) == 0) { - newShell.name = &argv[0][5]; + char *arg = *argv; + if (strncmp(arg, "path=", 5) == 0) { + path = arg + 5; + } else if (strncmp(arg, "name=", 5) == 0) { + newShell.name = arg + 5; + } else { + if (strncmp(arg, "quiet=", 6) == 0) { + newShell.echoOff = arg + 6; + } else if (strncmp(arg, "echo=", 5) == 0) { + newShell.echoOn = arg + 5; + } else if (strncmp(arg, "filter=", 7) == 0) { + newShell.noPrint = arg + 7; + newShell.noPrintLen = strlen(newShell.noPrint); + } else if (strncmp(arg, "echoFlag=", 9) == 0) { + newShell.echo = arg + 9; + } else if (strncmp(arg, "errFlag=", 8) == 0) { + newShell.exit = arg + 8; + } else if (strncmp(arg, "hasErrCtl=", 10) == 0) { + char c = arg[10]; + newShell.hasErrCtl = c == 'Y' || c == 'y' || + c == 'T' || c == 't'; + } else if (strncmp(arg, "newline=", 8) == 0) { + newShell.newline = arg + 8; + } else if (strncmp(arg, "check=", 6) == 0) { + newShell.errOnOrEcho = arg + 6; + } else if (strncmp(arg, "ignore=", 7) == 0) { + newShell.errOffOrExecIgnore = arg + 7; + } else if (strncmp(arg, "errout=", 7) == 0) { + newShell.errExit = arg + 7; + } else if (strncmp(arg, "comment=", 8) == 0) { + newShell.commentChar = arg[8]; } else { - if (strncmp(*argv, "quiet=", 6) == 0) { - newShell.echoOff = &argv[0][6]; - } else if (strncmp(*argv, "echo=", 5) == 0) { - newShell.echoOn = &argv[0][5]; - } else if (strncmp(*argv, "filter=", 7) == 0) { - newShell.noPrint = &argv[0][7]; - newShell.noPLen = strlen(newShell.noPrint); - } else if (strncmp(*argv, "echoFlag=", 9) == 0) { - newShell.echo = &argv[0][9]; - } else if (strncmp(*argv, "errFlag=", 8) == 0) { - newShell.exit = &argv[0][8]; - } else if (strncmp(*argv, "hasErrCtl=", 10) == 0) { - char c = argv[0][10]; - newShell.hasErrCtl = !((c != 'Y') && (c != 'y') && - (c != 'T') && (c != 't')); - } else if (strncmp(*argv, "newline=", 8) == 0) { - newShell.newline = &argv[0][8]; - } else if (strncmp(*argv, "check=", 6) == 0) { - newShell.errCheck = &argv[0][6]; - } else if (strncmp(*argv, "ignore=", 7) == 0) { - newShell.ignErr = &argv[0][7]; - } else if (strncmp(*argv, "errout=", 7) == 0) { - newShell.errOut = &argv[0][7]; - } else if (strncmp(*argv, "comment=", 8) == 0) { - newShell.commentChar = argv[0][8]; - } else { - Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", - *argv); - free(words); - return FALSE; - } - fullSpec = TRUE; + Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", arg); + free(words); + return FALSE; } + fullSpec = TRUE; + } } if (path == NULL) { /* * If no path was given, the user wants one of the pre-defined shells, - * yes? So we find the one s/he wants with the help of JobMatchShell + * 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. */ @@ -2513,7 +2396,7 @@ Job_ParseShell(char *line) free(words); return FALSE; } else { - if ((sh = JobMatchShell(newShell.name)) == NULL) { + if ((sh = FindShellByName(newShell.name)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", newShell.name); free(words); @@ -2541,7 +2424,7 @@ Job_ParseShell(char *line) if (path == NULL) { path = UNCONST(shellPath); } else { - path += 1; + path++; } if (newShell.name != NULL) { shellName = newShell.name; @@ -2549,7 +2432,7 @@ Job_ParseShell(char *line) shellName = path; } if (!fullSpec) { - if ((sh = JobMatchShell(shellName)) == NULL) { + if ((sh = FindShellByName(shellName)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", shellName); free(words); @@ -2569,11 +2452,11 @@ Job_ParseShell(char *line) } if (!commandShell->hasErrCtl) { - if (commandShell->errCheck == NULL) { - commandShell->errCheck = ""; + if (commandShell->errOnOrEcho == NULL) { + commandShell->errOnOrEcho = ""; } - if (commandShell->ignErr == NULL) { - commandShell->ignErr = "%s\n"; + if (commandShell->errOffOrExecIgnore == NULL) { + commandShell->errOffOrExecIgnore = "%s\n"; } } @@ -2585,23 +2468,15 @@ Job_ParseShell(char *line) return TRUE; } -/*- - *----------------------------------------------------------------------- - * JobInterrupt -- - * Handle the receipt of an interrupt. +/* 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 - * - * Results: - * None - * - * Side Effects: - * All children are killed. Another job will be started if the - * .INTERRUPT target was given. - *----------------------------------------------------------------------- */ static void JobInterrupt(int runINTERRUPT, int signo) @@ -2623,21 +2498,18 @@ JobInterrupt(int runINTERRUPT, int signo) JobDeleteTarget(gn); if (job->pid) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, - "JobInterrupt passing signal %d to child %d.\n", - signo, job->pid); - } + DEBUG2(JOB, "JobInterrupt passing signal %d to child %d.\n", + signo, job->pid); KILLPG(job->pid, signo); } } JobSigUnlock(&mask); - if (runINTERRUPT && !touchFlag) { - interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); + if (runINTERRUPT && !opts.touchFlag) { + interrupt = Targ_FindNode(".INTERRUPT"); if (interrupt != NULL) { - ignoreErrors = FALSE; + opts.ignoreErrors = FALSE; JobRun(interrupt); } } @@ -2645,46 +2517,24 @@ JobInterrupt(int runINTERRUPT, int signo) exit(signo); } -/* - *----------------------------------------------------------------------- - * Job_Finish -- - * Do final processing such as the running of the commands - * attached to the .END target. - * - * Results: - * Number of errors reported. +/* Do the final processing, i.e. run the commands attached to the .END target. * - * Side Effects: - * None. - *----------------------------------------------------------------------- - */ + * Return the number of errors reported. */ int Job_Finish(void) { - if (postCommands != NULL && - (!Lst_IsEmpty(postCommands->commands) || - !Lst_IsEmpty(postCommands->children))) { + GNode *endNode = Targ_GetEndNode(); + if (!Lst_IsEmpty(endNode->commands) || !Lst_IsEmpty(endNode->children)) { if (errors) { Error("Errors reported so .END ignored"); } else { - JobRun(postCommands); + JobRun(endNode); } } return errors; } -/*- - *----------------------------------------------------------------------- - * Job_End -- - * Cleanup any memory used by the jobs module - * - * Results: - * None. - * - * Side Effects: - * Memory is freed - *----------------------------------------------------------------------- - */ +/* Clean up any memory used by the jobs module. */ void Job_End(void) { @@ -2693,20 +2543,8 @@ Job_End(void) #endif } -/*- - *----------------------------------------------------------------------- - * Job_Wait -- - * Waits for all running jobs to finish and returns. Sets 'aborting' - * to ABORT_WAIT to prevent other jobs from starting. - * - * Results: - * None. - * - * Side Effects: - * Currently running jobs finish. - * - *----------------------------------------------------------------------- - */ +/* Waits for all running jobs to finish and returns. + * Sets 'aborting' to ABORT_WAIT to prevent other jobs from starting. */ void Job_Wait(void) { @@ -2714,23 +2552,14 @@ Job_Wait(void) while (jobTokensRunning != 0) { Job_CatchOutput(); } - aborting = 0; + aborting = ABORT_NONE; } -/*- - *----------------------------------------------------------------------- - * Job_AbortAll -- - * Abort all currently running jobs without handling output or anything. - * This function is to be called only in the event of a major - * error. Most definitely NOT to be called from JobInterrupt. - * - * Results: - * None +/* 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. * - * Side Effects: - * All children are killed, not just the firstborn - *----------------------------------------------------------------------- - */ + * All children are killed, not just the firstborn. */ void Job_AbortAll(void) { @@ -2759,20 +2588,8 @@ Job_AbortAll(void) continue; } -/*- - *----------------------------------------------------------------------- - * JobRestartJobs -- - * Tries to restart stopped jobs if there are slots available. - * Called in process context in response to a SIGCONT. - * - * Results: - * None. - * - * Side Effects: - * Resumes jobs. - * - *----------------------------------------------------------------------- - */ +/* Tries to restart stopped jobs if there are slots available. + * Called in process context in response to a SIGCONT. */ static void JobRestartJobs(void) { @@ -2781,17 +2598,14 @@ JobRestartJobs(void) for (job = job_table; job < job_table_end; job++) { if (job->job_state == JOB_ST_RUNNING && (make_suspended || job->job_suspended)) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "Restarting stopped job pid %d.\n", - job->pid); - } + DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid); if (job->job_suspended) { (void)printf("*** [%s] Continued\n", job->node->name); (void)fflush(stdout); } job->job_suspended = 0; if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { - fprintf(debug_file, "Failed to send SIGCONT to %d\n", job->pid); + debug_printf("Failed to send SIGCONT to %d\n", job->pid); } } if (job->job_state == JOB_ST_FINISHED) @@ -2825,10 +2639,10 @@ watchfd(Job *job) static void clearfd(Job *job) { - int i; + size_t i; if (job->inPollfd == NULL) Punt("Unwatching unwatched job"); - i = job->inPollfd - fds; + i = (size_t)(job->inPollfd - fds); nfds--; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) { @@ -2867,18 +2681,8 @@ readyfd(Job *job) return (job->inPollfd->revents & POLLIN) != 0; } -/*- - *----------------------------------------------------------------------- - * JobTokenAdd -- - * Put a token into the job pipe so that some make process can start - * another job. - * - * Side Effects: - * Allows more build jobs to be spawned somewhere. - * - *----------------------------------------------------------------------- - */ - +/* Put a token (back) into the job pipe. + * This allows a make process to start a build job. */ static void JobTokenAdd(void) { @@ -2888,21 +2692,13 @@ JobTokenAdd(void) while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1) continue; - if (DEBUG(JOB)) - fprintf(debug_file, "(%d) aborting %d, deposit token %c\n", - getpid(), aborting, JOB_TOKENS[aborting]); + DEBUG3(JOB, "(%d) aborting %d, deposit token %c\n", + getpid(), aborting, tok); while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; } -/*- - *----------------------------------------------------------------------- - * Job_ServerStartTokenAdd -- - * Prep the job token pipe in the root make process. - * - *----------------------------------------------------------------------- - */ - +/* Prep the job token pipe in the root make process. */ void Job_ServerStart(int max_tokens, int jp_0, int jp_1) { @@ -2938,14 +2734,7 @@ Job_ServerStart(int max_tokens, int jp_0, int jp_1) JobTokenAdd(); } -/*- - *----------------------------------------------------------------------- - * Job_TokenReturn -- - * Return a withdrawn token to the pool. - * - *----------------------------------------------------------------------- - */ - +/* Return a withdrawn token to the pool. */ void Job_TokenReturn(void) { @@ -2956,35 +2745,24 @@ Job_TokenReturn(void) JobTokenAdd(); } -/*- - *----------------------------------------------------------------------- - * Job_TokenWithdraw -- - * Attempt to withdraw a token from the pool. +/* Attempt to withdraw a token from the pool. * - * Results: - * Returns TRUE if a token was withdrawn, and FALSE if the pool - * is currently empty. - * - * Side Effects: - * If pool is empty, set wantToken so that we wake up - * when a token is released. + * If pool is empty, set wantToken so that we wake up when a token is + * released. * - *----------------------------------------------------------------------- - */ - - + * Returns TRUE if a token was withdrawn, and FALSE if the pool is currently + * empty. */ Boolean Job_TokenWithdraw(void) { char tok, tok1; - int count; + ssize_t count; wantToken = 0; - if (DEBUG(JOB)) - fprintf(debug_file, "Job_TokenWithdraw(%d): aborting %d, running %d\n", - getpid(), aborting, jobTokensRunning); + DEBUG3(JOB, "Job_TokenWithdraw(%d): aborting %d, running %d\n", + getpid(), aborting, jobTokensRunning); - if (aborting || (jobTokensRunning >= maxJobs)) + if (aborting != ABORT_NONE || (jobTokensRunning >= opts.maxJobs)) return FALSE; count = read(tokenWaitJob.inPipe, &tok, 1); @@ -2994,15 +2772,13 @@ Job_TokenWithdraw(void) if (errno != EAGAIN) { Fatal("job pipe read: %s", strerror(errno)); } - if (DEBUG(JOB)) - fprintf(debug_file, "(%d) blocked for token\n", getpid()); + DEBUG1(JOB, "(%d) blocked for token\n", getpid()); return FALSE; } if (count == 1 && tok != '+') { /* make being abvorted - remove any other job tokens */ - if (DEBUG(JOB)) - fprintf(debug_file, "(%d) aborted by token %c\n", getpid(), tok); + DEBUG2(JOB, "(%d) aborted by token %c\n", getpid(), tok); while (read(tokenWaitJob.inPipe, &tok1, 1) == 1) continue; /* And put the stopper back */ @@ -3019,29 +2795,17 @@ Job_TokenWithdraw(void) continue; jobTokensRunning++; - if (DEBUG(JOB)) - fprintf(debug_file, "(%d) withdrew token\n", getpid()); + DEBUG1(JOB, "(%d) withdrew token\n", getpid()); return TRUE; } -/*- - *----------------------------------------------------------------------- - * Job_RunTarget -- - * Run the named target if found. If a filename is specified, then - * set that to the sources. - * - * Results: - * None - * - * Side Effects: - * exits if the target fails. +/* Run the named target if found. If a filename is specified, then set that + * to the sources. * - *----------------------------------------------------------------------- - */ + * Exits if the target fails. */ Boolean Job_RunTarget(const char *target, const char *fname) { - GNode *gn = Targ_FindNode(target, TARG_NOCREATE); - + GNode *gn = Targ_FindNode(target); if (gn == NULL) return FALSE; @@ -1,4 +1,4 @@ -/* $NetBSD: job.h,v 1.47 2020/08/29 12:20:17 rillig Exp $ */ +/* $NetBSD: job.h,v 1.58 2020/10/26 21:34:10 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -73,10 +73,10 @@ * from: @(#)job.h 8.1 (Berkeley) 6/6/93 */ -/*- - * job.h -- - * Definitions pertaining to the running of jobs in parallel mode. +/* + * Running of jobs in parallel mode. */ + #ifndef MAKE_JOB_H #define MAKE_JOB_H @@ -110,28 +110,6 @@ emul_poll(struct pollfd *fd, int nfd, int timeout); */ #define POLL_MSEC 5000 -/*- - * Job Table definitions. - * - * Each job has several things associated with it: - * 1) The process id of the child shell - * 2) The graph node describing the target being made by this job - * 3) A LstNode for the first command to be saved after the job - * completes. This is NULL if there was no "..." in the job's - * commands. - * 4) An FILE* for writing out the commands. This is only - * used before the job is actually started. - * 5) The output is being caught via a pipe and - * the descriptors of our pipe, an array in which output is line - * buffered and the current position in that buffer are all - * maintained for each job. - * 6) A word of flags which determine how the module handles errors, - * echoing, etc. for the job - * - * When a job is finished, the Make_Update function is called on each of the - * parents of the node which was just remade. This takes care of the upward - * traversal of the dependency graph. - */ struct pollfd; @@ -139,122 +117,90 @@ struct pollfd; # include "meta.h" #endif -#define JOB_BUFSIZE 1024 +typedef enum JobState { + JOB_ST_FREE = 0, /* Job is available */ + JOB_ST_SETUP = 1, /* Job is allocated but otherwise invalid */ + JOB_ST_RUNNING = 3, /* Job is running, pid valid */ + JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */ +} JobState; + +typedef enum JobFlags { + /* Ignore non-zero exits */ + JOB_IGNERR = 0x001, + /* no output */ + JOB_SILENT = 0x002, + /* Target is a special one. i.e. run it locally + * if we can't export it and maxLocal is 0 */ + JOB_SPECIAL = 0x004, + /* Ignore "..." lines when processing commands */ + JOB_IGNDOTS = 0x008, + /* we've sent 'set -x' */ + JOB_TRACED = 0x400 +} JobFlags; + +/* A Job manages the shell commands that are run to create a single target. + * Each job is run in a separate subprocess by a shell. Several jobs can run + * in parallel. + * + * The shell commands for the target are written to a temporary file, + * then the shell is run with the temporary file as stdin, and the output + * of that shell is captured via a pipe. + * + * When a job is finished, Make_Update updates all parents of the node + * that was just remade, marking them as ready to be made next if all + * other dependencies are finished as well. */ typedef struct Job { - int pid; /* The child's process ID */ - GNode *node; /* The target the child is making */ - LstNode tailCmds; /* The node of the first command to be - * saved when the job has been run */ - FILE *cmdFILE; /* When creating the shell script, this is - * where the commands go */ - int exit_status; /* from wait4() in signal handler */ - char job_state; /* status of the job entry */ -#define JOB_ST_FREE 0 /* Job is available */ -#define JOB_ST_SETUP 1 /* Job is allocated but otherwise invalid */ -#define JOB_ST_RUNNING 3 /* Job is running, pid valid */ -#define JOB_ST_FINISHED 4 /* Job is done (ie after SIGCHILD) */ - char job_suspended; - short flags; /* Flags to control treatment of job */ -#define JOB_IGNERR 0x001 /* Ignore non-zero exits */ -#define JOB_SILENT 0x002 /* no output */ -#define JOB_SPECIAL 0x004 /* Target is a special one. i.e. run it locally - * if we can't export it and maxLocal is 0 */ -#define JOB_IGNDOTS 0x008 /* Ignore "..." lines when processing - * commands */ -#define JOB_TRACED 0x400 /* we've sent 'set -x' */ + /* The process ID of the shell running the commands */ + int pid; + + /* The target the child is making */ + GNode *node; + + /* If one of the shell commands is "...", all following commands are + * delayed until the .END node is made. This list node points to the + * first of these commands, if any. */ + StringListNode *tailCmds; + + /* When creating the shell script, this is where the commands go. + * This is only used before the job is actually started. */ + FILE *cmdFILE; - int jobPipe[2]; /* Pipe for reading output from job */ + int exit_status; /* from wait4() in signal handler */ + + JobState job_state; /* status of the job entry */ + + char job_suspended; + + JobFlags flags; /* Flags to control treatment of job */ + + int inPipe; /* Pipe for reading output from job */ + int outPipe; /* Pipe for writing control commands */ struct pollfd *inPollfd; /* pollfd associated with inPipe */ - char outBuf[JOB_BUFSIZE + 1]; - /* Buffer for storing the output of the - * job, line by line */ - int curPos; /* Current position in op_outBuf */ + +#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; + struct BuildMon bm; #endif } Job; -#define inPipe jobPipe[0] -#define outPipe jobPipe[1] - -/*- - * Shell Specifications: - * Each shell type has associated with it the following information: - * 1) The string which must match the last character of the shell name - * for the shell to be considered of this type. The longest match - * wins. - * 2) A command to issue to turn off echoing of command lines - * 3) A command to issue to turn echoing back on again - * 4) What the shell prints, and its length, when given the echo-off - * command. This line will not be printed when received from the shell - * 5) A boolean to tell if the shell has the ability to control - * error checking for individual commands. - * 6) The string to turn this checking on. - * 7) The string to turn it off. - * 8) The command-flag to give to cause the shell to start echoing - * commands right away. - * 9) The command-flag to cause the shell to Lib_Exit when an error is - * detected in one of the commands. - * - * Some special stuff goes on if a shell doesn't have error control. In such - * a case, errCheck becomes a printf template for echoing the command, - * should echoing be on and ignErr becomes another printf template for - * executing the command while ignoring the return status. Finally errOut - * is a printf template for running the command and causing the shell to - * exit on error. If any of these strings are empty when hasErrCtl is FALSE, - * the command will be executed anyway as is and if it causes an error, so be - * it. Any templates setup to echo the command will escape any '$ ` \ "'i - * characters in the command string to avoid common problems with - * echo "%s\n" as a template. - */ -typedef struct Shell { - const char *name; /* 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. - */ - Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */ - const char *echoOff; /* command to turn off echo */ - const char *echoOn; /* command to turn it back on again */ - const char *noPrint; /* command to skip when printing output from - * shell. This is usually the command which - * was executed to turn off echoing */ - int noPLen; /* length of noPrint command */ - Boolean hasErrCtl; /* set if can control error checking for - * individual commands */ - const char *errCheck; /* string to turn error checking on */ - const char *ignErr; /* string to turn off error checking */ - const char *errOut; /* string to use for testing exit code */ - const char *newline; /* string literal that results in a newline - * character when it appears outside of any - * 'quote' or "quote" characters */ - char commentChar; /* character used by shell for comment lines */ - - /* - * command-line flags - */ - const char *echo; /* echo commands */ - const char *exit; /* exit on error */ -} Shell; - extern const char *shellPath; extern const char *shellName; extern char *shellErrFlag; -extern int jobTokensRunning; /* tokens currently "out" */ -extern int maxJobs; /* Max jobs we can run */ +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 *, ...)); +Boolean Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...)); void Job_CatchChildren(void); void Job_CatchOutput(void); void Job_Make(GNode *); void Job_Init(void); -Boolean Job_Empty(void); Boolean Job_ParseShell(char *); int Job_Finish(void); void Job_End(void); @@ -1,4 +1,4 @@ -/* $NetBSD: lst.c,v 1.60 2020/08/31 05:56:02 rillig Exp $ */ +/* $NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -32,9 +32,9 @@ * SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif +#include "make.h" + +MAKE_RCSID("$NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $"); #ifdef HAVE_INTTYPES_H #include <inttypes.h> @@ -42,110 +42,34 @@ #include <stdint.h> #endif -#include "make.h" - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lst.c,v 1.60 2020/08/31 05:56:02 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -__RCSID("$NetBSD: lst.c,v 1.60 2020/08/31 05:56:02 rillig Exp $"); -#endif /* not lint */ -#endif - -struct ListNode { - struct ListNode *prev; /* previous element in list */ - struct ListNode *next; /* next in list */ - uint8_t useCount; /* Count of functions using the node. - * node may not be deleted until count - * goes to 0 */ - Boolean deleted; /* List node should be removed when done */ - union { - void *datum; /* datum associated with this element */ - const GNode *gnode; /* alias, just for debugging */ - const char *str; /* alias, just for debugging */ - }; -}; - -typedef enum { - Head, Middle, Tail, Unknown -} Where; - -struct List { - LstNode first; /* first node in list */ - LstNode last; /* last node in list */ - - /* fields for sequential access */ - Boolean isOpen; /* true if list has been Lst_Open'ed */ - Where lastAccess; /* Where in the list the last access was */ - LstNode curr; /* current node, if open. NULL if - * *just* opened */ - LstNode prev; /* Previous node, if open. Used by Lst_Remove */ -}; - -/* Allocate and initialize a list node. - * - * The fields 'prev' and 'next' must be initialized by the caller. - */ -static LstNode -LstNodeNew(void *datum) +static ListNode * +LstNodeNew(ListNode *prev, ListNode *next, void *datum) { - LstNode node = bmake_malloc(sizeof *node); - node->useCount = 0; - node->deleted = FALSE; + ListNode *node = bmake_malloc(sizeof *node); + node->prev = prev; + node->next = next; node->datum = datum; return node; } -static Boolean -LstIsEmpty(Lst list) -{ - return list->first == NULL; -} - /* Create and initialize a new, empty list. */ -Lst -Lst_Init(void) +List * +Lst_New(void) { - Lst list = bmake_malloc(sizeof *list); + List *list = bmake_malloc(sizeof *list); list->first = NULL; list->last = NULL; - list->isOpen = FALSE; - list->lastAccess = Unknown; return list; } -/* Duplicate an entire list, usually by copying the datum pointers. - * If copyProc is given, that function is used to create the new datum from the - * old datum, usually by creating a copy of it. */ -Lst -Lst_Copy(Lst list, LstCopyProc copyProc) -{ - Lst newList; - LstNode node; - - assert(list != NULL); - - newList = Lst_Init(); - - for (node = list->first; node != NULL; node = node->next) { - void *datum = copyProc != NULL ? copyProc(node->datum) : node->datum; - Lst_Append(newList, datum); - } - - return newList; -} - -/* Free a list and all its nodes. The list data itself are not freed though. */ +/* Free a list and all its nodes. The node data are not freed though. */ void -Lst_Free(Lst list) +Lst_Free(List *list) { - LstNode node; - LstNode next; - - assert(list != NULL); + ListNode *node; + ListNode *next; for (node = list->first; node != NULL; node = next) { next = node->next; @@ -158,13 +82,10 @@ Lst_Free(Lst list) /* Destroy a list and free all its resources. The freeProc is called with the * datum from each node in turn before the node is freed. */ void -Lst_Destroy(Lst list, LstFreeProc freeProc) +Lst_Destroy(List *list, LstFreeProc freeProc) { - LstNode node; - LstNode next; - - assert(list != NULL); - assert(freeProc != NULL); + ListNode *node; + ListNode *next; for (node = list->first; node != NULL; node = next) { next = node->next; @@ -179,44 +100,33 @@ Lst_Destroy(Lst list, LstFreeProc freeProc) * Functions to modify a list */ -/* Insert a new node with the given piece of data before the given node in the - * given list. */ +/* Insert a new node with the datum before the given node. */ void -Lst_InsertBefore(Lst list, LstNode node, void *datum) +Lst_InsertBefore(List *list, ListNode *node, void *datum) { - LstNode newNode; + ListNode *newNode; - assert(list != NULL); - assert(!LstIsEmpty(list)); - assert(node != NULL); assert(datum != NULL); - newNode = LstNodeNew(datum); - newNode->prev = node->prev; - newNode->next = node; + newNode = LstNodeNew(node->prev, node, datum); - if (node->prev != NULL) { + if (node->prev != NULL) node->prev->next = newNode; - } node->prev = newNode; - if (node == list->first) { + if (node == list->first) list->first = newNode; - } } /* Add a piece of data at the start of the given list. */ void -Lst_Prepend(Lst list, void *datum) +Lst_Prepend(List *list, void *datum) { - LstNode node; + ListNode *node; - assert(list != NULL); assert(datum != NULL); - node = LstNodeNew(datum); - node->prev = NULL; - node->next = list->first; + node = LstNodeNew(NULL, list->first, datum); if (list->first == NULL) { list->first = node; @@ -229,16 +139,13 @@ Lst_Prepend(Lst list, void *datum) /* Add a piece of data at the end of the given list. */ void -Lst_Append(Lst list, void *datum) +Lst_Append(List *list, void *datum) { - LstNode node; + ListNode *node; - assert(list != NULL); assert(datum != NULL); - node = LstNodeNew(datum); - node->prev = list->last; - node->next = NULL; + node = LstNodeNew(list->last, NULL, datum); if (list->last == NULL) { list->first = node; @@ -252,265 +159,83 @@ Lst_Append(Lst list, void *datum) /* 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(Lst list, LstNode node) +Lst_Remove(List *list, ListNode *node) { - assert(list != NULL); - assert(node != NULL); - - /* - * unlink it from the list - */ - if (node->next != NULL) { + /* unlink it from its neighbors */ + if (node->next != NULL) node->next->prev = node->prev; - } - if (node->prev != NULL) { + if (node->prev != NULL) node->prev->next = node->next; - } - /* - * if either the first or last of the list point to this node, - * adjust them accordingly - */ - if (list->first == node) { + /* unlink it from the list */ + if (list->first == node) list->first = node->next; - } - if (list->last == node) { + if (list->last == node) list->last = node->prev; - } - - /* - * Sequential access stuff. If the node we're removing is the current - * node in the list, reset the current node to the previous one. If the - * previous one was non-existent (prev == NULL), we set the - * end to be Unknown, since it is. - */ - if (list->isOpen && list->curr == node) { - list->curr = list->prev; - if (list->curr == NULL) { - list->lastAccess = Unknown; - } - } - - /* - * note that the datum is unmolested. The caller must free it as - * necessary and as expected. - */ - if (node->useCount == 0) { - free(node); - } else { - node->deleted = TRUE; - } } /* Replace the datum in the given node with the new datum. */ void -LstNode_Set(LstNode node, void *datum) +LstNode_Set(ListNode *node, void *datum) { - assert(node != NULL); assert(datum != NULL); node->datum = datum; } -/* Replace the datum in the given node to NULL. */ +/* Replace the datum in the given node to NULL. + * Having NULL values in a list is unusual though. */ void -LstNode_SetNull(LstNode node) +LstNode_SetNull(ListNode *node) { - assert(node != NULL); - node->datum = NULL; } - -/* - * Node-specific functions - */ - -/* Return the first node from the given list, or NULL if the list is empty. */ -LstNode -Lst_First(Lst list) -{ - assert(list != NULL); - - return list->first; -} - -/* Return the last node from the given list, or NULL if the list is empty. */ -LstNode -Lst_Last(Lst list) -{ - assert(list != NULL); - - return list->last; -} - -/* Return the successor to the given node on its list, or NULL. */ -LstNode -LstNode_Next(LstNode node) -{ - assert(node != NULL); - - return node->next; -} - -/* Return the predecessor to the given node on its list, or NULL. */ -LstNode -LstNode_Prev(LstNode node) -{ - assert(node != NULL); - return node->prev; -} - -/* Return the datum stored in the given node. */ -void * -LstNode_Datum(LstNode node) -{ - assert(node != NULL); - return node->datum; -} - - /* * Functions for entire lists */ -/* Return TRUE if the given list is empty. */ -Boolean -Lst_IsEmpty(Lst list) -{ - assert(list != NULL); - - return LstIsEmpty(list); -} - -/* Return the first node from the list for which the match function returns - * TRUE, or NULL if none of the nodes matched. */ -LstNode -Lst_Find(Lst list, LstFindProc match, const void *matchArgs) -{ - return Lst_FindFrom(list, Lst_First(list), match, matchArgs); -} - -/* Return the first node from the list, starting at the given node, for which - * the match function returns TRUE, or NULL if none of the nodes matches. - * - * The start node may be NULL, in which case nothing is found. This allows - * for passing Lst_First or LstNode_Next as the start node. */ -LstNode -Lst_FindFrom(Lst list, LstNode node, LstFindProc match, const void *matchArgs) -{ - LstNode tln; - - assert(list != NULL); - assert(match != NULL); - - for (tln = node; tln != NULL; tln = tln->next) { - if (match(tln->datum, matchArgs)) - return tln; - } - - return NULL; -} - /* Return the first node that contains the given datum, or NULL. */ -LstNode -Lst_FindDatum(Lst list, const void *datum) +ListNode * +Lst_FindDatum(List *list, const void *datum) { - LstNode node; + ListNode *node; - assert(list != NULL); assert(datum != NULL); - for (node = list->first; node != NULL; node = node->next) { - if (node->datum == datum) { + for (node = list->first; node != NULL; node = node->next) + if (node->datum == datum) return node; - } - } return NULL; } -/* Apply the given function to each element of the given list. The function - * should return 0 if traversal should continue and non-zero if it should - * abort. */ -int -Lst_ForEach(Lst list, LstActionProc proc, void *procData) -{ - if (LstIsEmpty(list)) - return 0; /* XXX: Document what this value means. */ - return Lst_ForEachFrom(list, Lst_First(list), proc, procData); -} - -/* Apply the given function to each element of the given list, starting from - * the given node. The function should return 0 if traversal should continue, - * and non-zero if it should abort. */ int -Lst_ForEachFrom(Lst list, LstNode node, - LstActionProc proc, void *procData) +Lst_ForEachUntil(List *list, LstActionUntilProc proc, void *procData) { - LstNode tln = node; - LstNode next; - Boolean done; - int result; - - assert(list != NULL); - assert(node != NULL); - assert(proc != NULL); - - do { - /* - * Take care of having the current element deleted out from under - * us. - */ - - next = tln->next; - - /* - * We're done with the traversal if - * - the next node to examine doesn't exist and - * - nothing's been added after the current node (check this - * after proc() has been called). - */ - done = next == NULL; - - tln->useCount++; - result = (*proc)(tln->datum, procData); - tln->useCount--; - - /* - * Now check whether a node has been added. - * Note: this doesn't work if this node was deleted before - * the new node was added. - */ - if (next != tln->next) { - next = tln->next; - done = 0; - } - - if (tln->deleted) { - free((char *)tln); - } - tln = next; - } while (!result && !LstIsEmpty(list) && !done); + ListNode *node; + int result = 0; + for (node = list->first; node != NULL; node = node->next) { + result = proc(node->datum, procData); + if (result != 0) + break; + } return result; } /* Move all nodes from list2 to the end of list1. * List2 is destroyed and freed. */ void -Lst_MoveAll(Lst list1, Lst list2) +Lst_MoveAll(List *list1, List *list2) { - assert(list1 != NULL); - assert(list2 != NULL); - if (list2->first != NULL) { list2->first->prev = list1->last; - if (list1->last != NULL) { + if (list1->last != NULL) list1->last->next = list2->first; - } else { + else list1->first = list2->first; - } + list1->last = list2->last; } free(list2); @@ -518,124 +243,77 @@ Lst_MoveAll(Lst list1, Lst list2) /* Copy the element data from src to the start of dst. */ void -Lst_PrependAll(Lst dst, Lst src) +Lst_PrependAll(List *dst, List *src) { - LstNode node; + ListNode *node; for (node = src->last; node != NULL; node = node->prev) Lst_Prepend(dst, node->datum); } /* Copy the element data from src to the end of dst. */ void -Lst_AppendAll(Lst dst, Lst src) +Lst_AppendAll(List *dst, List *src) { - LstNode node; + ListNode *node; for (node = src->first; node != NULL; node = node->next) Lst_Append(dst, node->datum); } /* - * these functions are for dealing with a list as a table, of sorts. - * An idea of the "current element" is kept and used by all the functions - * between Lst_Open() and Lst_Close(). - * - * The sequential functions access the list in a slightly different way. - * CurPtr points to their idea of the current node in the list and they - * access the list based on it. + * for using the list as a queue */ -/* Open a list for sequential access. A list can still be searched, etc., - * without confusing these functions. */ +/* Add the datum to the tail of the given list. */ void -Lst_Open(Lst list) +Lst_Enqueue(List *list, void *datum) { - assert(list != NULL); - assert(!list->isOpen); - - list->isOpen = TRUE; - list->lastAccess = LstIsEmpty(list) ? Head : Unknown; - list->curr = NULL; + Lst_Append(list, datum); } -/* Return the next node for the given list, or NULL if the end has been - * reached. */ -LstNode -Lst_Next(Lst list) +/* Remove and return the datum at the head of the given list. */ +void * +Lst_Dequeue(List *list) { - LstNode node; - - assert(list != NULL); - assert(list->isOpen); - - list->prev = list->curr; - - if (list->curr == NULL) { - if (list->lastAccess == Unknown) { - /* - * If we're just starting out, lastAccess will be Unknown. - * Then we want to start this thing off in the right - * direction -- at the start with lastAccess being Middle. - */ - list->curr = node = list->first; - list->lastAccess = Middle; - } else { - node = NULL; - list->lastAccess = Tail; - } - } else { - node = list->curr->next; - list->curr = node; - - if (node == list->first || node == NULL) { - /* - * If back at the front, then we've hit the end... - */ - list->lastAccess = Tail; - } else { - /* - * Reset to Middle if gone past first. - */ - list->lastAccess = Middle; - } - } - - return node; + void *datum = list->first->datum; + Lst_Remove(list, list->first); + assert(datum != NULL); /* since NULL would mean end of the list */ + return datum; } -/* Close a list which was opened for sequential access. */ void -Lst_Close(Lst list) +Vector_Init(Vector *v, size_t itemSize) { - assert(list != NULL); - assert(list->isOpen); - - list->isOpen = FALSE; - list->lastAccess = Unknown; + v->len = 0; + v->priv_cap = 10; + v->itemSize = itemSize; + v->items = bmake_malloc(v->priv_cap * v->itemSize); } - -/* - * for using the list as a queue - */ - -/* Add the datum to the tail of the given list. */ -void -Lst_Enqueue(Lst list, void *datum) +/* 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) { - Lst_Append(list, datum); + if (v->len >= v->priv_cap) { + v->priv_cap *= 2; + v->items = bmake_realloc(v->items, v->priv_cap * v->itemSize); + } + v->len++; + return Vector_Get(v, v->len - 1); } -/* Remove and return the datum at the head of the given list. */ +/* Return the pointer to the last item in the vector. + * The returned data is valid until the next modifying operation. */ void * -Lst_Dequeue(Lst list) +Vector_Pop(Vector *v) { - void *datum; - - assert(list != NULL); - assert(!LstIsEmpty(list)); + assert(v->len > 0); + v->len--; + return Vector_Get(v, v->len); +} - datum = list->first->datum; - Lst_Remove(list, list->first); - assert(datum != NULL); - return datum; +void +Vector_Done(Vector *v) +{ + free(v->items); } @@ -1,4 +1,4 @@ -/* $NetBSD: lst.h,v 1.60 2020/09/02 23:33:13 rillig Exp $ */ +/* $NetBSD: lst.h,v 1.84 2020/10/28 02:43:16 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -79,101 +79,109 @@ #define MAKE_LST_H #include <sys/param.h> +#include <stdint.h> #include <stdlib.h> /* A doubly-linked list of pointers. */ -typedef struct List *Lst; +typedef struct List List; /* A single node in the doubly-linked list. */ -typedef struct ListNode *LstNode; +typedef struct ListNode ListNode; + +struct ListNode { + ListNode *prev; /* previous node in list, or NULL */ + ListNode *next; /* next node in list, or NULL */ + union { + void *datum; /* datum associated with this element */ + const struct GNode *priv_gnode; /* alias, just for debugging */ + const char *priv_str; /* alias, just for debugging */ + }; +}; + +struct List { + ListNode *first; /* first node in list */ + ListNode *last; /* last node in list */ +}; -/* Copy a node, usually by allocating a copy of the given object. - * For reference-counted objects, the original object may need to be - * modified, therefore the parameter is not const. */ -typedef void *LstCopyProc(void *); /* Free the datum of a node, called before freeing the node itself. */ typedef void LstFreeProc(void *); -/* Return TRUE if the datum matches the args, for Lst_Find. */ -typedef Boolean LstFindProc(const void *datum, const void *args); -/* An action for Lst_ForEach. */ -typedef int LstActionProc(void *datum, void *args); +/* An action for Lst_ForEachUntil and Lst_ForEachUntilConcurrent. */ +typedef int LstActionUntilProc(void *datum, void *args); /* Create or destroy a list */ /* Create a new list. */ -Lst Lst_Init(void); -/* Duplicate an existing list. */ -Lst Lst_Copy(Lst, LstCopyProc); +List *Lst_New(void); /* Free the list, leaving the node data unmodified. */ -void Lst_Free(Lst); +void Lst_Free(List *); /* Free the list, freeing the node data using the given function. */ -void Lst_Destroy(Lst, LstFreeProc); +void Lst_Destroy(List *, LstFreeProc); /* Get information about a list */ -Boolean Lst_IsEmpty(Lst); -/* Return the first node of the list, or NULL. */ -LstNode Lst_First(Lst); -/* Return the last node of the list, or NULL. */ -LstNode Lst_Last(Lst); -/* Find the first node for which the function returns TRUE, or NULL. */ -LstNode Lst_Find(Lst, LstFindProc, const void *); -/* Find the first node for which the function returns TRUE, or NULL. - * The search starts at the given node, towards the end of the list. */ -LstNode Lst_FindFrom(Lst, LstNode, LstFindProc, const void *); +static inline MAKE_ATTR_UNUSED Boolean +Lst_IsEmpty(List *list) { return list->first == NULL; } + /* Find the first node that contains the given datum, or NULL. */ -LstNode Lst_FindDatum(Lst, const void *); +ListNode *Lst_FindDatum(List *, const void *); /* Modify a list */ /* Insert a datum before the given node. */ -void Lst_InsertBefore(Lst, LstNode, void *); +void Lst_InsertBefore(List *, ListNode *, void *); /* Place a datum at the front of the list. */ -void Lst_Prepend(Lst, void *); +void Lst_Prepend(List *, void *); /* Place a datum at the end of the list. */ -void Lst_Append(Lst, void *); +void Lst_Append(List *, void *); /* Remove the node from the list. */ -void Lst_Remove(Lst, LstNode); -void Lst_PrependAll(Lst, Lst); -void Lst_AppendAll(Lst, Lst); -void Lst_MoveAll(Lst, Lst); +void Lst_Remove(List *, ListNode *); +void Lst_PrependAll(List *, List *); +void Lst_AppendAll(List *, List *); +void Lst_MoveAll(List *, List *); /* Node-specific functions */ -/* Return the successor of the node, or NULL. */ -LstNode LstNode_Next(LstNode); -/* Return the predecessor of the node, or NULL. */ -LstNode LstNode_Prev(LstNode); -/* Return the datum of the node. Usually not NULL. */ -void *LstNode_Datum(LstNode); /* Replace the value of the node. */ -void LstNode_Set(LstNode, void *); +void LstNode_Set(ListNode *, void *); /* Set the value of the node to NULL. Having NULL in a list is unusual. */ -void LstNode_SetNull(LstNode); +void LstNode_SetNull(ListNode *); /* Iterating over a list, using a callback function */ -/* Apply a function to each datum of the list, until the callback function - * returns non-zero. */ -int Lst_ForEach(Lst, LstActionProc, void *); -/* Apply a function to each datum of the list, starting at the node, - * until the callback function returns non-zero. */ -int Lst_ForEachFrom(Lst, LstNode, LstActionProc, void *); - -/* Iterating over a list while keeping track of the current node and possible - * concurrent modifications */ - -/* Start iterating the list. */ -void Lst_Open(Lst); -/* Return the next node, or NULL. */ -LstNode Lst_Next(Lst); -/* Finish iterating the list. */ -void Lst_Close(Lst); +/* Run the action for each datum of the list, until the action returns + * non-zero. + * + * During this iteration, the list must not be modified structurally. */ +int Lst_ForEachUntil(List *, LstActionUntilProc, void *); /* Using the list as a queue */ /* Add a datum at the tail of the queue. */ -void Lst_Enqueue(Lst, void *); +void Lst_Enqueue(List *, void *); /* Remove the head node of the queue and return its datum. */ -void *Lst_Dequeue(Lst); +void *Lst_Dequeue(List *); + +/* A vector is an ordered collection of items, allowing for fast indexed + * access. */ +typedef struct Vector { + void *items; /* memory holding the items */ + size_t itemSize; /* size of a single item in bytes */ + size_t len; /* number of actually usable elements */ + size_t priv_cap; /* capacity */ +} Vector; + +void Vector_Init(Vector *, size_t); + +/* Return the pointer to the given item in the vector. + * The returned data is valid until the next modifying operation. */ +static inline MAKE_ATTR_UNUSED void * +Vector_Get(Vector *v, size_t i) +{ + unsigned char *items = v->items; + return items + i * v->itemSize; +} + +void *Vector_Push(Vector *); +void *Vector_Pop(Vector *); +void Vector_Done(Vector *); #endif /* MAKE_LST_H */ @@ -1,4 +1,4 @@ -/* $NetBSD: main.c,v 1.331 2020/08/30 19:56:02 rillig Exp $ */ +/* $NetBSD: main.c,v 1.421 2020/11/01 00:24:57 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,24 +68,6 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: main.c,v 1.331 2020/08/30 19:56:02 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\ - The Regents of the University of California. All rights reserved."); -#endif /* not lint */ - -#ifndef lint -#if 0 -static char sccsid[] = "@(#)main.c 8.3 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: main.c,v 1.331 2020/08/30 19:56:02 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - /*- * main.c -- * The main file for this entire program. Exit routines etc @@ -124,69 +106,47 @@ __RCSID("$NetBSD: main.c,v 1.331 2020/08/30 19:56:02 rillig Exp $"); #include <sys/utsname.h> #include "wait.h" -#include <ctype.h> #include <errno.h> #include <signal.h> #include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> #include <time.h> #include "make.h" -#include "hash.h" #include "dir.h" #include "job.h" #include "pathnames.h" #include "trace.h" -#ifdef USE_IOVEC -#include <sys/uio.h> +/* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: main.c,v 1.421 2020/11/01 00:24:57 rillig Exp $"); +#if defined(MAKE_NATIVE) && !defined(lint) +__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " + "The Regents of the University of California. " + "All rights reserved."); #endif #ifndef DEFMAXLOCAL #define DEFMAXLOCAL DEFMAXJOBS -#endif /* DEFMAXLOCAL */ +#endif #ifndef __arraycount # define __arraycount(__x) (sizeof(__x) / sizeof(__x[0])) #endif -Lst create; /* Targets to be made */ +CmdOpts opts; time_t now; /* Time at start of make */ GNode *DEFAULT; /* .DEFAULT node */ Boolean allPrecious; /* .PRECIOUS given on line by itself */ Boolean deleteOnError; /* .DELETE_ON_ERROR: set */ -static Boolean noBuiltins; /* -r flag */ -static Lst makefiles; /* ordered list of makefiles to read */ -static int printVars; /* -[vV] argument */ -#define COMPAT_VARS 1 -#define EXPAND_VARS 2 -static Lst variables; /* list of variables to print - * (for -v and -V) */ -int maxJobs; /* -j argument */ static int maxJobTokens; /* -j argument */ -Boolean compatMake; /* -B argument */ -int debug; /* -d argument */ -Boolean debugVflag; /* -dV */ -Boolean noExecute; /* -n flag */ -Boolean noRecursiveExecute; /* -N flag */ -Boolean keepgoing; /* -k flag */ -Boolean queryFlag; /* -q flag */ -Boolean touchFlag; /* -t flag */ -Boolean enterFlag; /* -w flag */ Boolean enterFlagObj; /* -w and objdir != srcdir */ -Boolean ignoreErrors; /* -i flag */ -Boolean beSilent; /* -s flag */ + Boolean oldVars; /* variable substitution style */ -Boolean checkEnvFirst; /* -e flag */ -Boolean parseWarnFatal; /* -W flag */ static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ -Boolean varNoExportEnv; /* -X flag */ Boolean doing_depend; /* Set while reading .depend */ static Boolean jobsRunning; /* TRUE if the jobs might be running */ static const char * tracefile; -static void MainParseArgs(int, char **); static int ReadMakefile(const char *); static void usage(void) MAKE_ATTR_DEAD; static void purge_cached_realpaths(void); @@ -199,9 +159,8 @@ char *makeDependfile; pid_t myPid; int makelevel; -FILE *debug_file; - Boolean forceJobs = FALSE; +static int errors = 0; /* * On some systems MACHINE is defined as something other than @@ -212,7 +171,7 @@ Boolean forceJobs = FALSE; # define MACHINE FORCE_MACHINE #endif -extern Lst parseIncPath; +extern SearchPath *parseIncPath; /* * For compatibility with the POSIX version of MAKEFLAGS that includes @@ -229,7 +188,7 @@ explode(const char *flags) return NULL; for (f = flags; *f; f++) - if (!isalpha((unsigned char)*f)) + if (!ch_isalpha(*f)) break; if (*f) @@ -247,118 +206,133 @@ explode(const char *flags) } static void +parse_debug_option_F(const char *modules) +{ + const char *mode; + size_t len; + char *fname; + + if (opts.debug_file != stdout && opts.debug_file != stderr) + fclose(opts.debug_file); + + if (*modules == '+') { + modules++; + mode = "a"; + } else + mode = "w"; + + if (strcmp(modules, "stdout") == 0) { + opts.debug_file = stdout; + return; + } + if (strcmp(modules, "stderr") == 0) { + opts.debug_file = stderr; + return; + } + + len = strlen(modules); + fname = bmake_malloc(len + 20); + memcpy(fname, modules, len + 1); + + /* Let the filename be modified by the pid */ + if (strcmp(fname + len - 3, ".%d") == 0) + snprintf(fname + len - 2, 20, "%d", getpid()); + + opts.debug_file = fopen(fname, mode); + if (!opts.debug_file) { + fprintf(stderr, "Cannot open debug file %s\n", + fname); + usage(); + } + free(fname); +} + +static void parse_debug_options(const char *argvalue) { const char *modules; - const char *mode; - char *fname; - int len; for (modules = argvalue; *modules; ++modules) { switch (*modules) { + case '0': /* undocumented, only intended for tests */ + opts.debug &= DEBUG_LINT; + break; case 'A': - debug = ~(0|DEBUG_LINT); + opts.debug = ~(0|DEBUG_LINT); break; case 'a': - debug |= DEBUG_ARCH; + opts.debug |= DEBUG_ARCH; break; case 'C': - debug |= DEBUG_CWD; + opts.debug |= DEBUG_CWD; break; case 'c': - debug |= DEBUG_COND; + opts.debug |= DEBUG_COND; break; case 'd': - debug |= DEBUG_DIR; + opts.debug |= DEBUG_DIR; break; case 'e': - debug |= DEBUG_ERROR; + opts.debug |= DEBUG_ERROR; break; case 'f': - debug |= DEBUG_FOR; + opts.debug |= DEBUG_FOR; break; case 'g': if (modules[1] == '1') { - debug |= DEBUG_GRAPH1; + opts.debug |= DEBUG_GRAPH1; ++modules; } else if (modules[1] == '2') { - debug |= DEBUG_GRAPH2; + opts.debug |= DEBUG_GRAPH2; ++modules; } else if (modules[1] == '3') { - debug |= DEBUG_GRAPH3; + opts.debug |= DEBUG_GRAPH3; ++modules; } break; case 'h': - debug |= DEBUG_HASH; + opts.debug |= DEBUG_HASH; break; case 'j': - debug |= DEBUG_JOB; + opts.debug |= DEBUG_JOB; break; case 'L': - debug |= DEBUG_LINT; + opts.debug |= DEBUG_LINT; break; case 'l': - debug |= DEBUG_LOUD; + opts.debug |= DEBUG_LOUD; break; case 'M': - debug |= DEBUG_META; + opts.debug |= DEBUG_META; break; case 'm': - debug |= DEBUG_MAKE; + opts.debug |= DEBUG_MAKE; break; case 'n': - debug |= DEBUG_SCRIPT; + opts.debug |= DEBUG_SCRIPT; break; case 'p': - debug |= DEBUG_PARSE; + opts.debug |= DEBUG_PARSE; break; case 's': - debug |= DEBUG_SUFF; + opts.debug |= DEBUG_SUFF; break; case 't': - debug |= DEBUG_TARG; + opts.debug |= DEBUG_TARG; break; case 'V': - debugVflag = TRUE; + opts.debugVflag = TRUE; break; case 'v': - debug |= DEBUG_VAR; + opts.debug |= DEBUG_VAR; break; case 'x': - debug |= DEBUG_SHELL; + opts.debug |= DEBUG_SHELL; break; case 'F': - if (debug_file != stdout && debug_file != stderr) - fclose(debug_file); - if (*++modules == '+') { - modules++; - mode = "a"; - } else - mode = "w"; - if (strcmp(modules, "stdout") == 0) { - debug_file = stdout; - goto debug_setbuf; - } - if (strcmp(modules, "stderr") == 0) { - debug_file = stderr; - goto debug_setbuf; - } - len = strlen(modules); - fname = bmake_malloc(len + 20); - memcpy(fname, modules, len + 1); - /* Let the filename be modified by the pid */ - if (strcmp(fname + len - 3, ".%d") == 0) - snprintf(fname + len - 2, 20, "%d", getpid()); - debug_file = fopen(fname, mode); - if (!debug_file) { - fprintf(stderr, "Cannot open debug file %s\n", - fname); - usage(); - } - free(fname); + parse_debug_option_F(modules + 1); goto debug_setbuf; default: (void)fprintf(stderr, @@ -372,8 +346,8 @@ debug_setbuf: * Make the debug_file unbuffered, and make * stdout line buffered (unless debugfile == stdout). */ - setvbuf(debug_file, NULL, _IONBF, 0); - if (debug_file != stdout) { + setvbuf(opts.debug_file, NULL, _IONBF, 0); + if (opts.debug_file != stdout) { setvbuf(stdout, NULL, _IOLBF, 0); } } @@ -401,48 +375,241 @@ is_relpath(const char *path) return FALSE; } -/*- - * MainParseArgs -- - * Parse a given argument vector. Called from main() and from - * Main_ParseArgLine() when the .MAKEFLAGS target is used. - * - * XXX: Deal with command line overriding .MAKEFLAGS in makefile +static void +MainParseArgChdir(const char *argvalue) +{ + struct stat sa, sb; + + if (chdir(argvalue) == -1) { + (void)fprintf(stderr, "%s: chdir %s: %s\n", + progname, argvalue, strerror(errno)); + exit(1); + } + if (getcwd(curdir, MAXPATHLEN) == NULL) { + (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno)); + exit(2); + } + if (!is_relpath(argvalue) && + stat(argvalue, &sa) != -1 && + stat(curdir, &sb) != -1 && + sa.st_ino == sb.st_ino && + sa.st_dev == sb.st_dev) + strncpy(curdir, argvalue, MAXPATHLEN); + ignorePWD = TRUE; +} + +static void +MainParseArgJobsInternal(const char *argvalue) +{ + if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) { + (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; + } else { + Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + } +} + +static void +MainParseArgJobs(const char *argvalue) +{ + char *p; + + forceJobs = TRUE; + opts.maxJobs = (int)strtol(argvalue, &p, 0); + if (*p != '\0' || opts.maxJobs < 1) { + (void)fprintf(stderr, + "%s: illegal argument to -j -- must be positive integer!\n", + progname); + exit(1); /* XXX: why not 2? */ + } + Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + Var_Set(".MAKE.JOBS", argvalue, VAR_GLOBAL); + maxJobTokens = opts.maxJobs; +} + +static void +MainParseArgSysInc(const char *argvalue) +{ + /* look for magic parent directory search string */ + if (strncmp(".../", argvalue, 4) == 0) { + char *found_path = Dir_FindHereOrAbove(curdir, argvalue + 4); + if (found_path == NULL) + return; + (void)Dir_AddDir(sysIncPath, found_path); + free(found_path); + } else { + (void)Dir_AddDir(sysIncPath, argvalue); + } + Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); +} + +static Boolean +MainParseArg(char c, const char *argvalue) +{ + switch (c) { + case '\0': + break; + case 'B': + opts.compatMake = TRUE; + Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL); + Var_Set(MAKE_MODE, "compat", VAR_GLOBAL); + break; + case 'C': + MainParseArgChdir(argvalue); + break; + case 'D': + if (argvalue[0] == '\0') return FALSE; + Var_Set(argvalue, "1", VAR_GLOBAL); + Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + break; + case 'I': + Parse_AddIncludeDir(argvalue); + Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + break; + case 'J': + MainParseArgJobsInternal(argvalue); + break; + case 'N': + opts.noExecute = TRUE; + opts.noRecursiveExecute = TRUE; + Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL); + break; + case 'S': + opts.keepgoing = FALSE; + Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL); + break; + case 'T': + tracefile = bmake_strdup(argvalue); + Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + break; + case 'V': + case 'v': + opts.printVars = c == 'v' ? EXPAND_VARS : COMPAT_VARS; + Lst_Append(opts.variables, bmake_strdup(argvalue)); + /* XXX: Why always -V? */ + Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + break; + case 'W': + opts.parseWarnFatal = TRUE; + break; + case 'X': + opts.varNoExportEnv = TRUE; + Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL); + break; + case 'd': + /* If '-d-opts' don't pass to children */ + if (argvalue[0] == '-') + argvalue++; + else { + Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + } + parse_debug_options(argvalue); + break; + case 'e': + opts.checkEnvFirst = TRUE; + Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL); + break; + case 'f': + Lst_Append(opts.makefiles, bmake_strdup(argvalue)); + break; + case 'i': + opts.ignoreErrors = TRUE; + Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL); + break; + case 'j': + MainParseArgJobs(argvalue); + break; + case 'k': + opts.keepgoing = TRUE; + Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL); + break; + case 'm': + MainParseArgSysInc(argvalue); + break; + case 'n': + opts.noExecute = TRUE; + Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL); + break; + case 'q': + opts.queryFlag = TRUE; + /* Kind of nonsensical, wot? */ + Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL); + break; + case 'r': + opts.noBuiltins = TRUE; + Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL); + break; + case 's': + opts.beSilent = TRUE; + Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL); + break; + case 't': + opts.touchFlag = TRUE; + Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL); + break; + case 'w': + opts.enterFlag = TRUE; + Var_Append(MAKEFLAGS, "-w", VAR_GLOBAL); + break; + default: + case '?': + usage(); + } + return TRUE; +} + +/* Parse the given arguments. Called from main() and from + * Main_ParseArgLine() when the .MAKEFLAGS target is used. * - * Results: - * None + * The arguments must be treated as read-only and will be freed after the + * call. * - * Side Effects: - * Various global and local flags will be set depending on the flags - * given - */ + * XXX: Deal with command line overriding .MAKEFLAGS in makefile */ static void MainParseArgs(int argc, char **argv) { - char *p; - char c = '?'; + char c; int arginc; char *argvalue; - const char *getopt_def; - struct stat sa, sb; char *optscan; Boolean inOption, dashDash = FALSE; - char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ -#define OPTFLAGS "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstv:w" + 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 */ - getopt_def = OPTFLAGS; rearg: inOption = FALSE; optscan = NULL; - while(argc > 1) { - char *getopt_spec; - if(!inOption) + while (argc > 1) { + const char *optspec; + if (!inOption) optscan = argv[1]; c = *optscan++; arginc = 0; - if(inOption) { - if(c == '\0') { + if (inOption) { + if (c == '\0') { ++argv; --argc; inOption = FALSE; @@ -455,13 +622,13 @@ rearg: c = *optscan++; } /* '-' found at some earlier point */ - getopt_spec = strchr(getopt_def, c); - if(c != '\0' && getopt_spec != NULL && getopt_spec[1] == ':') { + optspec = strchr(optspecs, c); + if (c != '\0' && optspec != NULL && optspec[1] == ':') { /* -<something> found, and <something> should have an arg */ inOption = FALSE; arginc = 1; argvalue = optscan; - if(*argvalue == '\0') { + if (*argvalue == '\0') { if (argc < 3) goto noarg; argvalue = argv[2]; @@ -470,192 +637,17 @@ rearg: } else { argvalue = NULL; } - switch(c) { + switch (c) { case '\0': arginc = 1; inOption = FALSE; break; - case 'B': - compatMake = TRUE; - Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL); - Var_Set(MAKE_MODE, "compat", VAR_GLOBAL); - break; - case 'C': - if (chdir(argvalue) == -1) { - (void)fprintf(stderr, - "%s: chdir %s: %s\n", - progname, argvalue, - strerror(errno)); - exit(1); - } - if (getcwd(curdir, MAXPATHLEN) == NULL) { - (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno)); - exit(2); - } - if (!is_relpath(argvalue) && - stat(argvalue, &sa) != -1 && - stat(curdir, &sb) != -1 && - sa.st_ino == sb.st_ino && - sa.st_dev == sb.st_dev) - strncpy(curdir, argvalue, MAXPATHLEN); - ignorePWD = TRUE; - break; - case 'D': - if (argvalue == NULL || argvalue[0] == 0) goto noarg; - Var_Set(argvalue, "1", VAR_GLOBAL); - Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'I': - if (argvalue == NULL) goto noarg; - Parse_AddIncludeDir(argvalue); - Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'J': - if (argvalue == NULL) goto noarg; - if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) { - (void)fprintf(stderr, - "%s: internal error -- J option malformed (%s)\n", - progname, argvalue); - usage(); - } - if ((fcntl(jp_0, F_GETFD, 0) < 0) || - (fcntl(jp_1, F_GETFD, 0) < 0)) { -#if 0 - (void)fprintf(stderr, - "%s: ###### warning -- J descriptors were closed!\n", - progname); - exit(2); -#endif - jp_0 = -1; - jp_1 = -1; - compatMake = TRUE; - } else { - Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - } - break; - case 'N': - noExecute = TRUE; - noRecursiveExecute = TRUE; - Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL); - break; - case 'S': - keepgoing = FALSE; - Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL); - break; - case 'T': - if (argvalue == NULL) goto noarg; - tracefile = bmake_strdup(argvalue); - Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'V': - case 'v': - if (argvalue == NULL) goto noarg; - printVars = c == 'v' ? EXPAND_VARS : COMPAT_VARS; - Lst_Append(variables, argvalue); - Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'W': - parseWarnFatal = TRUE; - break; - case 'X': - varNoExportEnv = TRUE; - Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL); - break; - case 'd': - if (argvalue == NULL) goto noarg; - /* If '-d-opts' don't pass to children */ - if (argvalue[0] == '-') - argvalue++; - else { - Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - } - parse_debug_options(argvalue); - break; - case 'e': - checkEnvFirst = TRUE; - Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL); - break; - case 'f': - if (argvalue == NULL) goto noarg; - Lst_Append(makefiles, argvalue); - break; - case 'i': - ignoreErrors = TRUE; - Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL); - break; - case 'j': - if (argvalue == NULL) goto noarg; - forceJobs = TRUE; - maxJobs = strtol(argvalue, &p, 0); - if (*p != '\0' || maxJobs < 1) { - (void)fprintf(stderr, "%s: illegal argument to -j -- must be positive integer!\n", - progname); - exit(1); - } - Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - Var_Set(".MAKE.JOBS", argvalue, VAR_GLOBAL); - maxJobTokens = maxJobs; - break; - case 'k': - keepgoing = TRUE; - Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL); - break; - case 'm': - if (argvalue == NULL) goto noarg; - /* look for magic parent directory search string */ - if (strncmp(".../", argvalue, 4) == 0) { - if (!Dir_FindHereOrAbove(curdir, argvalue+4, - found_path, sizeof(found_path))) - break; /* nothing doing */ - (void)Dir_AddDir(sysIncPath, found_path); - } else { - (void)Dir_AddDir(sysIncPath, argvalue); - } - Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'n': - noExecute = TRUE; - Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL); - break; - case 'q': - queryFlag = TRUE; - /* Kind of nonsensical, wot? */ - Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL); - break; - case 'r': - noBuiltins = TRUE; - Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL); - break; - case 's': - beSilent = TRUE; - Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL); - break; - case 't': - touchFlag = TRUE; - Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL); - break; - case 'w': - enterFlag = TRUE; - Var_Append(MAKEFLAGS, "-w", VAR_GLOBAL); - break; case '-': dashDash = TRUE; break; default: - case '?': -#ifndef MAKE_NATIVE - fprintf(stderr, "getopt(%s) -> %d (%c)\n", - OPTFLAGS, c, c); -#endif - usage(); + if (!MainParseArg(c, argvalue)) + goto noarg; } argv += arginc; argc -= arginc; @@ -668,16 +660,18 @@ rearg: * perform them if so. Else take them to be targets and stuff them * on the end of the "create" list. */ - for (; argc > 1; ++argv, --argc) - if (Parse_IsVar(argv[1])) { - Parse_DoVar(argv[1], VAR_CMD); + for (; argc > 1; ++argv, --argc) { + VarAssign var; + if (Parse_IsVar(argv[1], &var)) { + Parse_DoVar(&var, VAR_CMDLINE); } else { if (!*argv[1]) Punt("illegal (null) argument."); if (*argv[1] == '-' && !dashDash) goto rearg; - Lst_Append(create, bmake_strdup(argv[1])); + Lst_Append(opts.create, bmake_strdup(argv[1])); } + } return; noarg: @@ -686,29 +680,15 @@ noarg: usage(); } -/*- - * Main_ParseArgLine -- - * Used by the parse module when a .MFLAGS or .MAKEFLAGS target - * is encountered and by main() when reading the .MAKEFLAGS envariable. - * Takes a line of arguments and breaks it into its - * component words and passes those words and the number of them to the - * MainParseArgs function. - * The line should have all its leading whitespace removed. - * - * Input: - * line Line to fracture +/* Break a line of arguments into words and parse them. * - * Results: - * None - * - * Side Effects: - * Only those that come from the various arguments. - */ + * 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 *p1; + void *p1; const char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1); char *buf; @@ -769,7 +749,9 @@ Main_SetObjdir(const char *fmt, ...) /* look for the directory and try to chdir there */ if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { - if (chdir(path)) { + /* if not .CURDIR it must be writable */ + if ((strcmp(path, curdir) != 0 && access(path, W_OK) != 0) || + chdir(path)) { (void)fprintf(stderr, "make warning: %s: %s.\n", path, strerror(errno)); } else { @@ -779,7 +761,7 @@ Main_SetObjdir(const char *fmt, ...) Dir_InitDot(); purge_cached_realpaths(); rc = TRUE; - if (enterFlag && strcmp(objdir, curdir) != 0) + if (opts.enterFlag && strcmp(objdir, curdir) != 0) enterFlagObj = TRUE; } } @@ -790,8 +772,8 @@ Main_SetObjdir(const char *fmt, ...) static Boolean Main_SetVarObjdir(const char *var, const char *suffix) { - char *path_freeIt; - const char *path = Var_Value(var, VAR_CMD, &path_freeIt); + void *path_freeIt; + const char *path = Var_Value(var, VAR_CMDLINE, &path_freeIt); const char *xpath; char *xpath_freeIt; @@ -803,9 +785,11 @@ Main_SetVarObjdir(const char *var, const char *suffix) /* expand variable substitutions */ xpath = path; xpath_freeIt = NULL; - if (strchr(path, '$') != 0) - xpath = xpath_freeIt = Var_Subst(path, VAR_GLOBAL, - VARE_WANTRES); + if (strchr(path, '$') != 0) { + (void)Var_Subst(path, VAR_GLOBAL, VARE_WANTRES, &xpath_freeIt); + /* TODO: handle errors */ + xpath = xpath_freeIt; + } (void)Main_SetObjdir("%s%s", xpath, suffix); @@ -815,23 +799,15 @@ Main_SetVarObjdir(const char *var, const char *suffix) } /* Read and parse the makefile. - * Return TRUE if reading the makefile succeeded, for Lst_Find. */ -static Boolean -ReadMakefileSucceeded(const void *fname, const void *unused) + * Return TRUE if reading the makefile succeeded. */ +static int +ReadMakefileSucceeded(void *fname, void *unused) { return ReadMakefile(fname) == 0; } -/* Read and parse the makefile. - * Return TRUE if reading the makefile failed, for Lst_Find. */ -static Boolean -ReadMakefileFailed(const void *fname, const void *unused) -{ - return ReadMakefile(fname) != 0; -} - int -str2Lst_Append(Lst lp, char *str, const char *sep) +str2Lst_Append(StringList *lp, char *str, const char *sep) { char *cp; int n; @@ -870,13 +846,16 @@ MakeMode(const char *mode) { char *mode_freeIt = NULL; - if (mode == NULL) - mode = mode_freeIt = Var_Subst("${" MAKE_MODE ":tl}", - VAR_GLOBAL, VARE_WANTRES); + if (mode == NULL) { + (void)Var_Subst("${" MAKE_MODE ":tl}", + VAR_GLOBAL, VARE_WANTRES, &mode_freeIt); + /* TODO: handle errors */ + mode = mode_freeIt; + } if (mode[0] != '\0') { if (strstr(mode, "compat")) { - compatMake = TRUE; + opts.compatMake = TRUE; forceJobs = FALSE; } #if USE_META @@ -889,46 +868,56 @@ MakeMode(const char *mode) } static void +PrintVar(const char *varname, Boolean expandVars) +{ + if (strchr(varname, '$')) { + char *evalue; + (void)Var_Subst(varname, VAR_GLOBAL, VARE_WANTRES, &evalue); + /* TODO: handle errors */ + printf("%s\n", evalue); + bmake_free(evalue); + + } else if (expandVars) { + char *expr = str_concat3("${", varname, "}"); + char *evalue; + (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &evalue); + /* TODO: handle errors */ + free(expr); + printf("%s\n", evalue); + bmake_free(evalue); + + } else { + void *freeIt; + const char *value = Var_Value(varname, VAR_GLOBAL, &freeIt); + printf("%s\n", value ? value : ""); + bmake_free(freeIt); + } +} + +static void doPrintVars(void) { - LstNode ln; + StringListNode *ln; Boolean expandVars; - if (printVars == EXPAND_VARS) + if (opts.printVars == EXPAND_VARS) expandVars = TRUE; - else if (debugVflag) + else if (opts.debugVflag) expandVars = FALSE; else expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE); - for (ln = Lst_First(variables); ln != NULL; ln = LstNode_Next(ln)) { - char *var = LstNode_Datum(ln); - const char *value; - char *p1; - - if (strchr(var, '$')) { - value = p1 = Var_Subst(var, VAR_GLOBAL, VARE_WANTRES); - } else if (expandVars) { - char tmp[128]; - int len = snprintf(tmp, sizeof(tmp), "${%s}", var); - - if (len >= (int)sizeof(tmp)) - Fatal("%s: variable name too big: %s", - progname, var); - value = p1 = Var_Subst(tmp, VAR_GLOBAL, VARE_WANTRES); - } else { - value = Var_Value(var, VAR_GLOBAL, &p1); - } - printf("%s\n", value ? value : ""); - bmake_free(p1); + for (ln = opts.variables->first; ln != NULL; ln = ln->next) { + const char *varname = ln->datum; + PrintVar(varname, expandVars); } } static Boolean runTargets(void) { - Lst targs; /* target nodes to create -- passed to Make_Init */ - Boolean outOfDate; /* FALSE if all targets up to date */ + GNodeList *targs; /* target nodes to create -- passed to Make_Init */ + Boolean outOfDate; /* FALSE if all targets up to date */ /* * Have now read the entire graph and need to make a list of @@ -936,12 +925,12 @@ runTargets(void) * we consult the parsing module to find the main target(s) * to create. */ - if (Lst_IsEmpty(create)) + if (Lst_IsEmpty(opts.create)) targs = Parse_MainName(); else - targs = Targ_FindList(create, TARG_CREATE); + targs = Targ_FindList(opts.create); - if (!compatMake) { + if (!opts.compatMake) { /* * Initialize job module before traversing the graph * now that any .BEGIN and .END targets have been read. @@ -949,7 +938,7 @@ runTargets(void) * (to prevent the .BEGIN from being executed should * it exist). */ - if (!queryFlag) { + if (!opts.queryFlag) { Job_Init(); jobsRunning = TRUE; } @@ -968,6 +957,397 @@ runTargets(void) return outOfDate; } +/* + * Set up the .TARGETS variable to contain the list of targets to be + * created. If none specified, make the variable empty -- the parser + * will fill the thing in with the default or .MAIN target. + */ +static void +InitVarTargets(void) +{ + StringListNode *ln; + + if (Lst_IsEmpty(opts.create)) { + Var_Set(".TARGETS", "", VAR_GLOBAL); + return; + } + + for (ln = opts.create->first; ln != NULL; ln = ln->next) { + char *name = ln->datum; + Var_Append(".TARGETS", name, VAR_GLOBAL); + } +} + +static void +InitRandom(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + srandom((unsigned int)(tv.tv_sec + tv.tv_usec)); +} + +static const char * +init_machine(const struct utsname *utsname) +{ +#ifdef FORCE_MACHINE + const char *machine = FORCE_MACHINE; +#else + const char *machine = getenv("MACHINE"); +#endif + if (machine != NULL) + return machine; + +#ifdef MAKE_NATIVE + return utsname->machine; +#else +#ifdef MAKE_MACHINE + return MAKE_MACHINE; +#else + return "unknown"; +#endif +#endif +} + +static const char * +init_machine_arch(void) +{ + 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, __arraycount(mib), machine_arch_buf, + &len, NULL, 0) < 0) { + (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname, + strerror(errno)); + exit(2); + } + + return machine_arch_buf; + } +#else +#ifndef MACHINE_ARCH +#ifdef MAKE_MACHINE_ARCH + return MAKE_MACHINE_ARCH; +#else + return "unknown"; +#endif +#else + return MACHINE_ARCH; +#endif +#endif +} + +#ifndef NO_PWD_OVERRIDE +/* + * All this code is so that we know where we are when we start up + * on a different machine with pmake. + * + * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX + * since the value of curdir can vary depending on how we got + * here. Ie sitting at a shell prompt (shell that provides $PWD) + * or via subdir.mk in which case its likely a shell which does + * not provide it. + * + * So, to stop it breaking this case only, we ignore PWD if + * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a variable expression. + */ +static void +HandlePWD(const struct stat *curdir_st) +{ + char *pwd; + void *prefix_freeIt, *makeobjdir_freeIt; + const char *makeobjdir; + struct stat pwd_st; + + if (ignorePWD || (pwd = getenv("PWD")) == NULL) + return; + + if (Var_Value("MAKEOBJDIRPREFIX", VAR_CMDLINE, &prefix_freeIt) != NULL) { + bmake_free(prefix_freeIt); + return; + } + + makeobjdir = Var_Value("MAKEOBJDIR", VAR_CMDLINE, &makeobjdir_freeIt); + if (makeobjdir != NULL && strchr(makeobjdir, '$') != NULL) + goto ignore_pwd; + + if (stat(pwd, &pwd_st) == 0 && + curdir_st->st_ino == pwd_st.st_ino && + curdir_st->st_dev == pwd_st.st_dev) + (void)strncpy(curdir, pwd, MAXPATHLEN); + +ignore_pwd: + bmake_free(makeobjdir_freeIt); +} +#endif + +/* + * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that, + * MAKEOBJDIR is set in the environment, try only that value + * and fall back to .CURDIR if it does not exist. + * + * Otherwise, try _PATH_OBJDIR.MACHINE-MACHINE_ARCH, _PATH_OBJDIR.MACHINE, + * and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none + * of these paths exist, just use .CURDIR. + */ +static void +InitObjdir(const char *machine, const char *machine_arch) +{ + Dir_InitDir(curdir); + (void)Main_SetObjdir("%s", curdir); + + if (!Main_SetVarObjdir("MAKEOBJDIRPREFIX", curdir) && + !Main_SetVarObjdir("MAKEOBJDIR", "") && + !Main_SetObjdir("%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) && + !Main_SetObjdir("%s.%s", _PATH_OBJDIR, machine) && + !Main_SetObjdir("%s", _PATH_OBJDIR)) + (void)Main_SetObjdir("%s%s", _PATH_OBJDIRPREFIX, curdir); +} + +/* get rid of resource limit on file descriptors */ +static void +UnlimitFiles(void) +{ +#if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)) + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && + rl.rlim_cur != rl.rlim_max) { + rl.rlim_cur = rl.rlim_max; + (void)setrlimit(RLIMIT_NOFILE, &rl); + } +#endif +} + +static void +CmdOpts_Init(void) +{ + opts.compatMake = FALSE; /* No compat mode */ + opts.debug = 0; /* No debug verbosity, please. */ + /* opts.debug_file has been initialized earlier */ + opts.debugVflag = FALSE; + opts.checkEnvFirst = FALSE; + opts.makefiles = Lst_New(); + opts.ignoreErrors = FALSE; /* Pay attention to non-zero returns */ + opts.maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */ + opts.keepgoing = FALSE; /* Stop on error */ + opts.noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ + opts.noExecute = FALSE; /* Execute all commands */ + opts.queryFlag = FALSE; /* This is not just a check-run */ + opts.noBuiltins = FALSE; /* Read the built-in rules */ + opts.beSilent = FALSE; /* Print commands as executed */ + opts.touchFlag = FALSE; /* Actually update targets */ + opts.printVars = 0; + opts.variables = Lst_New(); + opts.parseWarnFatal = FALSE; + opts.enterFlag = FALSE; + opts.varNoExportEnv = FALSE; + opts.create = Lst_New(); +} + +/* Initialize MAKE and .MAKE to the path of the executable, so that it can be + * found by execvp(3) and the shells, even after a chdir. + * + * If it's a relative path and contains a '/', resolve it to an absolute path. + * Otherwise keep it as is, assuming it will be found in the PATH. */ +static void +InitVarMake(const char *argv0) +{ + const char *make = argv0; + + if (argv0[0] != '/' && strchr(argv0, '/') != NULL) { + char pathbuf[MAXPATHLEN]; + const char *abs = cached_realpath(argv0, pathbuf); + struct stat st; + if (abs != NULL && abs[0] == '/' && stat(make, &st) == 0) + make = abs; + } + + Var_Set("MAKE", make, VAR_GLOBAL); + Var_Set(".MAKE", make, VAR_GLOBAL); +} + +static void +InitDefSysIncPath(char *syspath) +{ + static char defsyspath[] = _PATH_DEFSYSPATH; + char *start, *cp; + + /* + * If no user-supplied system path was given (through the -m option) + * add the directories from the DEFSYSPATH (more than one may be given + * as dir1:...:dirn) to the system include path. + */ + /* XXX: mismatch: the -m option sets sysIncPath, not syspath */ + if (syspath == NULL || syspath[0] == '\0') + syspath = defsyspath; + else + syspath = bmake_strdup(syspath); + + for (start = syspath; *start != '\0'; start = cp) { + for (cp = start; *cp != '\0' && *cp != ':'; cp++) + continue; + if (*cp == ':') { + *cp++ = '\0'; + } + /* look for magic parent directory search string */ + if (strncmp(".../", start, 4) != 0) { + (void)Dir_AddDir(defSysIncPath, start); + } else { + char *dir = Dir_FindHereOrAbove(curdir, start + 4); + if (dir != NULL) { + (void)Dir_AddDir(defSysIncPath, dir); + free(dir); + } + } + } + + if (syspath != defsyspath) + free(syspath); +} + +static void +ReadBuiltinRules(void) +{ + StringList *sysMkPath = Lst_New(); + Dir_Expand(_PATH_DEFSYSMK, + Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath, + sysMkPath); + if (Lst_IsEmpty(sysMkPath)) + Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); + if (!Lst_ForEachUntil(sysMkPath, ReadMakefileSucceeded, NULL)) + Fatal("%s: cannot open %s.", progname, + (char *)sysMkPath->first->datum); + /* XXX: sysMkPath is not freed */ +} + +static void +InitMaxJobs(void) +{ + char *value; + int n; + + if (forceJobs || opts.compatMake || + !Var_Exists(".MAKE.JOBS", VAR_GLOBAL)) + return; + + (void)Var_Subst("${.MAKE.JOBS}", VAR_GLOBAL, VARE_WANTRES, &value); + /* TODO: handle errors */ + n = (int)strtol(value, NULL, 0); + if (n < 1) { + (void)fprintf(stderr, + "%s: illegal value for .MAKE.JOBS " + "-- must be positive integer!\n", + progname); + exit(1); + } + + if (n != opts.maxJobs) { + Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); + Var_Append(MAKEFLAGS, value, VAR_GLOBAL); + } + + opts.maxJobs = n; + maxJobTokens = opts.maxJobs; + forceJobs = TRUE; + free(value); +} + +/* + * For compatibility, look at the directories in the VPATH variable + * and add them to the search path, if the variable is defined. The + * variable's value is in the same format as the PATH environment + * variable, i.e. <directory>:<directory>:<directory>... + */ +static void +InitVpath(void) +{ + char *vpath, savec, *path; + if (!Var_Exists("VPATH", VAR_CMDLINE)) + return; + + (void)Var_Subst("${VPATH}", VAR_CMDLINE, VARE_WANTRES, &vpath); + /* TODO: handle errors */ + path = vpath; + do { + char *cp; + /* skip to end of directory */ + for (cp = path; *cp != ':' && *cp != '\0'; cp++) + continue; + /* Save terminator character so know when to stop */ + savec = *cp; + *cp = '\0'; + /* Add directory to search path */ + (void)Dir_AddDir(dirSearchPath, path); + *cp = savec; + path = cp + 1; + } while (savec == ':'); + free(vpath); +} + +static void +ReadMakefiles(void) +{ + if (opts.makefiles->first != NULL) { + StringListNode *ln; + + for (ln = opts.makefiles->first; ln != NULL; ln = ln->next) { + if (ReadMakefile(ln->datum) != 0) + Fatal("%s: cannot open %s.", + progname, (char *)ln->datum); + } + } else { + char *p1; + (void)Var_Subst("${" MAKEFILE_PREFERENCE "}", + VAR_CMDLINE, VARE_WANTRES, &p1); + /* TODO: handle errors */ + (void)str2Lst_Append(opts.makefiles, p1, NULL); + (void)Lst_ForEachUntil(opts.makefiles, + ReadMakefileSucceeded, NULL); + free(p1); + } +} + +static void +CleanUp(void) +{ +#ifdef CLEANUP + Lst_Destroy(opts.variables, free); + Lst_Free(opts.makefiles); /* don't free, may be used in GNodes */ + Lst_Destroy(opts.create, free); +#endif + + /* print the graph now it's been processed if the user requested it */ + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + + Trace_Log(MAKEEND, 0); + + if (enterFlagObj) + printf("%s: Leaving directory `%s'\n", progname, objdir); + if (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(); +} + /*- * main -- * The main function, for obvious reasons. Initializes variables @@ -988,55 +1368,28 @@ runTargets(void) int main(int argc, char **argv) { - Boolean outOfDate; /* FALSE if all targets up to date */ - struct stat sb, sa; - char *p1, *path; - char mdpath[MAXPATHLEN]; -#ifdef FORCE_MACHINE - const char *machine = FORCE_MACHINE; -#else - const char *machine = getenv("MACHINE"); -#endif - const char *machine_arch = getenv("MACHINE_ARCH"); + Boolean outOfDate; /* FALSE if all targets up to date */ + struct stat sa; + const char *machine; + const char *machine_arch; char *syspath = getenv("MAKESYSPATH"); - Lst sysMkPath; /* Path of sys.mk */ - char *cp = NULL, *start; - /* avoid faults on read-only strings */ - static char defsyspath[] = _PATH_DEFSYSPATH; - char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ - struct timeval rightnow; /* to initialize random seed */ struct utsname utsname; /* default to writing debug to stderr */ - debug_file = stderr; + opts.debug_file = stderr; #ifdef SIGINFO (void)bmake_signal(SIGINFO, siginfo); #endif - /* - * Set the seed to produce a different random sequence - * on each program execution. - */ - gettimeofday(&rightnow, NULL); - srandom(rightnow.tv_sec + rightnow.tv_usec); + + InitRandom(); if ((progname = strrchr(argv[0], '/')) != NULL) progname++; else progname = argv[0]; -#if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)) - /* - * get rid of resource limit on file descriptors - */ - { - struct rlimit rl; - if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && - rl.rlim_cur != rl.rlim_max) { - rl.rlim_cur = rl.rlim_max; - (void)setrlimit(RLIMIT_NOFILE, &rl); - } - } -#endif + + UnlimitFiles(); if (uname(&utsname) == -1) { (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, @@ -1052,44 +1405,8 @@ main(int argc, char **argv) * Note that both MACHINE and MACHINE_ARCH are decided at * run-time. */ - if (!machine) { -#ifdef MAKE_NATIVE - machine = utsname.machine; -#else -#ifdef MAKE_MACHINE - machine = MAKE_MACHINE; -#else - machine = "unknown"; -#endif -#endif - } - - if (!machine_arch) { -#if defined(MAKE_NATIVE) && defined(HAVE_SYSCTL) && defined(CTL_HW) && defined(HW_MACHINE_ARCH) - static char machine_arch_buf[sizeof(utsname.machine)]; - int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; - size_t len = sizeof(machine_arch_buf); - - if (sysctl(mib, __arraycount(mib), machine_arch_buf, - &len, NULL, 0) < 0) { - (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname, - strerror(errno)); - exit(2); - } - - machine_arch = machine_arch_buf; -#else -#ifndef MACHINE_ARCH -#ifdef MAKE_MACHINE_ARCH - machine_arch = MAKE_MACHINE_ARCH; -#else - machine_arch = "unknown"; -#endif -#else - machine_arch = MACHINE_ARCH; -#endif -#endif - } + machine = init_machine(&utsname); + machine_arch = init_machine_arch(); myPid = getpid(); /* remember this for vFork() */ @@ -1115,27 +1432,12 @@ main(int argc, char **argv) VAR_GLOBAL); Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL); - create = Lst_Init(); - makefiles = Lst_Init(); - printVars = 0; - debugVflag = FALSE; - variables = Lst_Init(); - beSilent = FALSE; /* Print commands as executed */ - ignoreErrors = FALSE; /* Pay attention to non-zero returns */ - noExecute = FALSE; /* Execute all commands */ - noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ - keepgoing = FALSE; /* Stop on error */ + CmdOpts_Init(); allPrecious = FALSE; /* Remove targets when interrupted */ deleteOnError = FALSE; /* Historical default behavior */ - queryFlag = FALSE; /* This is not just a check-run */ - noBuiltins = FALSE; /* Read the built-in rules */ - touchFlag = FALSE; /* Actually update targets */ - debug = 0; /* No debug verbosity, please. */ jobsRunning = FALSE; - maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */ - maxJobTokens = maxJobs; - compatMake = FALSE; /* No compat mode */ + maxJobTokens = opts.maxJobs; ignorePWD = FALSE; /* @@ -1151,30 +1453,13 @@ main(int argc, char **argv) * MFLAGS also gets initialized empty, for compatibility. */ Parse_Init(); - if (argv[0][0] == '/' || strchr(argv[0], '/') == NULL) { - /* - * Leave alone if it is an absolute path, or if it does - * not contain a '/' in which case we need to find it in - * the path, like execvp(3) and the shells do. - */ - p1 = argv[0]; - } else { - /* - * A relative path, canonicalize it. - */ - p1 = cached_realpath(argv[0], mdpath); - if (!p1 || *p1 != '/' || stat(p1, &sb) < 0) { - p1 = argv[0]; /* realpath failed */ - } - } - Var_Set("MAKE", p1, VAR_GLOBAL); - Var_Set(".MAKE", p1, VAR_GLOBAL); + InitVarMake(argv[0]); Var_Set(MAKEFLAGS, "", VAR_GLOBAL); Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL); Var_Set("MFLAGS", "", VAR_GLOBAL); Var_Set(".ALLTARGETS", "", VAR_GLOBAL); /* some makefiles need to know this */ - Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMD); + Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMDLINE); /* * Set some other useful macros @@ -1209,9 +1494,11 @@ main(int argc, char **argv) * in a different format). */ #ifdef POSIX - p1 = explode(getenv("MAKEFLAGS")); - Main_ParseArgLine(p1); - free(p1); + { + char *p1 = explode(getenv("MAKEFLAGS")); + Main_ParseArgLine(p1); + free(p1); + } #else Main_ParseArgLine(getenv("MAKE")); #endif @@ -1228,7 +1515,7 @@ main(int argc, char **argv) MainParseArgs(argc, argv); - if (enterFlag) + if (opts.enterFlag) printf("%s: Entering directory `%s'\n", progname, curdir); /* @@ -1240,57 +1527,12 @@ main(int argc, char **argv) exit(2); } - /* - * All this code is so that we know where we are when we start up - * on a different machine with pmake. - * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX - * since the value of curdir can vary depending on how we got - * here. Ie sitting at a shell prompt (shell that provides $PWD) - * or via subdir.mk in which case its likely a shell which does - * not provide it. - * So, to stop it breaking this case only, we ignore PWD if - * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform. - */ #ifndef NO_PWD_OVERRIDE - if (!ignorePWD) { - char *pwd, *ptmp1 = NULL, *ptmp2 = NULL; - - if ((pwd = getenv("PWD")) != NULL && - Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &ptmp1) == NULL) { - const char *makeobjdir = Var_Value("MAKEOBJDIR", - VAR_CMD, &ptmp2); - - if (makeobjdir == NULL || !strchr(makeobjdir, '$')) { - if (stat(pwd, &sb) == 0 && - sa.st_ino == sb.st_ino && - sa.st_dev == sb.st_dev) - (void)strncpy(curdir, pwd, MAXPATHLEN); - } - } - bmake_free(ptmp1); - bmake_free(ptmp2); - } + HandlePWD(&sa); #endif Var_Set(".CURDIR", curdir, VAR_GLOBAL); - /* - * 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. - */ - Dir_InitDir(curdir); - (void)Main_SetObjdir("%s", curdir); - - if (!Main_SetVarObjdir("MAKEOBJDIRPREFIX", curdir) && - !Main_SetVarObjdir("MAKEOBJDIR", "") && - !Main_SetObjdir("%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) && - !Main_SetObjdir("%s.%s", _PATH_OBJDIR, machine) && - !Main_SetObjdir("%s", _PATH_OBJDIR)) - (void)Main_SetObjdir("%s%s", _PATH_OBJDIRPREFIX, curdir); + InitObjdir(machine, machine_arch); /* * Initialize archive, target and suffix modules in preparation for @@ -1306,95 +1548,30 @@ main(int argc, char **argv) Trace_Log(MAKESTART, NULL); - /* - * Set up the .TARGETS variable to contain the list of targets to be - * created. If none specified, make the variable empty -- the parser - * will fill the thing in with the default or .MAIN target. - */ - if (!Lst_IsEmpty(create)) { - LstNode ln; - - for (ln = Lst_First(create); ln != NULL; ln = LstNode_Next(ln)) { - char *name = LstNode_Datum(ln); - Var_Append(".TARGETS", name, VAR_GLOBAL); - } - } else - Var_Set(".TARGETS", "", VAR_GLOBAL); - - - /* - * If no user-supplied system path was given (through the -m option) - * add the directories from the DEFSYSPATH (more than one may be given - * as dir1:...:dirn) to the system include path. - */ - /* XXX: mismatch: the -m option sets sysIncPath, not syspath */ - if (syspath == NULL || syspath[0] == '\0') - syspath = defsyspath; - else - syspath = bmake_strdup(syspath); + InitVarTargets(); - for (start = syspath; *start != '\0'; start = cp) { - for (cp = start; *cp != '\0' && *cp != ':'; cp++) - continue; - if (*cp == ':') { - *cp++ = '\0'; - } - /* look for magic parent directory search string */ - if (strncmp(".../", start, 4) != 0) { - (void)Dir_AddDir(defIncPath, start); - } else { - if (Dir_FindHereOrAbove(curdir, start+4, - found_path, sizeof(found_path))) { - (void)Dir_AddDir(defIncPath, found_path); - } - } - } - if (syspath != defsyspath) - free(syspath); + InitDefSysIncPath(syspath); /* * Read in the built-in rules first, followed by the specified * makefiles, or the default makefile and Makefile, in that order, * if no makefiles were given on the command line. */ - if (!noBuiltins) { - LstNode ln; - - sysMkPath = Lst_Init(); - Dir_Expand(_PATH_DEFSYSMK, - Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath, - sysMkPath); - if (Lst_IsEmpty(sysMkPath)) - Fatal("%s: no system rules (%s).", progname, - _PATH_DEFSYSMK); - ln = Lst_Find(sysMkPath, ReadMakefileSucceeded, NULL); - if (ln == NULL) - Fatal("%s: cannot open %s.", progname, - (char *)LstNode_Datum(Lst_First(sysMkPath))); - } - - if (!Lst_IsEmpty(makefiles)) { - LstNode ln; - - ln = Lst_Find(makefiles, ReadMakefileFailed, NULL); - if (ln != NULL) - Fatal("%s: cannot open %s.", progname, - (char *)LstNode_Datum(ln)); - } else { - p1 = Var_Subst("${" MAKEFILE_PREFERENCE "}", - VAR_CMD, VARE_WANTRES); - (void)str2Lst_Append(makefiles, p1, NULL); - (void)Lst_Find(makefiles, ReadMakefileSucceeded, NULL); - free(p1); - } - + if (!opts.noBuiltins) + ReadBuiltinRules(); + ReadMakefiles(); + /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ - if (!noBuiltins || !printVars) { - makeDependfile = Var_Subst("${.MAKE.DEPENDFILE:T}", - VAR_CMD, VARE_WANTRES); - doing_depend = TRUE; - (void)ReadMakefile(makeDependfile); - doing_depend = FALSE; + if (!opts.noBuiltins || !opts.printVars) { + /* ignore /dev/null and anything starting with "no" */ + (void)Var_Subst("${.MAKE.DEPENDFILE:N/dev/null:Nno*:T}", + VAR_CMDLINE, VARE_WANTRES, &makeDependfile); + if (makeDependfile[0] != '\0') { + /* TODO: handle errors */ + doing_depend = TRUE; + (void)ReadMakefile(makeDependfile); + doing_depend = FALSE; + } } if (enterFlagObj) @@ -1402,81 +1579,33 @@ main(int argc, char **argv) MakeMode(NULL); - Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL); - bmake_free(p1); - - if (!forceJobs && !compatMake && - Var_Exists(".MAKE.JOBS", VAR_GLOBAL)) { - char *value; - int n; + { + void *freeIt; + Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &freeIt), + VAR_GLOBAL); + bmake_free(freeIt); - value = Var_Subst("${.MAKE.JOBS}", VAR_GLOBAL, VARE_WANTRES); - n = strtol(value, NULL, 0); - if (n < 1) { - (void)fprintf(stderr, "%s: illegal value for .MAKE.JOBS -- must be positive integer!\n", - progname); - exit(1); - } - if (n != maxJobs) { - Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); - Var_Append(MAKEFLAGS, value, VAR_GLOBAL); - } - maxJobs = n; - maxJobTokens = maxJobs; - forceJobs = TRUE; - free(value); } + InitMaxJobs(); + /* * Be compatible if user did not specify -j and did not explicitly * turned compatibility on */ - if (!compatMake && !forceJobs) { - compatMake = TRUE; + if (!opts.compatMake && !forceJobs) { + opts.compatMake = TRUE; } - if (!compatMake) + if (!opts.compatMake) Job_ServerStart(maxJobTokens, jp_0, jp_1); - if (DEBUG(JOB)) - fprintf(debug_file, - "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n", - jp_0, jp_1, maxJobs, maxJobTokens, compatMake ? 1 : 0); + 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 (!printVars) + if (!opts.printVars) Main_ExportMAKEFLAGS(TRUE); /* initial export */ - - /* - * For compatibility, look at the directories in the VPATH variable - * and add them to the search path, if the variable is defined. The - * variable's value is in the same format as the PATH envariable, i.e. - * <directory>:<directory>:<directory>... - */ - if (Var_Exists("VPATH", VAR_CMD)) { - char *vpath, savec; - /* - * GCC stores string constants in read-only memory, but - * Var_Subst will want to write this thing, so store it - * in an array - */ - static char VPATH[] = "${VPATH}"; - - vpath = Var_Subst(VPATH, VAR_CMD, VARE_WANTRES); - path = vpath; - do { - /* skip to end of directory */ - for (cp = path; *cp != ':' && *cp != '\0'; cp++) - continue; - /* Save terminator character so know when to stop */ - savec = *cp; - *cp = '\0'; - /* Add directory to search path */ - (void)Dir_AddDir(dirSearchPath, path); - *cp = savec; - path = cp + 1; - } while (savec == ':'); - free(vpath); - } + InitVpath(); /* * Now that all search paths have been read for suffixes et al, it's @@ -1494,42 +1623,17 @@ main(int argc, char **argv) Targ_PrintGraph(1); /* print the values of any variables requested by the user */ - if (printVars) { + if (opts.printVars) { doPrintVars(); outOfDate = FALSE; } else { outOfDate = runTargets(); } -#ifdef CLEANUP - Lst_Free(variables); - Lst_Free(makefiles); - Lst_Destroy(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, 0); - - if (enterFlagObj) - printf("%s: Leaving directory `%s'\n", progname, objdir); - if (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(); + CleanUp(); + if (DEBUG(LINT) && (errors > 0 || Parse_GetFatals() > 0)) + return 2; /* Not 1 so -q can distinguish error */ return outOfDate ? 1 : 0; } @@ -1572,9 +1676,11 @@ ReadMakefile(const char *fname) } /* look in -I and system include directories. */ name = Dir_FindFile(fname, parseIncPath); - if (!name) - name = Dir_FindFile(fname, - Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath); + if (!name) { + SearchPath *sysInc = Lst_IsEmpty(sysIncPath) + ? defSysIncPath : sysIncPath; + name = Dir_FindFile(fname, sysInc); + } if (!name || (fd = open(name, O_RDONLY)) == -1) { free(name); free(path); @@ -1613,10 +1719,10 @@ found: char * Cmd_Exec(const char *cmd, const char **errfmt) { - const char *args[4]; /* Args for invoking the shell */ - int fds[2]; /* Pipe streams */ - int cpid; /* Child PID */ - int pid; /* PID from wait() */ + const char *args[4]; /* Args for invoking the shell */ + int fds[2]; /* Pipe streams */ + int cpid; /* Child PID */ + int pid; /* PID from wait() */ WAIT_T status; /* command exit status */ Buffer buf; /* buffer to store the result */ ssize_t bytes_read; @@ -1704,7 +1810,7 @@ Cmd_Exec(const char *cmd, const char **errfmt) JobReapChild(pid, status, FALSE); continue; } - res_len = Buf_Size(&buf); + res_len = Buf_Len(&buf); res = Buf_Destroy(&buf, FALSE); if (savederr != 0) @@ -1728,24 +1834,17 @@ bad: return bmake_strdup(""); } -/*- - * Error -- - * Print an error message given its format. +/* Print a printf-style error message. * - * Results: - * None. - * - * Side Effects: - * The message is printed. - */ -/* VARARGS */ + * This error message has no consequences, in particular it does not affect + * the exit status. */ void Error(const char *fmt, ...) { va_list ap; FILE *err_file; - err_file = debug_file; + err_file = opts.debug_file; if (err_file == stdout) err_file = stderr; (void)fflush(stdout); @@ -1760,20 +1859,12 @@ Error(const char *fmt, ...) break; err_file = stderr; } + errors++; } -/*- - * Fatal -- - * Produce a Fatal error message. If jobs are running, waits for them - * to finish. - * - * Results: - * None +/* Produce a Fatal error message, then exit immediately. * - * Side Effects: - * The program exits - */ -/* VARARGS */ + * If jobs are running, wait for them to finish. */ void Fatal(const char *fmt, ...) { @@ -1797,18 +1888,8 @@ Fatal(const char *fmt, ...) exit(2); /* Not 1 so -q can distinguish error */ } -/* - * Punt -- - * Major exception once jobs are being created. Kills all jobs, prints - * a message and exits. - * - * Results: - * None - * - * Side Effects: - * All children are killed indiscriminately and the program Lib_Exits - */ -/* VARARGS */ +/* Major exception once jobs are being created. + * Kills all jobs, prints a message and exits. */ void Punt(const char *fmt, ...) { @@ -1827,16 +1908,7 @@ Punt(const char *fmt, ...) DieHorribly(); } -/*- - * DieHorribly -- - * Exit without giving a message. - * - * Results: - * None - * - * Side Effects: - * A big one... - */ +/* Exit without giving a message. */ void DieHorribly(void) { @@ -1848,24 +1920,15 @@ DieHorribly(void) exit(2); /* Not 1, so -q can distinguish error */ } -/* - * Finish -- - * Called when aborting due to errors in child shell to signal - * abnormal exit. - * - * Results: - * None - * - * Side Effects: - * The program exits - */ +/* 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 errors) - /* number of errors encountered in Make_Make */ +Finish(int errs) { if (dieQuietly(NULL, -1)) exit(2); - Fatal("%d error%s", errors, errors == 1 ? "" : "s"); + Fatal("%d error%s", errs, errs == 1 ? "" : "s"); } /* @@ -1887,37 +1950,45 @@ eunlink(const char *file) 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; + } +} + /* - * execError -- + * execDie -- * Print why exec failed, avoiding stdio. */ -void -execError(const char *af, const char *av) +void MAKE_ATTR_DEAD +execDie(const char *af, const char *av) { -#ifdef USE_IOVEC - int i = 0; - struct iovec iov[8]; -#define IOADD(s) \ - (void)(iov[i].iov_base = UNCONST(s), \ - iov[i].iov_len = strlen(iov[i].iov_base), \ - i++) -#else -#define IOADD(s) (void)write(2, s, strlen(s)) -#endif + Buffer buf; - IOADD(progname); - IOADD(": "); - IOADD(af); - IOADD("("); - IOADD(av); - IOADD(") failed ("); - IOADD(strerror(errno)); - IOADD(")\n"); - -#ifdef USE_IOVEC - while (writev(2, iov, 8) == -1 && errno == EAGAIN) - continue; -#endif + Buf_Init(&buf, 0); + Buf_AddStr(&buf, progname); + Buf_AddStr(&buf, ": "); + Buf_AddStr(&buf, af); + Buf_AddStr(&buf, "("); + Buf_AddStr(&buf, av); + Buf_AddStr(&buf, ") failed ("); + Buf_AddStr(&buf, strerror(errno)); + Buf_AddStr(&buf, ")\n"); + + write_all(STDERR_FILENO, Buf_GetAll(&buf, NULL), Buf_Len(&buf)); + + Buf_Destroy(&buf, TRUE); + _exit(1); } /* @@ -1964,16 +2035,17 @@ static void purge_cached_realpaths(void) { GNode *cache = get_cached_realpaths(); - Hash_Entry *he, *nhe; - Hash_Search hs; - - he = Hash_EnumFirst(&cache->context, &hs); - while (he) { - nhe = Hash_EnumNext(&hs); - if (he->name[0] != '/') { + HashEntry *he, *nhe; + HashIter hi; + + HashIter_Init(&hi, &cache->context); + he = HashIter_Next(&hi); + while (he != NULL) { + nhe = HashIter_Next(&hi); + if (he->key[0] != '/') { if (DEBUG(DIR)) - fprintf(stderr, "cached_realpath: purging %s\n", he->name); - Hash_DeleteEntry(&cache->context, he); + fprintf(stderr, "cached_realpath: purging %s\n", he->key); + HashTable_DeleteEntry(&cache->context, he); } he = nhe; } @@ -1984,41 +2056,24 @@ cached_realpath(const char *pathname, char *resolved) { GNode *cache; const char *rp; - char *cp; + void *freeIt; if (!pathname || !pathname[0]) return NULL; cache = get_cached_realpaths(); - if ((rp = Var_Value(pathname, cache, &cp)) != NULL) { + if ((rp = Var_Value(pathname, cache, &freeIt)) != NULL) { /* a hit */ strlcpy(resolved, rp, MAXPATHLEN); } else if ((rp = realpath(pathname, resolved)) != NULL) { Var_Set(pathname, rp, cache); } /* else should we negative-cache? */ - bmake_free(cp); + bmake_free(freeIt); return rp ? resolved : NULL; } -int -PrintAddr(void *a, void *b) -{ - printf("%lx ", (unsigned long) a); - return b ? 0 : 0; -} - - -static int -addErrorCMD(void *cmdp, void *gnp MAKE_ATTR_UNUSED) -{ - if (cmdp == NULL) - return 1; /* stop */ - Var_Append(".ERROR_CMD", cmdp, VAR_GLOBAL); - return 0; -} - /* * Return true if we should die without noise. * For example our failing child was a sub-make @@ -2030,16 +2085,36 @@ dieQuietly(GNode *gn, int bf) static int quietly = -1; if (quietly < 0) { - if (DEBUG(JOB) || getBoolean(".MAKE.DIE_QUIETLY", 1) == 0) + if (DEBUG(JOB) || !getBoolean(".MAKE.DIE_QUIETLY", TRUE)) quietly = 0; else if (bf >= 0) quietly = bf; else - quietly = (gn) ? ((gn->type & (OP_MAKE)) != 0) : 0; + quietly = gn != NULL ? ((gn->type & (OP_MAKE)) != 0) : 0; } return quietly; } +static void +SetErrorVars(GNode *gn) +{ + StringListNode *ln; + + /* + * We can print this even if there is no .ERROR target. + */ + Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL); + Var_Delete(".ERROR_CMD", VAR_GLOBAL); + + for (ln = gn->commands->first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; + + if (cmd == NULL) + break; + Var_Append(".ERROR_CMD", cmd, VAR_GLOBAL); + } +} + void PrintOnError(GNode *gn, const char *s) { @@ -2063,16 +2138,11 @@ PrintOnError(GNode *gn, const char *s) if (en) return; /* we've been here! */ - if (gn) { - /* - * We can print this even if there is no .ERROR target. - */ - Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL); - Var_Delete(".ERROR_CMD", VAR_GLOBAL); - Lst_ForEach(gn->commands, addErrorCMD, gn); - } + if (gn) + SetErrorVars(gn); expr = "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}"; - cp = Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES); + (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &cp); + /* TODO: handle errors */ printf("%s", cp); free(cp); fflush(stdout); @@ -2080,7 +2150,7 @@ PrintOnError(GNode *gn, const char *s) /* * Finally, see if there is a .ERROR target, and run it if so. */ - en = Targ_FindNode(".ERROR", TARG_NOCREATE); + en = Targ_FindNode(".ERROR"); if (en) { en->type |= OP_SPECIAL; Compat_Make(en, en); @@ -2099,7 +2169,8 @@ Main_ExportMAKEFLAGS(Boolean first) once = FALSE; expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}"; - s = Var_Subst(expr, VAR_CMD, VARE_WANTRES); + (void)Var_Subst(expr, VAR_CMDLINE, VARE_WANTRES, &s); + /* TODO: handle errors */ if (s[0] != '\0') { #ifdef POSIX setenv("MAKEFLAGS", s, 1); @@ -2121,8 +2192,9 @@ getTmpdir(void) * Honor $TMPDIR but only if it is valid. * Ensure it ends with /. */ - tmpdir = Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL, - VARE_WANTRES); + (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL, + VARE_WANTRES, &tmpdir); + /* TODO: handle errors */ if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { free(tmpdir); tmpdir = bmake_strdup(_PATH_TMP); @@ -2133,19 +2205,19 @@ getTmpdir(void) /* * Create and open a temp file using "pattern". - * If "fnamep" is provided set it to a copy of the filename created. + * 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 **fnamep) +mkTempFile(const char *pattern, char **out_fname) { static char *tmpdir = NULL; char tfile[MAXPATHLEN]; int fd; - if (!pattern) + if (pattern != NULL) pattern = TMPPAT; - if (!tmpdir) + if (tmpdir == NULL) tmpdir = getTmpdir(); if (pattern[0] == '/') { snprintf(tfile, sizeof(tfile), "%s", pattern); @@ -2154,8 +2226,8 @@ mkTempFile(const char *pattern, char **fnamep) } if ((fd = mkstemp(tfile)) < 0) Punt("Could not create temporary file %s: %s", tfile, strerror(errno)); - if (fnamep) { - *fnamep = bmake_strdup(tfile); + if (out_fname) { + *out_fname = bmake_strdup(tfile); } else { unlink(tfile); /* we just want the descriptor */ } @@ -2170,50 +2242,41 @@ mkTempFile(const char *pattern, char **fnamep) Boolean s2Boolean(const char *s, Boolean bf) { - if (s) { - switch(*s) { - case '\0': /* not set - the default wins */ - break; - case '0': - case 'F': - case 'f': - case 'N': - case 'n': - bf = FALSE; - break; - case 'O': - case 'o': - switch (s[1]) { - case 'F': - case 'f': - bf = FALSE; - break; - default: - bf = TRUE; - break; - } - break; - default: - bf = TRUE; - break; - } + switch(s[0]) { + case '\0': /* not set - the default wins */ + break; + case '0': + case 'F': + case 'f': + case 'N': + case 'n': + return FALSE; + case 'O': + case 'o': + return s[1] != 'F' && s[1] != 'f'; + default: + return TRUE; } return bf; } /* - * Return a Boolean based on setting of a knob. + * Return a Boolean based on a variable. * - * If the knob is not set, the supplied default is the return value. - * If set, anything that looks or smells like "No", "False", "Off", "0" etc, + * If the knob is not set, return the fallback. + * If set, anything that looks or smells like "No", "False", "Off", "0", etc. * is FALSE, otherwise TRUE. */ Boolean -getBoolean(const char *name, Boolean fallback) +getBoolean(const char *varname, Boolean fallback) { - char *expr = str_concat3("${", name, ":U:tl}"); - char *value = Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES); - Boolean res = s2Boolean(value, fallback); + char *expr = str_concat3("${", varname, ":U}"); + char *value; + Boolean res; + + (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &value); + /* TODO: handle errors */ + res = s2Boolean(value, fallback); free(value); free(expr); return res; diff --git a/make-bootstrap.sh.in b/make-bootstrap.sh.in index ec100138b6d1..a75634b3dae6 100755 --- a/make-bootstrap.sh.in +++ b/make-bootstrap.sh.in @@ -58,22 +58,15 @@ do_link() { ${CC} ${LDSTATIC} ${LDFLAGS} -o "$output" "$@" ${LIBS} } -BASE_OBJECTS="arch.o buf.o compat.o cond.o dir.o for.o getopt hash.o \ -make.o make_malloc.o metachar.o parse.o sigcompat.o str.o strlist.o \ +BASE_OBJECTS="arch.o buf.o compat.o cond.o dir.o enum.o for.o getopt hash.o \ +lst.o make.o make_malloc.o metachar.o parse.o sigcompat.o str.o strlist.o \ suff.o targ.o trace.o var.o util.o" -LST_OBJECTS="lstAppend.o lstDupl.o lstInit.o lstOpen.o \ -lstAtEnd.o lstEnQueue.o lstInsert.o lstAtFront.o lstIsAtEnd.o \ -lstClose.o lstFind.o lstIsEmpty.o lstRemove.o lstConcat.o \ -lstFindFrom.o lstLast.o lstReplace.o lstFirst.o lstDatum.o \ -lstForEach.o lstMember.o lstSucc.o lstDeQueue.o lstForEachFrom.o \ -lstDestroy.o lstNext.o lstPrev.o" - LIB_OBJECTS="@LIBOBJS@" do_compile main.o ${MDEFS} -for o in ${BASE_OBJECTS} ${LST_OBJECTS} ${LIB_OBJECTS} +for o in ${BASE_OBJECTS} ${LIB_OBJECTS} do do_compile "$o" done diff --git a/make-conf.h b/make-conf.h index 5b13e295ae0c..bc3b9e7e4915 100644 --- a/make-conf.h +++ b/make-conf.h @@ -1,4 +1,4 @@ -/* $NetBSD: config.h,v 1.22 2020/09/01 17:40:34 rillig Exp $ */ +/* $NetBSD: config.h,v 1.25 2020/10/19 23:43:55 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -76,10 +76,11 @@ * DEFMAXJOBS * DEFMAXLOCAL * These control the default concurrency. On no occasion will more - * than DEFMAXJOBS targets be created at once (locally or remotely) + * than DEFMAXJOBS targets be created at once (locally or remotely). + * * DEFMAXLOCAL is the highest number of targets which will be * created on the local machine at once. Note that if you set this - * to 0, nothing will ever happen... + * to 0, nothing will ever happen. */ #define DEFMAXJOBS 4 #define DEFMAXLOCAL 1 @@ -88,10 +89,12 @@ * INCLUDES * LIBRARIES * These control the handling of the .INCLUDES and .LIBS variables. + * * If INCLUDES is defined, the .INCLUDES variable will be filled * from the search paths of those suffixes which are marked by - * .INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS - * See suff.c for more details. + * .INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS. + * + * See varname-dot-include.mk and varname-dot-libs.mk for more details. */ #define INCLUDES #define LIBRARIES @@ -108,11 +111,13 @@ * If defined, Make_Update will check a target for its current * modification time after it has been re-made, setting it to the * starting time of the make only if the target still doesn't exist. + * * Unfortunately, under NFS the modification time often doesn't * get updated in time, so a target will appear to not have been - * re-made, causing later targets to appear up-to-date. On systems - * that don't have this problem, you should define this. Under - * NFS you probably should not, unless you aren't exporting jobs. + * re-made, causing later targets to appear up-to-date. + * + * On systems that don't have this problem, you should define this. + * Under NFS you probably should not, unless you aren't exporting jobs. */ #define RECHECK @@ -128,8 +133,10 @@ /* * SYSVINCLUDE * Recognize system V like include directives [include "filename"] + * (required by POSIX 2018) * SYSVVARSUB * Recognize system V like ${VAR:x=y} variable substitutions + * (required by POSIX 2018) */ #define SYSVINCLUDE #define SYSVVARSUB @@ -149,14 +156,6 @@ */ #define SUNSHCMD -/* - * USE_IOVEC - * We have writev(2) - */ -#ifdef HAVE_SYS_UIO_H -# define USE_IOVEC -#endif - #if defined(MAKE_NATIVE) && !defined(__ELF__) # ifndef RANLIBMAG # define RANLIBMAG "__.SYMDEF" @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.289 2020/08/28 17:15:04 rillig Exp $ +.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,7 +29,7 @@ .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd August 28, 2020 +.Dd November 1, 2020 .Dt MAKE 1 .Os .Sh NAME @@ -1872,7 +1872,7 @@ has been defined and has commands associated with it. .Ar Expression may also be an arithmetic or string comparison. Variable expansion is -performed on both sides of the comparison, after which the integral +performed on both sides of the comparison, after which the numerical values are compared. A value is interpreted as hexadecimal if it is preceded by 0x, otherwise it is decimal; octal numbers are not supported. @@ -1882,7 +1882,7 @@ variable expansion, either the left or right hand side of a .Ql Ic == or .Ql Ic "!=" -operator is not an integral value, then +operator is not a numerical value, then string comparison is performed between the expanded variables. If no relational operator is given, it is assumed that the expanded @@ -1,4 +1,4 @@ -/* $NetBSD: make.c,v 1.133 2020/08/30 14:11:42 rillig Exp $ */ +/* $NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,85 +68,74 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: make.c,v 1.133 2020/08/30 14:11:42 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)make.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: make.c,v 1.133 2020/08/30 14:11:42 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - /*- * make.c -- * The functions which perform the examination of targets and * their suitability for creation * * Interface: - * Make_Run Initialize things for the module and recreate - * whatever needs recreating. Returns TRUE if - * work was (or would have been) done and FALSE - * otherwise. + * Make_Run Initialize things for the module and recreate + * whatever needs recreating. Returns TRUE if + * work was (or would have been) done and FALSE + * otherwise. * - * Make_Update Update all parents of a given child. Performs - * various bookkeeping chores like the updating - * of the cmgn field of the parent, filling - * of the IMPSRC context variable, etc. It will - * place the parent on the toBeMade queue if it - * should be. + * Make_Update Update all parents of a given child. Performs + * various bookkeeping chores like the updating + * of the youngestChild field of the parent, filling + * of the IMPSRC context variable, etc. It will + * place the parent on the toBeMade queue if it + * should be. * - * Make_TimeStamp Function to set the parent's cmgn field - * based on a child's modification time. + * Make_TimeStamp Function to set the parent's youngestChild field + * based on a child's modification time. * - * Make_DoAllVar Set up the various local variables for a - * target, including the .ALLSRC variable, making - * sure that any variable that needs to exist - * at the very least has the empty value. + * Make_DoAllVar Set up the various local variables for a + * target, including the .ALLSRC variable, making + * sure that any variable that needs to exist + * at the very least has the empty value. * - * Make_OODate Determine if a target is out-of-date. + * Make_OODate Determine if a target is out-of-date. * - * Make_HandleUse See if a child is a .USE node for a parent - * and perform the .USE actions if so. + * Make_HandleUse See if a child is a .USE node for a parent + * and perform the .USE actions if so. * - * Make_ExpandUse Expand .USE nodes + * Make_ExpandUse Expand .USE nodes */ -#include "make.h" -#include "enum.h" -#include "dir.h" -#include "job.h" - -static unsigned int checked = 1;/* Sequence # to detect recursion */ -static Lst toBeMade; /* The current fringe of the graph. These - * are nodes which await examination by - * MakeOODate. It is added to by - * Make_Update and subtracted from by - * MakeStartJobs */ - -static int MakeAddChild(void *, void *); -static int MakeFindChild(void *, void *); -static int MakeUnmark(void *, void *); -static int MakeAddAllSrc(void *, void *); -static int MakeTimeStamp(void *, void *); -static int MakeHandleUse(void *, void *); -static Boolean MakeStartJobs(void); -static int MakePrintStatus(void *, void *); +#include "make.h" +#include "dir.h" +#include "job.h" + +/* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ +MAKE_RCSID("$NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $"); + +/* Sequence # to detect recursion. */ +static unsigned int checked = 1; + +/* The current fringe of the graph. + * These are nodes which await examination by MakeOODate. + * It is added to by Make_Update and subtracted from by MakeStartJobs */ +static GNodeList *toBeMade; + static int MakeCheckOrder(void *, void *); -static int MakeBuildChild(void *, void *); static int MakeBuildParent(void *, void *); +void +debug_printf(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vfprintf(opts.debug_file, fmt, args); + va_end(args); +} + MAKE_ATTR_DEAD static void make_abort(GNode *gn, int line) { - static int two = 2; - - fprintf(debug_file, "make_abort from line %d\n", line); - Targ_PrintNode(gn, &two); - Lst_ForEach(toBeMade, Targ_PrintNode, &two); + debug_printf("make_abort from line %d\n", line); + Targ_PrintNode(gn, 2); + Targ_PrintNodes(toBeMade, 2); Targ_PrintGraph(3); abort(); } @@ -188,65 +177,31 @@ GNode_FprintDetails(FILE *f, const char *prefix, const GNode *gn, suffix); } -/*- - *----------------------------------------------------------------------- - * Make_TimeStamp -- - * Set the cmgn field of a parent node based on the mtime stamp in its - * child. Called from MakeOODate via Lst_ForEach. - * - * Input: - * pgn the current parent - * cgn the child we've just examined - * - * Results: - * Always returns 0. - * - * Side Effects: - * The cmgn of the parent node will be changed if the mtime - * field of the child is greater than it. - *----------------------------------------------------------------------- - */ -int -Make_TimeStamp(GNode *pgn, GNode *cgn) +Boolean +GNode_ShouldExecute(GNode *gn) { - if (pgn->cmgn == NULL || cgn->mtime > pgn->cmgn->mtime) { - pgn->cmgn = cgn; - } - return 0; + return !((gn->type & OP_MAKE) ? opts.noRecursiveExecute : opts.noExecute); } -/* - * Input: - * pgn the current parent - * cgn the child we've just examined - * - */ -static int -MakeTimeStamp(void *pgn, void *cgn) +/* Update the youngest child of the node, according to the given child. */ +void +Make_TimeStamp(GNode *pgn, GNode *cgn) { - return Make_TimeStamp((GNode *)pgn, (GNode *)cgn); + if (pgn->youngestChild == NULL || cgn->mtime > pgn->youngestChild->mtime) { + pgn->youngestChild = cgn; + } } -/*- - *----------------------------------------------------------------------- - * Make_OODate -- - * See if a given node is out of date with respect to its sources. - * Used by Make_Run when deciding which nodes to place on the - * toBeMade queue initially and by Make_Update to screen out USE and - * EXEC nodes. In the latter case, however, any other sort of node - * must be considered out-of-date since at least one of its children - * will have been recreated. - * - * Input: - * gn the node to check +/* See if the node is out of date with respect to its sources. * - * Results: - * TRUE if the node is out of date. FALSE otherwise. + * 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. * - * Side Effects: - * The mtime field of the node and the cmgn field of its parents - * will/may be changed. - *----------------------------------------------------------------------- + * The mtime field of the node and the youngestChild field of its parents + * may be changed. */ Boolean Make_OODate(GNode *gn) @@ -261,17 +216,17 @@ Make_OODate(GNode *gn) (void)Dir_MTime(gn, 1); if (DEBUG(MAKE)) { if (gn->mtime != 0) { - fprintf(debug_file, "modified %s...", Targ_FmtTime(gn->mtime)); + debug_printf("modified %s...", Targ_FmtTime(gn->mtime)); } else { - fprintf(debug_file, "non-existent..."); + debug_printf("non-existent..."); } } } /* * A target is remade in one of the following circumstances: - * its modification time is smaller than that of its youngest child - * and it would actually be run (has commands or type OP_NOP) + * 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. @@ -287,33 +242,25 @@ Make_OODate(GNode *gn) * If the node is a USE node it is *never* out of date * no matter *what*. */ - if (DEBUG(MAKE)) { - fprintf(debug_file, ".USE node..."); - } + DEBUG0(MAKE, ".USE node..."); oodate = FALSE; } else if ((gn->type & OP_LIB) && ((gn->mtime==0) || Arch_IsLib(gn))) { - if (DEBUG(MAKE)) { - fprintf(debug_file, "library..."); - } + DEBUG0(MAKE, "library..."); /* * always out of date if no children and :: target * or non-existent. */ oodate = (gn->mtime == 0 || Arch_LibOODate(gn) || - (gn->cmgn == NULL && (gn->type & OP_DOUBLEDEP))); + (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. */ - if (DEBUG(MAKE)) { - fprintf(debug_file, ".JOIN node..."); - } - if (DEBUG(MAKE)) { - fprintf(debug_file, "source %smade...", gn->flags & CHILDMADE ? "" : "not "); - } + DEBUG0(MAKE, ".JOIN node..."); + DEBUG1(MAKE, "source %smade...", gn->flags & CHILDMADE ? "" : "not "); oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE; } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) { /* @@ -322,34 +269,36 @@ Make_OODate(GNode *gn) */ if (DEBUG(MAKE)) { if (gn->type & OP_FORCE) { - fprintf(debug_file, "! operator..."); + debug_printf("! operator..."); } else if (gn->type & OP_PHONY) { - fprintf(debug_file, ".PHONY node..."); + debug_printf(".PHONY node..."); } else { - fprintf(debug_file, ".EXEC node..."); + debug_printf(".EXEC node..."); } } oodate = TRUE; - } else if ((gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) || - (gn->cmgn == NULL && + } else if ((gn->youngestChild != NULL && + gn->mtime < gn->youngestChild->mtime) || + (gn->youngestChild == NULL && ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) - || gn->type & OP_DOUBLEDEP))) + || gn->type & OP_DOUBLEDEP))) { /* * A node whose modification time is less than that of its - * youngest child or that has no children (cmgn == NULL) and + * youngest child or that has no children (youngestChild == NULL) and * either doesn't exist (mtime == 0) and it isn't optional * or was the object of a * :: operator is out-of-date. * Why? Because that's the way Make does it. */ if (DEBUG(MAKE)) { - if (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) { - fprintf(debug_file, "modified before source %s...", - gn->cmgn->path ? gn->cmgn->path : gn->cmgn->name); + if (gn->youngestChild != NULL && + gn->mtime < gn->youngestChild->mtime) { + debug_printf("modified before source %s...", + GNode_Path(gn->youngestChild)); } else if (gn->mtime == 0) { - fprintf(debug_file, "non-existent and no sources..."); + debug_printf("non-existent and no sources..."); } else { - fprintf(debug_file, ":: operator and no sources..."); + debug_printf(":: operator and no sources..."); } } oodate = TRUE; @@ -363,7 +312,7 @@ Make_OODate(GNode *gn) */ if (DEBUG(MAKE)) { if (gn->flags & FORCE) - fprintf(debug_file, "non existing child..."); + debug_printf("non existing child..."); } oodate = (gn->flags & FORCE) ? TRUE : FALSE; } @@ -377,71 +326,47 @@ Make_OODate(GNode *gn) /* * If the target isn't out-of-date, the parents need to know its * modification time. Note that targets that appear to be out-of-date - * but aren't, because they have no commands and aren't of type OP_NOP, + * 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) { - Lst_ForEach(gn->parents, MakeTimeStamp, gn); + GNodeListNode *ln; + for (ln = gn->parents->first; ln != NULL; ln = ln->next) + Make_TimeStamp(ln->datum, gn); } return oodate; } -/*- - *----------------------------------------------------------------------- - * MakeAddChild -- - * Function used by Make_Run to add a child to the list l. - * It will only add the child if its make field is FALSE. - * - * Input: - * gnp the node to add - * lp the list to which to add it - * - * Results: - * Always returns 0 - * - * Side Effects: - * The given list is extended - *----------------------------------------------------------------------- - */ +/* Add the node to the list if it needs to be examined. */ static int MakeAddChild(void *gnp, void *lp) { - GNode *gn = (GNode *)gnp; - Lst l = (Lst) lp; + GNode *gn = gnp; + GNodeList *l = lp; if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) { - if (DEBUG(MAKE)) - fprintf(debug_file, "MakeAddChild: need to examine %s%s\n", - gn->name, gn->cohort_num); + DEBUG2(MAKE, "MakeAddChild: need to examine %s%s\n", + gn->name, gn->cohort_num); Lst_Enqueue(l, gn); } return 0; } -/*- - *----------------------------------------------------------------------- - * MakeFindChild -- - * Function used by Make_Run to find the pathname of a child - * that was already made. +/* Find the pathname of a child that was already made. + * + * The path and mtime of the node and the youngestChild of the parent are + * updated; the unmade children count of the parent is decremented. * * Input: * gnp the node to find - * - * Results: - * Always returns 0 - * - * Side Effects: - * The path and mtime of the node and the cmgn of the parent are - * updated; the unmade children count of the parent is decremented. - *----------------------------------------------------------------------- */ static int MakeFindChild(void *gnp, void *pgnp) { - GNode *gn = (GNode *)gnp; - GNode *pgn = (GNode *)pgnp; + GNode *gn = gnp; + GNode *pgn = pgnp; (void)Dir_MTime(gn, 0); Make_TimeStamp(pgn, gn); @@ -459,17 +384,18 @@ MakeFindChild(void *gnp, void *pgnp) * has commands. * * Input: - * cgn The .USE node - * pgn The target of the .USE node + * 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) { - LstNode ln; /* An element in the children list */ + GNodeListNode *ln; /* An element in the children list */ #ifdef DEBUG_SRC if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) { - fprintf(debug_file, "Make_HandleUse: called for plain node %s\n", cgn->name); + debug_printf("Make_HandleUse: called for plain node %s\n", cgn->name); return; } #endif @@ -484,9 +410,8 @@ Make_HandleUse(GNode *cgn, GNode *pgn) } } - Lst_Open(cgn->children); - while ((ln = Lst_Next(cgn->children)) != NULL) { - GNode *gn = LstNode_Datum(ln); + for (ln = cgn->children->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; /* * Expand variables in the .USE node's name @@ -499,57 +424,44 @@ Make_HandleUse(GNode *cgn, GNode *pgn) } else { free(gn->name); } - gn->name = Var_Subst(gn->uname, pgn, VARE_WANTRES); + (void)Var_Subst(gn->uname, pgn, VARE_WANTRES, &gn->name); + /* TODO: handle errors */ if (gn->uname && strcmp(gn->name, gn->uname) != 0) { /* See if we have a target for this node. */ - GNode *tgn = Targ_FindNode(gn->name, TARG_NOCREATE); + GNode *tgn = Targ_FindNode(gn->name); if (tgn != NULL) gn = tgn; } Lst_Append(pgn->children, gn); Lst_Append(gn->parents, pgn); - pgn->unmade += 1; + pgn->unmade++; } - Lst_Close(cgn->children); pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_USEBEFORE|OP_TRANSFORM); } -/*- - *----------------------------------------------------------------------- - * MakeHandleUse -- - * Callback function for Lst_ForEach, used by Make_Run on the downward - * pass to handle .USE nodes. Should be called before the children - * are enqueued to be looked at by MakeAddChild. - * This function calls Make_HandleUse to copy the .USE node's commands, - * type flags and children to the parent node. - * - * Input: - * cgnp the child we've just examined - * pgnp the current parent - * - * Results: - * returns 0. +/* 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. * - * Side Effects: - * After expansion, .USE child nodes are removed from the parent + * 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 int -MakeHandleUse(void *cgnp, void *pgnp) +static void +MakeHandleUse(GNode *cgn, GNode *pgn, GNodeListNode *ln) { - GNode *cgn = (GNode *)cgnp; - GNode *pgn = (GNode *)pgnp; - LstNode ln; /* An element in the children list */ - int unmarked; + Boolean unmarked; unmarked = ((cgn->type & OP_MARK) == 0); cgn->type |= OP_MARK; if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0) - return 0; + return; if (unmarked) Make_HandleUse(cgn, pgn); @@ -561,29 +473,23 @@ MakeHandleUse(void *cgnp, void *pgnp) * children the parent has. This is used by Make_Run to decide * whether to queue the parent or examine its children... */ - if ((ln = Lst_FindDatum(pgn->children, cgn)) != NULL) { - Lst_Remove(pgn->children, ln); - pgn->unmade--; + 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); } - return 0; } -/*- - *----------------------------------------------------------------------- - * Make_Recheck -- - * Check the modification time of a gnode, and update it as described - * in the comments below. - * - * Results: - * returns 0 if the gnode does not exist, or its filesystem - * time if it does. - * - * Side Effects: - * the gnode's modification time and path name are affected. - * - *----------------------------------------------------------------------- - */ +/* 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) { @@ -598,10 +504,10 @@ Make_Recheck(GNode *gn) * 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 + * 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 @@ -641,74 +547,74 @@ Make_Recheck(GNode *gn) * the target is made now. Otherwise archives with ... rules * don't work! */ - if (NoExecute(gn) || (gn->type & OP_SAVE_CMDS) || + if (!GNode_ShouldExecute(gn) || (gn->type & OP_SAVE_CMDS) || (mtime == 0 && !(gn->type & OP_WAIT))) { - if (DEBUG(MAKE)) { - fprintf(debug_file, " recheck(%s): update time from %s to now\n", - gn->name, Targ_FmtTime(gn->mtime)); - } + DEBUG2(MAKE, " recheck(%s): update time from %s to now\n", + gn->name, Targ_FmtTime(gn->mtime)); gn->mtime = now; } else { - if (DEBUG(MAKE)) { - fprintf(debug_file, " recheck(%s): current update time: %s\n", - gn->name, Targ_FmtTime(gn->mtime)); - } + DEBUG2(MAKE, " recheck(%s): current update time: %s\n", + gn->name, Targ_FmtTime(gn->mtime)); } #endif return mtime; } -/*- - *----------------------------------------------------------------------- - * Make_Update -- - * Perform update on the parents of a node. Used by JobFinish once - * a node has been dealt with and by MakeStartJobs if it finds an - * up-to-date node. - * - * Input: - * cgn the child node - * - * Results: - * Always returns 0 - * - * Side Effects: - * The unmade field of pgn is decremented and pgn may be placed on - * the toBeMade queue if this field becomes 0. +/* + * Set the .PREFIX and .IMPSRC variables for all the implied parents + * of this node. + */ +static void +UpdateImplicitParentsVars(GNode *cgn, const char *cname) +{ + GNodeListNode *ln; + const char *cpref = GNode_VarPrefix(cgn); + + for (ln = cgn->implicitParents->first; ln != NULL; ln = ln->next) { + GNode *pgn = ln->datum; + if (pgn->flags & REMAKE) { + Var_Set(IMPSRC, cname, pgn); + if (cpref != NULL) + Var_Set(PREFIX, cpref, pgn); + } + } +} + +/* 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. * - * If the child was made, the parent's flag CHILDMADE field will be - * set true. + * The unmade field of pgn is decremented and pgn may be placed on + * the toBeMade queue if this field becomes 0. * - * If the child is not up-to-date and still does not exist, - * set the FORCE flag on the parents. + * If the child was made, the parent's flag CHILDMADE field will be + * set true. * - * If the child wasn't made, the cmgn field of the parent will be - * altered if the child's mtime is big enough. + * If the child is not up-to-date and still does not exist, + * set the FORCE flag on the parents. * - * Finally, if the child is the implied source for the parent, the - * parent's IMPSRC variable is set appropriately. + * 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) { - GNode *pgn; /* the parent node */ - const char *cname; /* the child's name */ - LstNode ln; /* Element in parents and implicitParents lists */ + const char *cname; /* the child's name */ time_t mtime = -1; - char *p1; - Lst parents; + GNodeList *parents; + GNodeListNode *ln; GNode *centurion; /* It is save to re-examine any nodes again */ checked++; - cname = Var_Value(TARGET, cgn, &p1); - bmake_free(p1); + cname = GNode_VarTarget(cgn); - if (DEBUG(MAKE)) - fprintf(debug_file, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num); + DEBUG2(MAKE, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num); /* * If the child was actually made, see what its modification time is @@ -726,7 +632,7 @@ Make_Update(GNode *cgn) if ((centurion = cgn->centurion) != NULL) { if (!Lst_IsEmpty(cgn->parents)) Punt("%s%s: cohort has parents", cgn->name, cgn->cohort_num); - centurion->unmade_cohorts -= 1; + centurion->unmade_cohorts--; if (centurion->unmade_cohorts < 0) Error("Graph cycles through centurion %s", centurion->name); } else { @@ -735,22 +641,21 @@ Make_Update(GNode *cgn) parents = centurion->parents; /* If this was a .ORDER node, schedule the RHS */ - Lst_ForEach(centurion->order_succ, MakeBuildParent, Lst_First(toBeMade)); + Lst_ForEachUntil(centurion->order_succ, MakeBuildParent, toBeMade->first); /* Now mark all the parents as having one less unmade child */ - Lst_Open(parents); - while ((ln = Lst_Next(parents)) != NULL) { - pgn = LstNode_Datum(ln); + for (ln = parents->first; ln != NULL; ln = ln->next) { + GNode *pgn = ln->datum; + if (DEBUG(MAKE)) - fprintf(debug_file, "inspect parent %s%s: flags %x, " - "type %x, made %d, unmade %d ", - pgn->name, pgn->cohort_num, pgn->flags, - pgn->type, pgn->made, pgn->unmade-1); + debug_printf("inspect parent %s%s: flags %x, " + "type %x, made %d, unmade %d ", + pgn->name, pgn->cohort_num, pgn->flags, + pgn->type, pgn->made, pgn->unmade - 1); if (!(pgn->flags & REMAKE)) { /* This parent isn't needed */ - if (DEBUG(MAKE)) - fprintf(debug_file, "- not needed\n"); + DEBUG0(MAKE, "- not needed\n"); continue; } if (mtime == 0 && !(cgn->type & OP_WAIT)) @@ -758,15 +663,14 @@ Make_Update(GNode *cgn) /* * If the parent has the .MADE attribute, its timestamp got - * updated to that of its newest child, and its unmake + * 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) { - if (DEBUG(MAKE)) - fprintf(debug_file, "- .MADE\n"); + DEBUG0(MAKE, "- .MADE\n"); continue; } @@ -781,19 +685,17 @@ Make_Update(GNode *cgn) * of a `::' dependency. */ if (centurion->unmade_cohorts != 0 || centurion->made < MADE) { - if (DEBUG(MAKE)) - fprintf(debug_file, - "- centurion made %d, %d unmade cohorts\n", - centurion->made, centurion->unmade_cohorts); + 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 -= 1; + pgn->unmade--; if (pgn->unmade < 0) { if (DEBUG(MAKE)) { - fprintf(debug_file, "Graph cycles through %s%s\n", - pgn->name, pgn->cohort_num); + 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); @@ -802,8 +704,7 @@ Make_Update(GNode *cgn) /* We must always rescan the parents of .WAIT and .ORDER nodes. */ if (pgn->unmade != 0 && !(centurion->type & OP_WAIT) && !(centurion->flags & DONE_ORDER)) { - if (DEBUG(MAKE)) - fprintf(debug_file, "- unmade children\n"); + DEBUG0(MAKE, "- unmade children\n"); continue; } if (pgn->made != DEFERRED) { @@ -812,113 +713,78 @@ Make_Update(GNode *cgn) * or it on the RHS of a .WAIT directive * or it is already on the toBeMade list. */ - if (DEBUG(MAKE)) - fprintf(debug_file, "- not deferred\n"); + DEBUG0(MAKE, "- not deferred\n"); continue; } assert(pgn->order_pred != NULL); - if (Lst_ForEach(pgn->order_pred, MakeCheckOrder, 0)) { + if (Lst_ForEachUntil(pgn->order_pred, MakeCheckOrder, 0)) { /* A .ORDER rule stops us building this */ continue; } if (DEBUG(MAKE)) { - static int two = 2; - fprintf(debug_file, "- %s%s made, schedule %s%s (made %d)\n", - cgn->name, cgn->cohort_num, - pgn->name, pgn->cohort_num, pgn->made); - Targ_PrintNode(pgn, &two); + 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); } - Lst_Close(parents); - /* - * Set the .PREFIX and .IMPSRC variables for all the implied parents - * of this node. - */ - Lst_Open(cgn->implicitParents); - { - const char *cpref = Var_Value(PREFIX, cgn, &p1); - - while ((ln = Lst_Next(cgn->implicitParents)) != NULL) { - pgn = LstNode_Datum(ln); - if (pgn->flags & REMAKE) { - Var_Set(IMPSRC, cname, pgn); - if (cpref != NULL) - Var_Set(PREFIX, cpref, pgn); - } - } - bmake_free(p1); - Lst_Close(cgn->implicitParents); - } + UpdateImplicitParentsVars(cgn, cname); } -/*- - *----------------------------------------------------------------------- - * MakeAddAllSrc -- - * Add a child's name to the ALLSRC and OODATE variables of the given - * node. Called from Make_DoAllVar via Lst_ForEach. A child is added only - * if it has not been given the .EXEC, .USE or .INVISIBLE attributes. - * .EXEC and .USE children are very rarely going to be files, so... - * If the child is a .JOIN node, its ALLSRC is propagated to the parent. - * - * A child is added to the OODATE variable if its modification time is - * later than that of its parent, as defined by Make, except if the - * parent is a .JOIN node. In that case, it is only added to the OODATE - * variable if it was actually made (since .JOIN nodes don't have - * modification times, the comparison is rather unfair...).. - * - * Results: - * Always returns 0 - * - * Side Effects: - * The ALLSRC variable for the given node is extended. - *----------------------------------------------------------------------- - */ -static int -MakeUnmark(void *cgnp, void *pgnp MAKE_ATTR_UNUSED) +static void +UnmarkChildren(GNode *gn) { - GNode *cgn = (GNode *)cgnp; + GNodeListNode *ln; - cgn->type &= ~OP_MARK; - return 0; + 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: - * cgnp The child to add - * pgnp The parent to whose ALLSRC variable it should + * cgn The child to add + * pgn The parent to whose ALLSRC variable it should * be added - * */ -static int -MakeAddAllSrc(void *cgnp, void *pgnp) +static void +MakeAddAllSrc(GNode *cgn, GNode *pgn) { - GNode *cgn = (GNode *)cgnp; - GNode *pgn = (GNode *)pgnp; - if (cgn->type & OP_MARK) - return 0; + return; cgn->type |= OP_MARK; if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) { const char *child, *allsrc; - char *p1 = NULL, *p2 = NULL; if (cgn->type & OP_ARCHV) - child = Var_Value(MEMBER, cgn, &p1); + child = GNode_VarMember(cgn); else - child = cgn->path ? cgn->path : cgn->name; + child = GNode_Path(cgn); if (cgn->type & OP_JOIN) { - allsrc = Var_Value(ALLSRC, cgn, &p2); + allsrc = GNode_VarAllsrc(cgn); } else { allsrc = child; } if (allsrc != NULL) Var_Append(ALLSRC, allsrc, pgn); - bmake_free(p2); if (pgn->type & OP_JOIN) { if (cgn->made == MADE) { Var_Append(OODATE, child, pgn); @@ -944,74 +810,46 @@ MakeAddAllSrc(void *cgnp, void *pgnp) */ Var_Append(OODATE, child, pgn); } - bmake_free(p1); } - return 0; } -/*- - *----------------------------------------------------------------------- - * Make_DoAllVar -- - * Set up the ALLSRC and OODATE variables. Sad to say, it must be - * done separately, rather than while traversing the graph. This is - * because Make defined OODATE to contain all sources whose modification - * times were later than that of the target, *not* those sources that - * were out-of-date. Since in both compatibility and native modes, - * the modification time of the parent isn't found until the child - * has been dealt with, we have to wait until now to fill in the - * variable. As for ALLSRC, the ordering is important and not - * guaranteed when in native mode, so it must be set here, too. - * - * Results: - * None +/* 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. * - * Side Effects: - * The ALLSRC and OODATE variables of the given node is filled in. - * If the node is a .JOIN node, its TARGET variable will be set to - * match its ALLSRC variable. - *----------------------------------------------------------------------- + * If the node is a .JOIN node, its TARGET variable will be set to + * match its ALLSRC variable. */ void Make_DoAllVar(GNode *gn) { + GNodeListNode *ln; + if (gn->flags & DONE_ALLSRC) return; - Lst_ForEach(gn->children, MakeUnmark, gn); - Lst_ForEach(gn->children, MakeAddAllSrc, gn); + UnmarkChildren(gn); + for (ln = gn->children->first; ln != NULL; ln = ln->next) + MakeAddAllSrc(ln->datum, gn); - if (!Var_Exists (OODATE, gn)) { + if (!Var_Exists(OODATE, gn)) { Var_Set(OODATE, "", gn); } - if (!Var_Exists (ALLSRC, gn)) { + if (!Var_Exists(ALLSRC, gn)) { Var_Set(ALLSRC, "", gn); } - if (gn->type & OP_JOIN) { - char *p1; - Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn); - bmake_free(p1); - } + if (gn->type & OP_JOIN) + Var_Set(TARGET, GNode_VarAllsrc(gn), gn); gn->flags |= DONE_ALLSRC; } -/*- - *----------------------------------------------------------------------- - * MakeStartJobs -- - * Start as many jobs as possible. - * - * Results: - * If the query flag was given to pmake, no job will be started, - * but as soon as an out-of-date target is found, this function - * returns TRUE. At all other times, this function returns FALSE. - * - * Side Effects: - * Nodes are removed from the toBeMade queue and job table slots - * are filled. - * - *----------------------------------------------------------------------- - */ - static int MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED) { @@ -1019,9 +857,9 @@ MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED) if (bn->made >= MADE || !(bn->flags & REMAKE)) return 0; - if (DEBUG(MAKE)) - fprintf(debug_file, "MakeCheckOrder: Waiting for .ORDER node %s%s\n", - bn->name, bn->cohort_num); + + DEBUG2(MAKE, "MakeCheckOrder: Waiting for .ORDER node %s%s\n", + bn->name, bn->cohort_num); return 1; } @@ -1030,23 +868,20 @@ MakeBuildChild(void *v_cn, void *toBeMade_next) { GNode *cn = v_cn; - if (DEBUG(MAKE)) - fprintf(debug_file, "MakeBuildChild: inspect %s%s, made %d, type %x\n", - cn->name, cn->cohort_num, cn->made, cn->type); + DEBUG4(MAKE, "MakeBuildChild: inspect %s%s, made %d, type %x\n", + cn->name, cn->cohort_num, cn->made, cn->type); if (cn->made > DEFERRED) return 0; /* If this node is on the RHS of a .ORDER, check LHSs. */ assert(cn->order_pred); - if (Lst_ForEach(cn->order_pred, MakeCheckOrder, 0)) { + if (Lst_ForEachUntil(cn->order_pred, MakeCheckOrder, 0)) { /* Can't build this (or anything else in this child list) yet */ cn->made = DEFERRED; return 0; /* but keep looking */ } - if (DEBUG(MAKE)) - fprintf(debug_file, "MakeBuildChild: schedule %s%s\n", - cn->name, cn->cohort_num); + DEBUG2(MAKE, "MakeBuildChild: schedule %s%s\n", cn->name, cn->cohort_num); cn->made = REQUESTED; if (toBeMade_next == NULL) @@ -1055,10 +890,10 @@ MakeBuildChild(void *v_cn, void *toBeMade_next) Lst_InsertBefore(toBeMade, toBeMade_next, cn); if (cn->unmade_cohorts != 0) - Lst_ForEach(cn->cohorts, MakeBuildChild, toBeMade_next); + Lst_ForEachUntil(cn->cohorts, MakeBuildChild, toBeMade_next); /* - * If this node is a .WAIT node with unmade chlidren + * 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; @@ -1081,6 +916,12 @@ MakeBuildParent(void *v_pn, void *toBeMade_next) return 0; } +/* Start as many jobs as possible, taking them from the toBeMade queue. + * + * If the query flag was given to pmake, no job will be started, + * but as soon as an out-of-date target is found, this function + * returns TRUE. At all other times, this function returns FALSE. + */ static Boolean MakeStartJobs(void) { @@ -1094,26 +935,21 @@ MakeStartJobs(void) have_token = 1; gn = Lst_Dequeue(toBeMade); - if (DEBUG(MAKE)) - fprintf(debug_file, "Examining %s%s...\n", - gn->name, gn->cohort_num); + DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num); if (gn->made != REQUESTED) { - if (DEBUG(MAKE)) - fprintf(debug_file, "state %d\n", gn->made); + DEBUG1(MAKE, "state %d\n", gn->made); make_abort(gn, __LINE__); } - if (gn->checked == checked) { + if (gn->checked_seqno == checked) { /* We've already looked at this node since a job finished... */ - if (DEBUG(MAKE)) - fprintf(debug_file, "already checked %s%s\n", - gn->name, gn->cohort_num); + DEBUG2(MAKE, "already checked %s%s\n", gn->name, gn->cohort_num); gn->made = DEFERRED; continue; } - gn->checked = checked; + gn->checked_seqno = checked; if (gn->unmade != 0) { /* @@ -1121,28 +957,23 @@ MakeStartJobs(void) * just before the current first element. */ gn->made = DEFERRED; - Lst_ForEach(gn->children, MakeBuildChild, Lst_First(toBeMade)); + Lst_ForEachUntil(gn->children, MakeBuildChild, toBeMade->first); /* and drop this node on the floor */ - if (DEBUG(MAKE)) - fprintf(debug_file, "dropped %s%s\n", gn->name, gn->cohort_num); + DEBUG2(MAKE, "dropped %s%s\n", gn->name, gn->cohort_num); continue; } gn->made = BEINGMADE; if (Make_OODate(gn)) { - if (DEBUG(MAKE)) { - fprintf(debug_file, "out-of-date\n"); - } - if (queryFlag) { + DEBUG0(MAKE, "out-of-date\n"); + if (opts.queryFlag) { return TRUE; } Make_DoAllVar(gn); Job_Make(gn); have_token = 0; } else { - if (DEBUG(MAKE)) { - fprintf(debug_file, "up-to-date\n"); - } + DEBUG0(MAKE, "up-to-date\n"); gn->made = UPTODATE; if (gn->type & OP_JOIN) { /* @@ -1163,58 +994,44 @@ MakeStartJobs(void) return FALSE; } -/*- - *----------------------------------------------------------------------- - * MakePrintStatus -- - * Print the status of a top-level node, viz. it being up-to-date - * already or not created due to an error in a lower level. - * Callback function for Make_Run via Lst_ForEach. - * - * Input: - * gnp Node to examine - * cyclep True if gn->unmade being non-zero implies a - * cycle in the graph, not an error in an - * inferior. - * - * Results: - * Always returns 0. - * - * Side Effects: - * A message may be printed. - * - *----------------------------------------------------------------------- - */ -static int -MakePrintStatusOrder(void *ognp, void *gnp) +static void +MakePrintStatusOrderNode(GNode *ogn, GNode *gn) { - GNode *ogn = ognp; - GNode *gn = gnp; - if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED) /* not waiting for this one */ - return 0; + return; printf(" `%s%s' has .ORDER dependency against %s%s ", - gn->name, gn->cohort_num, ogn->name, ogn->cohort_num); + gn->name, gn->cohort_num, ogn->name, ogn->cohort_num); GNode_FprintDetails(stdout, "(", ogn, ")\n"); - if (DEBUG(MAKE) && debug_file != stdout) { - fprintf(debug_file, " `%s%s' has .ORDER dependency against %s%s ", - gn->name, gn->cohort_num, ogn->name, ogn->cohort_num); - GNode_FprintDetails(debug_file, "(", 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"); } - return 0; } -static int -MakePrintStatus(void *gnp, void *v_errors) +static void +MakePrintStatusOrder(GNode *gn) { - GNode *gn = (GNode *)gnp; - int *errors = v_errors; + GNodeListNode *ln; + for (ln = gn->order_pred->first; ln != NULL; ln = ln->next) + MakePrintStatusOrderNode(ln->datum, gn); +} + +static void MakePrintStatusList(GNodeList *, int *); +/* Print the status of a top-level node, viz. it being up-to-date already + * or not created due to an error in a lower level. + * Callback function for Make_Run via Lst_ForEachUntil. + */ +static Boolean +MakePrintStatus(GNode *gn, int *errors) +{ if (gn->flags & DONECYCLE) /* We've completely processed this node before, don't do it again. */ - return 0; + return FALSE; if (gn->unmade == 0) { gn->flags |= DONECYCLE; @@ -1231,29 +1048,27 @@ MakePrintStatus(void *gnp, void *v_errors) (*errors)++; printf("`%s%s' was not built", gn->name, gn->cohort_num); GNode_FprintDetails(stdout, " (", gn, ")!\n"); - if (DEBUG(MAKE) && debug_file != stdout) { - fprintf(debug_file, "`%s%s' was not built", - gn->name, gn->cohort_num); - GNode_FprintDetails(debug_file, " (", 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 */ - Lst_ForEach(gn->order_pred, MakePrintStatusOrder, gn); + MakePrintStatusOrder(gn); break; default: /* Errors - already counted */ printf("`%s%s' not remade because of errors.\n", gn->name, gn->cohort_num); - if (DEBUG(MAKE) && debug_file != stdout) - fprintf(debug_file, "`%s%s' not remade because of errors.\n", - gn->name, gn->cohort_num); + if (DEBUG(MAKE) && opts.debug_file != stdout) + debug_printf("`%s%s' not remade because of errors.\n", + gn->name, gn->cohort_num); break; } - return 0; + return FALSE; } - if (DEBUG(MAKE)) - fprintf(debug_file, "MakePrintStatus: %s%s has %d unmade children\n", - gn->name, gn->cohort_num, gn->unmade); + 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. @@ -1261,10 +1076,10 @@ MakePrintStatus(void *gnp, void *v_errors) if (!(gn->flags & CYCLE)) { /* Fist time we've seen this node, check all children */ gn->flags |= CYCLE; - Lst_ForEach(gn->children, MakePrintStatus, errors); + MakePrintStatusList(gn->children, errors); /* Mark that this node needn't be processed again */ gn->flags |= DONECYCLE; - return 0; + return FALSE; } /* Only output the error once per node */ @@ -1272,32 +1087,41 @@ MakePrintStatus(void *gnp, void *v_errors) Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num); if ((*errors)++ > 100) /* Abandon the whole error report */ - return 1; + return TRUE; /* Reporting for our children will give the rest of the loop */ - Lst_ForEach(gn->children, MakePrintStatus, errors); - return 0; + MakePrintStatusList(gn->children, errors); + return FALSE; } +static void +MakePrintStatusList(GNodeList *gnodes, int *errors) +{ + GNodeListNode *ln; + for (ln = gnodes->first; ln != NULL; ln = ln->next) + if (MakePrintStatus(ln->datum, errors)) + break; +} -/*- - *----------------------------------------------------------------------- - * Make_ExpandUse -- - * Expand .USE nodes and create a new targets list +/* Expand .USE nodes and create a new targets list. * * Input: * targs the initial list of targets - * - * Side Effects: - *----------------------------------------------------------------------- */ void -Make_ExpandUse(Lst targs) +Make_ExpandUse(GNodeList *targs) { - GNode *gn; /* a temporary pointer */ - Lst examine; /* List of targets to examine */ + GNodeList *examine; /* List of targets to examine */ - examine = Lst_Copy(targs, NULL); + { + /* XXX: Why is it necessary to copy the list? There shouldn't be + * any modifications to the list, at least the function name + * ExpandUse doesn't suggest that. */ + GNodeListNode *ln; + examine = Lst_New(); + for (ln = targs->first; ln != NULL; ln = ln->next) + Lst_Append(examine, ln->datum); + } /* * Make an initial downward pass over the graph, marking nodes to be made @@ -1308,15 +1132,14 @@ Make_ExpandUse(Lst targs) * and go on about our business. */ while (!Lst_IsEmpty(examine)) { - gn = Lst_Dequeue(examine); + GNode *gn = Lst_Dequeue(examine); if (gn->flags & REMAKE) /* We've looked at this one already */ continue; gn->flags |= REMAKE; - if (DEBUG(MAKE)) - fprintf(debug_file, "Make_ExpandUse: examine %s%s\n", - gn->name, gn->cohort_num); + DEBUG2(MAKE, "Make_ExpandUse: examine %s%s\n", + gn->name, gn->cohort_num); if (gn->type & OP_DOUBLEDEP) Lst_PrependAll(examine, gn->cohorts); @@ -1342,81 +1165,52 @@ Make_ExpandUse(Lst targs) } (void)Dir_MTime(gn, 0); - Var_Set(TARGET, gn->path ? gn->path : gn->name, gn); - Lst_ForEach(gn->children, MakeUnmark, gn); - Lst_ForEach(gn->children, MakeHandleUse, gn); + Var_Set(TARGET, GNode_Path(gn), gn); + UnmarkChildren(gn); + HandleUseNodes(gn); if ((gn->type & OP_MADE) == 0) Suff_FindDeps(gn); else { /* Pretend we made all this node's children */ - Lst_ForEach(gn->children, MakeFindChild, gn); + Lst_ForEachUntil(gn->children, MakeFindChild, gn); if (gn->unmade != 0) printf("Warning: %s%s still has %d unmade children\n", gn->name, gn->cohort_num, gn->unmade); } if (gn->unmade != 0) - Lst_ForEach(gn->children, MakeAddChild, examine); + Lst_ForEachUntil(gn->children, MakeAddChild, examine); } Lst_Free(examine); } -/*- - *----------------------------------------------------------------------- - * Make_ProcessWait -- - * Convert .WAIT nodes into dependencies - * - * Input: - * targs the initial list of targets - * - *----------------------------------------------------------------------- - */ - -static int -link_parent(void *cnp, void *pnp) -{ - GNode *cn = cnp; - GNode *pn = pnp; - - Lst_Append(pn->children, cn); - Lst_Append(cn->parents, pn); - pn->unmade++; - return 0; -} - -static int -add_wait_dep(void *v_cn, void *v_wn) +/* Make the .WAIT node depend on the previous children */ +static void +add_wait_dependency(GNodeListNode *owln, GNode *wn) { - GNode *cn = v_cn; - GNode *wn = v_wn; + GNodeListNode *cln; + GNode *cn; - if (cn == wn) - return 1; + 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); - if (cn == NULL || wn == NULL) { - printf("bad wait dep %p %p\n", cn, wn); - exit(4); + /* XXX: This pattern should be factored out, it repeats often */ + Lst_Append(wn->children, cn); + wn->unmade++; + Lst_Append(cn->parents, wn); } - if (DEBUG(MAKE)) - fprintf(debug_file, ".WAIT: add dependency %s%s -> %s\n", - cn->name, cn->cohort_num, wn->name); - - Lst_Append(wn->children, cn); - wn->unmade++; - Lst_Append(cn->parents, wn); - return 0; } +/* Convert .WAIT nodes into dependencies. */ static void -Make_ProcessWait(Lst targs) +Make_ProcessWait(GNodeList *targs) { - GNode *pgn; /* 'parent' node we are examining */ - GNode *cgn; /* Each child in turn */ - LstNode owln; /* Previous .WAIT node */ - Lst examine; /* List of targets to examine */ - LstNode ln; + 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 @@ -1430,40 +1224,47 @@ Make_ProcessWait(Lst targs) /* Get it displayed in the diag dumps */ Lst_Prepend(Targ_List(), pgn); - Lst_ForEach(targs, link_parent, pgn); + { + GNodeListNode *ln; + for (ln = targs->first; ln != NULL; ln = ln->next) { + GNode *cgn = ln->datum; + + Lst_Append(pgn->children, cgn); + Lst_Append(cgn->parents, pgn); + pgn->unmade++; + } + } /* Start building with the 'dummy' .MAIN' node */ MakeBuildChild(pgn, NULL); - examine = Lst_Init(); + examine = Lst_New(); Lst_Append(examine, pgn); while (!Lst_IsEmpty(examine)) { + GNodeListNode *ln; + pgn = Lst_Dequeue(examine); /* We only want to process each child-list once */ if (pgn->flags & DONE_WAIT) continue; pgn->flags |= DONE_WAIT; - if (DEBUG(MAKE)) - fprintf(debug_file, "Make_ProcessWait: examine %s\n", pgn->name); + DEBUG1(MAKE, "Make_ProcessWait: examine %s\n", pgn->name); if (pgn->type & OP_DOUBLEDEP) Lst_PrependAll(examine, pgn->cohorts); - owln = Lst_First(pgn->children); - Lst_Open(pgn->children); - for (; (ln = Lst_Next(pgn->children)) != NULL; ) { - cgn = LstNode_Datum(ln); + owln = pgn->children->first; + for (ln = pgn->children->first; ln != NULL; ln = ln->next) { + GNode *cgn = ln->datum; if (cgn->type & OP_WAIT) { - /* Make the .WAIT node depend on the previous children */ - Lst_ForEachFrom(pgn->children, owln, add_wait_dep, cgn); + add_wait_dependency(owln, cgn); owln = ln; } else { Lst_Append(examine, cgn); } } - Lst_Close(pgn->children); } Lst_Free(examine); @@ -1494,22 +1295,22 @@ Make_ProcessWait(Lst targs) *----------------------------------------------------------------------- */ Boolean -Make_Run(Lst targs) +Make_Run(GNodeList *targs) { - int errors; /* Number of errors the Job module reports */ + int errors; /* Number of errors the Job module reports */ /* Start trying to make the current targets... */ - toBeMade = Lst_Init(); + toBeMade = Lst_New(); Make_ExpandUse(targs); Make_ProcessWait(targs); if (DEBUG(MAKE)) { - fprintf(debug_file, "#***# full graph\n"); + debug_printf("#***# full graph\n"); Targ_PrintGraph(1); } - if (queryFlag) { + 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 @@ -1547,12 +1348,11 @@ Make_Run(Lst targs) * Print the final status of each target. E.g. if it wasn't made * because some inferior reported an error. */ - if (DEBUG(MAKE)) - fprintf(debug_file, "done: errors %d\n", errors); + DEBUG1(MAKE, "done: errors %d\n", errors); if (errors == 0) { - Lst_ForEach(targs, MakePrintStatus, &errors); + MakePrintStatusList(targs, &errors); if (DEBUG(MAKE)) { - fprintf(debug_file, "done: errors %d\n", errors); + debug_printf("done: errors %d\n", errors); if (errors) Targ_PrintGraph(4); } @@ -1,4 +1,4 @@ -/* $NetBSD: make.h,v 1.137 2020/09/02 23:42:58 rillig Exp $ */ +/* $NetBSD: make.h,v 1.179 2020/11/01 17:47:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -91,6 +91,7 @@ #include <assert.h> #include <ctype.h> #include <fcntl.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #ifdef HAVE_STRING_H @@ -142,23 +143,31 @@ #ifdef USE_DOUBLE_BOOLEAN /* During development, to find type mismatches in function declarations. */ typedef double Boolean; +#define TRUE 1.0 +#define FALSE 0.0 #elif defined(USE_UCHAR_BOOLEAN) /* During development, to find code that depends on the exact value of TRUE or * that stores other values in Boolean variables. */ typedef unsigned char Boolean; #define TRUE ((unsigned char)0xFF) #define FALSE ((unsigned char)0x00) +#elif defined(USE_CHAR_BOOLEAN) +/* During development, to find code that uses a boolean as array index, via + * -Wchar-subscripts. */ +typedef char Boolean; +#define TRUE ((char)-1) +#define FALSE ((char)0x00) #elif defined(USE_ENUM_BOOLEAN) -typedef enum { FALSE, TRUE} Boolean; +typedef enum Boolean { FALSE, TRUE } Boolean; #else typedef int Boolean; -#endif #ifndef TRUE #define TRUE 1 -#endif /* TRUE */ +#endif #ifndef FALSE #define FALSE 0 -#endif /* FALSE */ +#endif +#endif #include "lst.h" #include "enum.h" @@ -196,18 +205,20 @@ typedef enum { * communicating to other parts of the program the way in which a target * should be made. * - * These constants are bitwise-OR'ed together and placed in the 'type' field - * of each node. Any node that has a 'type' field which satisfies the OP_NOP - * function was never never on the left-hand side of an operator, though it - * may have been on the right-hand side... */ -typedef enum { - /* Execution of commands depends on children (:) */ + * Some of the OP_ constants can be combined, others cannot. */ +typedef enum GNodeType { + /* 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, - /* Always execute commands (!) */ + /* The dependency operator '!' always executes its commands, even if + * its children are up-to-date. */ OP_FORCE = 1 << 1, - /* Execution of commands depends on children per line (::) */ + /* 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. */ 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 */ @@ -216,8 +227,8 @@ typedef enum { 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 errors when creating the node */ + 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, @@ -259,23 +270,32 @@ typedef enum { /* The node is a transformation rule */ OP_TRANSFORM = 1 << 31, /* Target is a member of an archive */ + /* XXX: How does this differ from OP_ARCHV? */ OP_MEMBER = 1 << 30, - /* Target is a library */ + /* The node is a library, + * its name has the form "-l<libname>" */ OP_LIB = 1 << 29, - /* Target is an archive construct */ + /* The node is an archive member, + * its name has the form "archive(member)" */ + /* XXX: How does this differ from OP_MEMBER? */ OP_ARCHV = 1 << 28, /* Target has all the commands it should. Used when parsing to catch - * multiple commands for a target. */ + * multiple command groups for a target. Only applies to the dependency + * operators ':' and '!', but not to '::'. */ OP_HAS_COMMANDS = 1 << 27, - /* Saving commands on .END (Compat) */ + /* The special command "..." has been seen. All further commands from + * this node will be saved on the .END node instead, to be executed at + * the very end. */ OP_SAVE_CMDS = 1 << 26, /* Already processed by Suff_FindDeps */ OP_DEPS_FOUND = 1 << 25, /* Node found while expanding .ALLSRC */ - OP_MARK = 1 << 24 + OP_MARK = 1 << 24, + + OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM } GNodeType; -typedef enum { +typedef enum GNodeFlags { REMAKE = 0x0001, /* this target needs to be (re)made */ CHILDMADE = 0x0002, /* children of this target were made */ FORCE = 0x0004, /* children don't exist, and we pretend made */ @@ -288,6 +308,14 @@ typedef enum { INTERNAL = 0x4000 /* Internal use only */ } GNodeFlags; +typedef struct List StringList; +typedef struct ListNode StringListNode; + +typedef struct List GNodeList; +typedef struct ListNode GNodeListNode; + +typedef struct List /* of CachedDir */ SearchPath; + /* A graph node represents a target that can possibly be made, including its * relation to other targets and a lot of other details. */ typedef struct GNode { @@ -302,40 +330,41 @@ typedef struct GNode { /* 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; - /* whether it is involved in this invocation of make */ GNodeFlags flags; /* The state of processing on this node */ GNodeMade made; int unmade; /* The number of unmade children */ - time_t mtime; /* Its modification time */ - struct GNode *cmgn; /* The youngest child */ + /* The modification time; 0 means the node does not have a corresponding + * file; see Make_OODate. */ + 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. */ - Lst implicitParents; + GNodeList *implicitParents; - /* Other nodes of the same name for the :: operator. */ - Lst cohorts; + /* Other nodes of the same name, for the '::' operator. */ + GNodeList *cohorts; /* The nodes that depend on this one, or in other words, the nodes for * which this is a source. */ - Lst parents; + GNodeList *parents; /* The nodes on which this one depends. */ - Lst children; + 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. */ - Lst order_pred; + 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. */ - Lst order_succ; + GNodeList *order_succ; - /* #n for this cohort */ + /* 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; @@ -344,14 +373,17 @@ typedef struct GNode { struct GNode *centurion; /* Last time (sequence number) we tried to make this node */ - unsigned int checked; + unsigned int checked_seqno; /* The "local" variables that are specific to this target and this target - * only, such as $@, $<, $?. */ - Hash_Table context; + * only, such as $@, $<, $?. + * + * Also used for the global variable scopes VAR_GLOBAL, VAR_CMDLINE, + * VAR_INTERNAL, which contain variables with arbitrary names. */ + HashTable /* of Var pointer */ context; /* The commands to be given to a shell to create this target. */ - Lst commands; + StringList *commands; /* Suffix for the node (determined by Suff_FindDeps and opaque to everyone * but the Suff module) */ @@ -363,40 +395,21 @@ typedef struct GNode { int lineno; } GNode; -#define NoExecute(gn) ((gn->type & OP_MAKE) ? noRecursiveExecute : noExecute) -/* - * OP_NOP will return TRUE if the node with the given type was not the - * object of a dependency operator - */ -#define OP_NOP(t) (((t) & OP_OPMASK) == 0x00000000) - -#define OP_NOTARGET (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM) - -/* - * The TARG_ constants are used when calling the Targ_FindNode and - * Targ_FindList functions in targ.c. They simply tell the functions what to - * do if the desired node(s) is (are) not found. If the TARG_CREATE constant - * is given, a new, empty node will be created for the target, placed in the - * table of all targets and its address returned. If TARG_NOCREATE is given, - * a NULL pointer will be returned. - */ -#define TARG_NOCREATE 0x00 /* don't create it */ -#define TARG_CREATE 0x01 /* create node if not found */ -#define TARG_NOHASH 0x02 /* don't look in/add to hash table */ - /* * Error levels for parsing. PARSE_FATAL means the process cannot continue - * once the makefile has been parsed. PARSE_WARNING means it can. Passed - * as the first argument to Parse_Error. + * once the top-level makefile has been parsed. PARSE_WARNING and PARSE_INFO + * mean it can. */ -#define PARSE_INFO 3 -#define PARSE_WARNING 2 -#define PARSE_FATAL 1 +typedef enum ParseErrorLevel { + PARSE_FATAL = 1, + PARSE_WARNING, + PARSE_INFO +} ParseErrorLevel; /* - * Values returned by Cond_Eval. + * Values returned by Cond_EvalLine and Cond_EvalCondition. */ -typedef enum { +typedef enum CondEvalResult { COND_PARSE, /* Parse the next lines */ COND_SKIP, /* Skip the next lines */ COND_INVALID /* Not a conditional statement */ @@ -405,77 +418,52 @@ typedef enum { /* * Definitions for the "local" variables. Used only for clarity. */ -#define TARGET "@" /* Target of dependency */ -#define OODATE "?" /* All out-of-date sources */ -#define ALLSRC ">" /* All sources */ -#define IMPSRC "<" /* Source implied by transformation */ -#define PREFIX "*" /* Common prefix */ -#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ -#define MEMBER "%" /* Member in "archive(member)" syntax */ - -#define FTARGET "@F" /* file part of TARGET */ -#define DTARGET "@D" /* directory part of TARGET */ -#define FIMPSRC "<F" /* file part of IMPSRC */ -#define DIMPSRC "<D" /* directory part of IMPSRC */ -#define FPREFIX "*F" /* file part of PREFIX */ -#define DPREFIX "*D" /* directory part of PREFIX */ +#define TARGET "@" /* Target of dependency */ +#define OODATE "?" /* All out-of-date sources */ +#define ALLSRC ">" /* All sources */ +#define IMPSRC "<" /* Source implied by transformation */ +#define PREFIX "*" /* Common prefix */ +#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ +#define MEMBER "%" /* Member in "archive(member)" syntax */ + +#define FTARGET "@F" /* file part of TARGET */ +#define DTARGET "@D" /* directory part of TARGET */ +#define FIMPSRC "<F" /* file part of IMPSRC */ +#define DIMPSRC "<D" /* directory part of IMPSRC */ +#define FPREFIX "*F" /* file part of PREFIX */ +#define DPREFIX "*D" /* directory part of PREFIX */ /* * Global Variables */ -extern Lst create; /* The list of target names specified on the - * command line. used to resolve #if - * make(...) statements */ -extern Lst dirSearchPath; /* The list of directories to search when +extern SearchPath *dirSearchPath; + /* The list of directories to search when * looking for targets */ - -extern Boolean compatMake; /* True if we are make compatible */ -extern Boolean ignoreErrors; /* True if should ignore all errors */ -extern Boolean beSilent; /* True if should print no commands */ -extern Boolean noExecute; /* True if should execute nothing */ -extern Boolean noRecursiveExecute; /* True if should execute nothing */ -extern Boolean allPrecious; /* True if every target is precious */ +extern Boolean allPrecious; /* True if every target is precious */ extern Boolean deleteOnError; /* True if failed targets should be deleted */ -extern Boolean keepgoing; /* True if should continue on unaffected - * portions of the graph when have an error - * in one portion */ -extern Boolean touchFlag; /* TRUE if targets should just be 'touched' - * if out of date. Set by the -t flag */ -extern Boolean queryFlag; /* TRUE if we aren't supposed to really make - * anything, just see if the targets are out- - * of-date */ extern Boolean doing_depend; /* TRUE if processing .depend */ -extern Boolean checkEnvFirst; /* TRUE if environment should be searched for - * variables before the global context */ - -extern Boolean parseWarnFatal; /* TRUE if makefile parsing warnings are - * treated as errors */ - -extern Boolean varNoExportEnv; /* TRUE if we should not export variables - * set on the command line to the env. */ - -extern GNode *DEFAULT; /* .DEFAULT rule */ +extern GNode *DEFAULT; /* .DEFAULT rule */ extern GNode *VAR_INTERNAL; /* Variables defined internally by make * which should not override those set by * makefiles. */ -extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g +extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g * in the Makefile itself */ -extern GNode *VAR_CMD; /* Variables defined on the command line */ -extern char var_Error[]; /* Value returned by Var_Parse when an error +extern GNode *VAR_CMDLINE; /* Variables defined on the command line */ +extern char var_Error[]; /* Value returned by Var_Parse when an error * is encountered. It actually points to * an empty string, so naive callers needn't * worry about it. */ -extern time_t now; /* The time at the start of this whole +extern time_t now; /* The time at the start of this whole * process */ -extern Boolean oldVars; /* Do old-style variable substitution */ +extern Boolean oldVars; /* Do old-style variable substitution */ -extern Lst sysIncPath; /* The system include path. */ -extern Lst defIncPath; /* The default include path. */ +extern SearchPath *sysIncPath; /* The system include path. */ +extern SearchPath *defSysIncPath; /* The default system include path. */ extern char curdir[]; /* Startup directory */ extern char *progname; /* The program name */ @@ -495,7 +483,7 @@ extern pid_t myPid; #define MAKEOVERRIDES ".MAKEOVERRIDES" #define MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX" /* prefix for job target output */ #define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */ -#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all the makefiles we read */ +#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all makefiles already loaded */ #define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */ #define MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE" #define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */ @@ -504,61 +492,191 @@ extern pid_t myPid; # define MAKE_LEVEL_ENV "MAKELEVEL" #endif -/* - * debug control: - * There is one bit per module. It is up to the module what debug - * information to print. - */ -extern FILE *debug_file; /* Output is written here - default stderr */ -extern int debug; -#define DEBUG_ARCH 0x00001 -#define DEBUG_COND 0x00002 -#define DEBUG_DIR 0x00004 -#define DEBUG_GRAPH1 0x00008 -#define DEBUG_GRAPH2 0x00010 -#define DEBUG_JOB 0x00020 -#define DEBUG_MAKE 0x00040 -#define DEBUG_SUFF 0x00080 -#define DEBUG_TARG 0x00100 -#define DEBUG_VAR 0x00200 -#define DEBUG_FOR 0x00400 -#define DEBUG_SHELL 0x00800 -#define DEBUG_ERROR 0x01000 -#define DEBUG_LOUD 0x02000 -#define DEBUG_META 0x04000 -#define DEBUG_HASH 0x08000 - -#define DEBUG_GRAPH3 0x10000 -#define DEBUG_SCRIPT 0x20000 -#define DEBUG_PARSE 0x40000 -#define DEBUG_CWD 0x80000 - -#define DEBUG_LINT 0x100000 +typedef enum DebugFlags { + DEBUG_ARCH = 1 << 0, + DEBUG_COND = 1 << 1, + DEBUG_DIR = 1 << 2, + DEBUG_GRAPH1 = 1 << 3, + DEBUG_GRAPH2 = 1 << 4, + DEBUG_JOB = 1 << 5, + DEBUG_MAKE = 1 << 6, + DEBUG_SUFF = 1 << 7, + DEBUG_TARG = 1 << 8, + DEBUG_VAR = 1 << 9, + DEBUG_FOR = 1 << 10, + DEBUG_SHELL = 1 << 11, + DEBUG_ERROR = 1 << 12, + DEBUG_LOUD = 1 << 13, + DEBUG_META = 1 << 14, + DEBUG_HASH = 1 << 15, + + DEBUG_GRAPH3 = 1 << 16, + DEBUG_SCRIPT = 1 << 17, + DEBUG_PARSE = 1 << 18, + DEBUG_CWD = 1 << 19, + + DEBUG_LINT = 1 << 20 +} DebugFlags; #define CONCAT(a,b) a##b -#define DEBUG(module) (debug & CONCAT(DEBUG_,module)) +#define DEBUG(module) (opts.debug & CONCAT(DEBUG_,module)) + +void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); + +#define DEBUG0(module, text) \ + if (!DEBUG(module)) (void)0; \ + else debug_printf("%s", text) + +#define DEBUG1(module, fmt, arg1) \ + if (!DEBUG(module)) (void)0; \ + else debug_printf(fmt, arg1) + +#define DEBUG2(module, fmt, arg1, arg2) \ + if (!DEBUG(module)) (void)0; \ + else debug_printf(fmt, arg1, arg2) + +#define DEBUG3(module, fmt, arg1, arg2, arg3) \ + if (!DEBUG(module)) (void)0; \ + else debug_printf(fmt, arg1, arg2, arg3) + +#define DEBUG4(module, fmt, arg1, arg2, arg3, arg4) \ + if (!DEBUG(module)) (void)0; \ + else debug_printf(fmt, arg1, arg2, arg3, arg4) + +#define DEBUG5(module, fmt, arg1, arg2, arg3, arg4, arg5) \ + if (!DEBUG(module)) (void)0; \ + else debug_printf(fmt, arg1, arg2, arg3, arg4, arg5) + +typedef enum PrintVarsMode { + COMPAT_VARS = 1, + EXPAND_VARS +} PrintVarsMode; + +/* Command line options */ +typedef struct CmdOpts { + /* -B: whether we are make compatible */ + Boolean 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; + + /* -dV: for the -V option, print unexpanded variable values */ + Boolean debugVflag; + + /* -e: check environment variables before global variables */ + Boolean checkEnvFirst; + + /* -f: the makefiles to read */ + StringList *makefiles; + + /* -i: if true, ignore all errors from shell commands */ + Boolean ignoreErrors; + + /* -j: the maximum number of jobs that can run in parallel; + * this is coordinated with the submakes */ + int maxJobs; + + /* -k: if true, continue on unaffected portions of the graph when an + * error occurs in one portion */ + Boolean keepgoing; + + /* -N: execute no commands from the targets */ + Boolean noRecursiveExecute; + + /* -n: execute almost no commands from the targets */ + Boolean noExecute; + + /* -q: if true, we aren't supposed to really make anything, just see if + * the targets are out-of-date */ + Boolean queryFlag; + + /* -r: raw mode, without loading the builtin rules. */ + Boolean noBuiltins; + + /* -s: don't echo the shell commands before executing them */ + Boolean beSilent; + + /* -t: touch the targets if they are out-of-date, but don't actually + * make them */ + Boolean 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; + + /* -w: print Entering and Leaving for submakes */ + Boolean enterFlag; + + /* -X: if true, do not export variables set on the command line to the + * environment. */ + Boolean varNoExportEnv; + + /* The target names specified on the command line. + * Used to resolve .if make(...) statements. */ + StringList *create; + +} CmdOpts; + +extern CmdOpts opts; #include "nonints.h" -int Make_TimeStamp(GNode *, GNode *); +void Make_TimeStamp(GNode *, GNode *); Boolean Make_OODate(GNode *); -void Make_ExpandUse(Lst); +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(Lst); +Boolean Make_Run(GNodeList *); int dieQuietly(GNode *, int); void PrintOnError(GNode *, const char *); void Main_ExportMAKEFLAGS(Boolean); Boolean Main_SetObjdir(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); int mkTempFile(const char *, char **); -int str2Lst_Append(Lst, char *, const char *); +int str2Lst_Append(StringList *, char *, const char *); void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); +Boolean GNode_ShouldExecute(GNode *gn); + +/* See if the node was seen on the left-hand side of a dependency operator. */ +static MAKE_ATTR_UNUSED Boolean +GNode_IsTarget(const GNode *gn) +{ + return (gn->type & OP_OPMASK) != 0; +} + +static MAKE_ATTR_UNUSED const char * +GNode_Path(const GNode *gn) +{ + return gn->path != NULL ? gn->path : gn->name; +} + +static MAKE_ATTR_UNUSED const char * +GNode_VarTarget(GNode *gn) { return Var_ValueDirect(TARGET, gn); } +static MAKE_ATTR_UNUSED const char * +GNode_VarOodate(GNode *gn) { return Var_ValueDirect(OODATE, gn); } +static MAKE_ATTR_UNUSED const char * +GNode_VarAllsrc(GNode *gn) { return Var_ValueDirect(ALLSRC, gn); } +static MAKE_ATTR_UNUSED const char * +GNode_VarImpsrc(GNode *gn) { return Var_ValueDirect(IMPSRC, gn); } +static MAKE_ATTR_UNUSED const char * +GNode_VarPrefix(GNode *gn) { return Var_ValueDirect(PREFIX, gn); } +static MAKE_ATTR_UNUSED const char * +GNode_VarArchive(GNode *gn) { return Var_ValueDirect(ARCHIVE, gn); } +static MAKE_ATTR_UNUSED const char * +GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); } #ifdef __GNUC__ -#define UNCONST(ptr) ({ \ +#define UNCONST(ptr) ({ \ union __unconst { \ const void *__cp; \ void *__p; \ @@ -568,13 +686,6 @@ void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); #define UNCONST(ptr) (void *)(ptr) #endif -#ifndef MIN -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#endif -#ifndef MAX -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) -#endif - /* At least GNU/Hurd systems lack hardcoded MAXPATHLEN/PATH_MAX */ #ifdef HAVE_LIMITS_H #include <limits.h> @@ -592,4 +703,42 @@ void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); #define KILLPG(pid, sig) killpg((pid), (sig)) #endif +static inline MAKE_ATTR_UNUSED Boolean ch_isalnum(char ch) +{ return isalnum((unsigned char)ch) != 0; } +static inline MAKE_ATTR_UNUSED Boolean ch_isalpha(char ch) +{ return isalpha((unsigned char)ch) != 0; } +static inline MAKE_ATTR_UNUSED Boolean ch_isdigit(char ch) +{ return isdigit((unsigned char)ch) != 0; } +static inline MAKE_ATTR_UNUSED Boolean ch_isspace(char ch) +{ return isspace((unsigned char)ch) != 0; } +static inline MAKE_ATTR_UNUSED Boolean ch_isupper(char ch) +{ return isupper((unsigned char)ch) != 0; } +static inline MAKE_ATTR_UNUSED char ch_tolower(char ch) +{ return (char)tolower((unsigned char)ch); } +static inline MAKE_ATTR_UNUSED char ch_toupper(char ch) +{ return (char)toupper((unsigned char)ch); } + +static inline MAKE_ATTR_UNUSED void +cpp_skip_whitespace(const char **pp) +{ + while (ch_isspace(**pp)) + (*pp)++; +} + +static inline MAKE_ATTR_UNUSED void +pp_skip_whitespace(char **pp) +{ + while (ch_isspace(**pp)) + (*pp)++; +} + +#ifdef MAKE_NATIVE +# include <sys/cdefs.h> +# ifndef lint +# define MAKE_RCSID(id) __RCSID(id) +# endif +#else +# define MAKE_RCSID(id) static volatile char rcsid[] = id +#endif + #endif /* MAKE_MAKE_H */ diff --git a/make_malloc.c b/make_malloc.c index ba9632b2b254..9a8570b6a2b0 100644 --- a/make_malloc.c +++ b/make_malloc.c @@ -1,4 +1,4 @@ -/* $NetBSD: make_malloc.c,v 1.18 2020/09/02 06:10:44 rillig Exp $ */ +/* $NetBSD: make_malloc.c,v 1.23 2020/10/05 19:27:47 rillig Exp $ */ /*- * Copyright (c) 2009 The NetBSD Foundation, Inc. @@ -26,20 +26,13 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifdef MAKE_NATIVE -#include <sys/cdefs.h> -__RCSID("$NetBSD: make_malloc.c,v 1.18 2020/09/02 06:10:44 rillig Exp $"); -#endif - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> #include <errno.h> #include "make.h" +MAKE_RCSID("$NetBSD: make_malloc.c,v 1.23 2020/10/05 19:27:47 rillig Exp $"); + #ifndef USE_EMALLOC -static MAKE_ATTR_DEAD void enomem(void); /* die when out of memory. */ static MAKE_ATTR_DEAD void diff --git a/make_malloc.h b/make_malloc.h index ac804d79c711..aa03f380070e 100644 --- a/make_malloc.h +++ b/make_malloc.h @@ -1,4 +1,4 @@ -/* $NetBSD: make_malloc.h,v 1.10 2020/08/29 16:47:45 rillig Exp $ */ +/* $NetBSD: make_malloc.h,v 1.12 2020/10/19 23:43:55 rillig Exp $ */ /*- * Copyright (c) 2009 The NetBSD Foundation, Inc. @@ -33,20 +33,20 @@ char *bmake_strdup(const char *); char *bmake_strldup(const char *, size_t); #else #include <util.h> -#define bmake_malloc(x) emalloc(x) -#define bmake_realloc(x,y) erealloc(x,y) -#define bmake_strdup(x) estrdup(x) -#define bmake_strldup(x,y) estrndup(x,y) +#define bmake_malloc(n) emalloc(n) +#define bmake_realloc(p, n) erealloc(p, n) +#define bmake_strdup(s) estrdup(s) +#define bmake_strldup(s, n) estrndup(s, n) #endif + char *bmake_strsedup(const char *, const char *); /* Thin wrapper around free(3) to avoid the extra function call in case - * p is NULL, which on x86_64 costs about 12 machine instructions. - * Other platforms are similarly affected. + * p is NULL, to save a few machine instructions. * * The case of a NULL pointer happens especially often after Var_Value, * since only environment variables need to be freed, but not others. */ -static inline void MAKE_ATTR_UNUSED +static inline MAKE_ATTR_UNUSED void bmake_free(void *p) { if (p != NULL) @@ -1,4 +1,4 @@ -/* $NetBSD: meta.c,v 1.113 2020/09/02 04:08:54 rillig Exp $ */ +/* $NetBSD: meta.c,v 1.136 2020/10/31 12:04:24 rillig Exp $ */ /* * Implement 'meta' mode. @@ -55,9 +55,9 @@ char * dirname(char *); #endif static BuildMon Mybm; /* for compat */ -static Lst metaBailiwick; /* our scope of control */ +static StringList *metaBailiwick; /* our scope of control */ static char *metaBailiwickStr; /* string storage for the list */ -static Lst metaIgnorePaths; /* paths we deliberately ignore */ +static StringList *metaIgnorePaths; /* paths we deliberately ignore */ static char *metaIgnorePathsStr; /* string storage for the list */ #ifndef MAKE_META_IGNORE_PATHS @@ -165,7 +165,6 @@ static int filemon_read(FILE *mfp, int fd) { char buf[BUFSIZ]; - int n; int error; /* Check if we're not writing to a meta data file.*/ @@ -180,11 +179,13 @@ filemon_read(FILE *mfp, int fd) 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 ((int)fwrite(buf, 1, n, mfp) < n) + if ((ssize_t)fwrite(buf, 1, (size_t)n, mfp) < n) error = EIO; } } @@ -230,7 +231,7 @@ eat_dots(char *buf, size_t bufsz, int dots) } while (cp > buf && *cp != '/'); } if (*cp == '/') { - strlcpy(cp, cp2, bufsz - (cp - buf)); + strlcpy(cp, cp2, bufsz - (size_t)(cp - buf)); } else { return; /* can't happen? */ } @@ -264,7 +265,7 @@ meta_name(char *mname, size_t mnamelen, rp++; cp++; if (strcmp(cp, rp) != 0) - strlcpy(rp, cp, sizeof(buf) - (rp - buf)); + strlcpy(rp, cp, sizeof buf - (size_t)(rp - buf)); } tname = buf; } else { @@ -320,7 +321,7 @@ static int is_submake(void *cmdp, void *gnp) { static const char *p_make = NULL; - static int p_len; + static size_t p_len; char *cmd = cmdp; GNode *gn = gnp; char *mp = NULL; @@ -329,12 +330,14 @@ is_submake(void *cmdp, void *gnp) int rc = 0; /* keep looking */ if (!p_make) { - p_make = Var_Value(".MAKE", gn, &cp); + void *dontFreeIt; + p_make = Var_Value(".MAKE", gn, &dontFreeIt); p_len = strlen(p_make); } cp = strchr(cmd, '$'); if ((cp)) { - mp = Var_Subst(cmd, gn, VARE_WANTRES); + (void)Var_Subst(cmd, gn, VARE_WANTRES, &mp); + /* TODO: handle errors */ cmd = mp; } cp2 = strstr(cmd, p_make); @@ -368,29 +371,37 @@ typedef struct meta_file_s { GNode *gn; } meta_file_t; -static int -printCMD(void *cmdp, void *mfpp) +static void +printCMD(const char *cmd, meta_file_t *mfp) { - meta_file_t *mfp = mfpp; - char *cmd = cmdp; char *cmd_freeIt = NULL; if (strchr(cmd, '$')) { - cmd = cmd_freeIt = Var_Subst(cmd, mfp->gn, VARE_WANTRES); + (void)Var_Subst(cmd, mfp->gn, VARE_WANTRES, &cmd_freeIt); + /* TODO: handle errors */ + cmd = cmd_freeIt; } fprintf(mfp->fp, "CMD %s\n", cmd); free(cmd_freeIt); - return 0; +} + +static void +printCMDs(GNode *gn, meta_file_t *mf) +{ + GNodeListNode *ln; + + for (ln = gn->commands->first; ln != NULL; ln = ln->next) + printCMD(ln->datum, mf); } /* * Certain node types never get a .meta file */ #define SKIP_META_TYPE(_type) do { \ - if ((gn->type & __CONCAT(OP_, _type))) { \ + if ((gn->type & __CONCAT(OP_, _type))) { \ if (verbose) { \ - fprintf(debug_file, "Skipping meta for %s: .%s\n", \ - gn->name, __STRING(_type)); \ + debug_printf("Skipping meta for %s: .%s\n", \ + gn->name, __STRING(_type)); \ } \ return FALSE; \ } \ @@ -423,16 +434,13 @@ meta_needed(GNode *gn, const char *dname, /* Check if there are no commands to execute. */ if (Lst_IsEmpty(gn->commands)) { if (verbose) - fprintf(debug_file, "Skipping meta for %s: no commands\n", - gn->name); + debug_printf("Skipping meta for %s: no commands\n", gn->name); return FALSE; } if ((gn->type & (OP_META|OP_SUBMAKE)) == OP_SUBMAKE) { /* OP_SUBMAKE is a bit too aggressive */ - if (Lst_ForEach(gn->commands, is_submake, gn)) { - if (DEBUG(META)) - fprintf(debug_file, "Skipping meta for %s: .SUBMAKE\n", - gn->name); + if (Lst_ForEachUntil(gn->commands, is_submake, gn)) { + DEBUG1(META, "Skipping meta for %s: .SUBMAKE\n", gn->name); return FALSE; } } @@ -440,8 +448,7 @@ meta_needed(GNode *gn, const char *dname, /* The object directory may not exist. Check it.. */ if (cached_stat(dname, &mst) != 0) { if (verbose) - fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n", - gn->name); + debug_printf("Skipping meta for %s: no .OBJDIR\n", gn->name); return FALSE; } @@ -452,8 +459,8 @@ meta_needed(GNode *gn, const char *dname, /* If we aren't in the object directory, don't create a meta file. */ if (!metaCurdirOk && strcmp(curdir, dname) == 0) { if (verbose) - fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n", - gn->name); + debug_printf("Skipping meta for %s: .OBJDIR == .CURDIR\n", + gn->name); return FALSE; } return TRUE; @@ -471,14 +478,12 @@ meta_create(BuildMon *pbm, GNode *gn) const char *tname; char *fname; const char *cp; - char *p[5]; /* >= possible uses */ - int i; + void *objdir_freeIt; mf.fp = NULL; - i = 0; - dname = Var_Value(".OBJDIR", gn, &p[i++]); - tname = Var_Value(TARGET, gn, &p[i++]); + dname = Var_Value(".OBJDIR", gn, &objdir_freeIt); + tname = GNode_VarTarget(gn); /* if this succeeds objdir is realpath of dname */ if (!meta_needed(gn, dname, objdir, TRUE)) @@ -489,7 +494,8 @@ meta_create(BuildMon *pbm, GNode *gn) char *mp; /* Describe the target we are building */ - mp = Var_Subst("${" MAKE_META_PREFIX "}", gn, VARE_WANTRES); + (void)Var_Subst("${" MAKE_META_PREFIX "}", gn, VARE_WANTRES, &mp); + /* TODO: handle errors */ if (*mp) fprintf(stdout, "%s\n", mp); free(mp); @@ -511,8 +517,7 @@ meta_create(BuildMon *pbm, GNode *gn) dname, tname, objdir); #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_create: %s\n", fname); + DEBUG1(META, "meta_create: %s\n", fname); #endif if ((mf.fp = fopen(fname, "w")) == NULL) @@ -522,11 +527,11 @@ meta_create(BuildMon *pbm, GNode *gn) mf.gn = gn; - Lst_ForEach(gn->commands, printCMD, &mf); + printCMDs(gn, &mf); fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf))); fprintf(mf.fp, "TARGET %s\n", tname); - cp = Var_Value(".OODATE", gn, &p[i++]); + cp = GNode_VarOodate(gn); if (cp && *cp) { fprintf(mf.fp, "OODATE %s\n", cp); } @@ -546,9 +551,7 @@ meta_create(BuildMon *pbm, GNode *gn) gn->type |= OP_SILENT; } out: - for (i--; i >= 0; i--) { - bmake_free(p[i]); - } + bmake_free(objdir_freeIt); return mf.fp; } @@ -592,6 +595,7 @@ meta_mode_init(const char *make_mode) { static int once = 0; char *cp; + void *freeIt; useMeta = TRUE; useFilemon = TRUE; @@ -630,32 +634,34 @@ meta_mode_init(const char *make_mode) /* * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} */ - metaBailiwick = Lst_Init(); - metaBailiwickStr = Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", - VAR_GLOBAL, VARE_WANTRES); + metaBailiwick = Lst_New(); + (void)Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", + VAR_GLOBAL, VARE_WANTRES, &metaBailiwickStr); + /* TODO: handle errors */ str2Lst_Append(metaBailiwick, metaBailiwickStr, NULL); /* * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} */ - metaIgnorePaths = Lst_Init(); + metaIgnorePaths = Lst_New(); Var_Append(MAKE_META_IGNORE_PATHS, "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL); - metaIgnorePathsStr = Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", - VAR_GLOBAL, VARE_WANTRES); + (void)Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", + VAR_GLOBAL, VARE_WANTRES, &metaIgnorePathsStr); + /* TODO: handle errors */ str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr, NULL); /* * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS} */ - cp = NULL; - if (Var_Value(MAKE_META_IGNORE_PATTERNS, VAR_GLOBAL, &cp)) { + freeIt = NULL; + if (Var_Value(MAKE_META_IGNORE_PATTERNS, VAR_GLOBAL, &freeIt)) { metaIgnorePatterns = TRUE; - bmake_free(cp); + bmake_free(freeIt); } - cp = NULL; - if (Var_Value(MAKE_META_IGNORE_FILTER, VAR_GLOBAL, &cp)) { + freeIt = NULL; + if (Var_Value(MAKE_META_IGNORE_FILTER, VAR_GLOBAL, &freeIt)) { metaIgnoreFilter = TRUE; - bmake_free(cp); + bmake_free(freeIt); } } @@ -790,7 +796,7 @@ meta_job_error(Job *job, GNode *gn, int flags, int status) "(ignored)" : ""); } if (gn) { - Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL); + Var_Set(".ERROR_TARGET", GNode_Path(gn), VAR_GLOBAL); } getcwd(cwd, sizeof(cwd)); Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL); @@ -813,15 +819,16 @@ meta_job_output(Job *job, char *cp, const char *nl) if (pbm->mfp != NULL) { if (metaVerbose) { static char *meta_prefix = NULL; - static int meta_prefix_len; + static size_t meta_prefix_len; if (!meta_prefix) { char *cp2; - meta_prefix = Var_Subst("${" MAKE_META_PREFIX "}", - VAR_GLOBAL, VARE_WANTRES); + (void)Var_Subst("${" MAKE_META_PREFIX "}", + VAR_GLOBAL, VARE_WANTRES, &meta_prefix); + /* TODO: handle errors */ if ((cp2 = strchr(meta_prefix, '$'))) - meta_prefix_len = cp2 - meta_prefix; + meta_prefix_len = (size_t)(cp2 - meta_prefix); else meta_prefix_len = strlen(meta_prefix); } @@ -910,9 +917,9 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp) struct stat fs; int x; - if (fgets(&buf[o], bufsz - o, fp) != NULL) { + if (fgets(&buf[o], (int)bufsz - o, fp) != NULL) { check_newline: - x = o + strlen(&buf[o]); + x = o + (int)strlen(&buf[o]); if (buf[x - 1] == '\n') return x; /* @@ -923,20 +930,18 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp) size_t newsz; char *p; - newsz = ROUNDUP((fs.st_size / 2), BUFSIZ); + newsz = ROUNDUP(((size_t)fs.st_size / 2), BUFSIZ); if (newsz <= bufsz) - newsz = ROUNDUP(fs.st_size, BUFSIZ); + newsz = ROUNDUP((size_t)fs.st_size, BUFSIZ); if (newsz <= bufsz) return x; /* truncated */ - if (DEBUG(META)) - fprintf(debug_file, "growing buffer %u -> %u\n", - (unsigned)bufsz, (unsigned)newsz); + DEBUG2(META, "growing buffer %zu -> %zu\n", bufsz, newsz); p = bmake_realloc(buf, newsz); if (p) { *bufp = buf = p; *szp = bufsz = newsz; /* fetch the rest */ - if (!fgets(&buf[x], bufsz - x, fp)) + if (!fgets(&buf[x], (int)bufsz - x, fp)) return x; /* truncated! */ goto check_newline; } @@ -945,7 +950,7 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp) return 0; } -/* Lst_ForEach wants 1 to stop search */ +/* Lst_ForEachUntil wants 1 to stop search */ static int prefix_match(void *p, void *q) { @@ -958,24 +963,15 @@ prefix_match(void *p, void *q) /* See if the path equals prefix or starts with "prefix/". */ static Boolean -path_match(const void *p, const void *q) +path_starts_with(const char *path, const char *prefix) { - const char *path = p; - const char *prefix = q; size_t n = strlen(prefix); if (strncmp(path, prefix, n) != 0) - return FALSE; + return FALSE; return path[n] == '\0' || path[n] == '/'; } -static Boolean -string_match(const void *p, const void *q) -{ - return strcmp(p, q) == 0; -} - - static int meta_ignore(GNode *gn, const char *p) { @@ -986,11 +982,9 @@ meta_ignore(GNode *gn, const char *p) if (*p == '/') { cached_realpath(p, fname); /* clean it up */ - if (Lst_ForEach(metaIgnorePaths, prefix_match, fname)) { + if (Lst_ForEachUntil(metaIgnorePaths, prefix_match, fname)) { #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: ignoring path: %s\n", - p); + DEBUG1(META, "meta_oodate: ignoring path: %s\n", p); #endif return TRUE; } @@ -1002,12 +996,11 @@ meta_ignore(GNode *gn, const char *p) Var_Set(".p.", p, gn); expr = "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}"; - pm = Var_Subst(expr, gn, VARE_WANTRES); + (void)Var_Subst(expr, gn, VARE_WANTRES, &pm); + /* TODO: handle errors */ if (*pm) { #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: ignoring pattern: %s\n", - p); + DEBUG1(META, "meta_oodate: ignoring pattern: %s\n", p); #endif free(pm); return TRUE; @@ -1022,12 +1015,11 @@ meta_ignore(GNode *gn, const char *p) snprintf(fname, sizeof(fname), "${%s:L:${%s:ts:}}", p, MAKE_META_IGNORE_FILTER); - fm = Var_Subst(fname, gn, VARE_WANTRES); + (void)Var_Subst(fname, gn, VARE_WANTRES, &fm); + /* TODO: handle errors */ if (*fm == '\0') { #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: ignoring filtered: %s\n", - p); + DEBUG1(META, "meta_oodate: ignoring filtered: %s\n", p); #endif free(fm); return TRUE; @@ -1063,6 +1055,17 @@ meta_ignore(GNode *gn, const char *p) *ep = '\0'; \ } +static void +append_if_new(StringList *list, const char *str) +{ + StringListNode *ln; + + for (ln = list->first; ln != NULL; ln = ln->next) + if (strcmp(ln->datum, str) == 0) + return; + Lst_Append(list, bmake_strdup(str)); +} + Boolean meta_oodate(GNode *gn, Boolean oodate) { @@ -1086,25 +1089,22 @@ meta_oodate(GNode *gn, Boolean oodate) static size_t tmplen = 0; FILE *fp; Boolean needOODATE = FALSE; - Lst missingFiles; - char *pa[4]; /* >= possible uses */ - int i; + StringList *missingFiles; int have_filemon = FALSE; + void *objdir_freeIt; if (oodate) return oodate; /* we're done */ - i = 0; - - dname = Var_Value(".OBJDIR", gn, &pa[i++]); - tname = Var_Value(TARGET, gn, &pa[i++]); + dname = Var_Value(".OBJDIR", gn, &objdir_freeIt); + tname = GNode_VarTarget(gn); /* if this succeeds fname3 is realpath of dname */ if (!meta_needed(gn, dname, fname3, FALSE)) goto oodate_out; dname = fname3; - missingFiles = Lst_Init(); + missingFiles = Lst_New(); /* * We need to check if the target is out-of-date. This includes @@ -1117,8 +1117,7 @@ meta_oodate(GNode *gn, Boolean oodate) meta_name(fname, sizeof(fname), dname, tname, dname); #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: %s\n", fname); + DEBUG1(META, "meta_oodate: %s\n", fname); #endif if ((fp = fopen(fname, "r")) != NULL) { @@ -1128,7 +1127,7 @@ meta_oodate(GNode *gn, Boolean oodate) int lastpid = 0; int pid; int x; - LstNode ln; + StringListNode *cmdNode; struct make_stat mst; if (!buf) { @@ -1152,7 +1151,7 @@ meta_oodate(GNode *gn, Boolean oodate) /* we want to track all the .meta we read */ Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); - ln = Lst_First(gn->commands); + cmdNode = gn->commands->first; while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) { lineno++; if (buf[x - 1] == '\n') @@ -1179,8 +1178,7 @@ meta_oodate(GNode *gn, Boolean oodate) /* Delimit the record type. */ p = buf; #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf); + DEBUG3(META, "%s: %d: %s\n", fname, lineno, buf); #endif strsep(&p, " "); if (have_filemon) { @@ -1223,7 +1221,7 @@ meta_oodate(GNode *gn, Boolean oodate) pid = atoi(p); if (pid > 0 && pid != lastpid) { const char *ldir; - char *tp; + void *tp; if (lastpid > 0) { /* We need to remember these. */ @@ -1249,9 +1247,9 @@ meta_oodate(GNode *gn, Boolean oodate) continue; #ifdef DEBUG_META_MODE if (DEBUG(META)) - fprintf(debug_file, "%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n", - fname, lineno, - pid, buf[0], cwd, lcwd, latestdir); + debug_printf("%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n", + fname, lineno, + pid, buf[0], cwd, lcwd, latestdir); #endif break; } @@ -1279,9 +1277,10 @@ meta_oodate(GNode *gn, Boolean oodate) Var_Set(cldir, latestdir, VAR_GLOBAL); #ifdef DEBUG_META_MODE if (DEBUG(META)) - fprintf(debug_file, "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", - fname, lineno, - child, cwd, lcwd, latestdir); + debug_printf( + "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", + fname, lineno, + child, cwd, lcwd, latestdir); #endif } } @@ -1294,8 +1293,8 @@ meta_oodate(GNode *gn, Boolean oodate) Var_Set(lcwd_vname, lcwd, VAR_GLOBAL); Var_Set(ldir_vname, lcwd, VAR_GLOBAL); #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd); + DEBUG4(META, "%s: %d: cwd=%s ldir=%s\n", + fname, lineno, cwd, lcwd); #endif break; @@ -1317,29 +1316,23 @@ meta_oodate(GNode *gn, Boolean oodate) DEQUOTE(move_target); /* FALLTHROUGH */ case 'D': /* unlink */ - if (*p == '/' && !Lst_IsEmpty(missingFiles)) { + if (*p == '/') { /* remove any missingFiles entries that match p */ - ln = Lst_Find(missingFiles, path_match, p); - if (ln != NULL) { - LstNode nln; - char *tp; - - do { - nln = Lst_FindFrom(missingFiles, - LstNode_Next(ln), - path_match, p); - tp = LstNode_Datum(ln); + 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); - free(tp); - } while ((ln = nln) != NULL); + } + ln = next; } } if (buf[0] == 'M') { /* the target of the mv is a file 'W'ritten */ #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: M %s -> %s\n", - p, move_target); + DEBUG2(META, "meta_oodate: M %s -> %s\n", + p, move_target); #endif p = move_target; goto check_write; @@ -1360,9 +1353,7 @@ meta_oodate(GNode *gn, Boolean oodate) DEQUOTE(p); DEQUOTE(link_src); #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: L %s -> %s\n", - link_src, p); + DEBUG2(META, "meta_oodate: L %s -> %s\n", link_src, p); #endif /* FALLTHROUGH */ case 'W': /* Write */ @@ -1383,7 +1374,7 @@ meta_oodate(GNode *gn, Boolean oodate) if (strncmp(p, cwd, cwdlen) == 0) break; - if (!Lst_ForEach(metaBailiwick, prefix_match, p)) + if (!Lst_ForEachUntil(metaBailiwick, prefix_match, p)) break; /* tmpdir might be within */ @@ -1396,18 +1387,15 @@ meta_oodate(GNode *gn, Boolean oodate) if ((link_src != NULL && cached_lstat(p, &mst) < 0) || (link_src == NULL && cached_stat(p, &mst) < 0)) { - if (!meta_ignore(gn, p)) { - if (Lst_Find(missingFiles, string_match, p) == NULL) - Lst_Append(missingFiles, bmake_strdup(p)); - } + if (!meta_ignore(gn, p)) + append_if_new(missingFiles, p); } break; check_link_src: p = link_src; link_src = NULL; #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: L src %s\n", p); + DEBUG1(META, "meta_oodate: L src %s\n", p); #endif /* FALLTHROUGH */ case 'R': /* Read */ @@ -1455,8 +1443,8 @@ meta_oodate(GNode *gn, Boolean oodate) for (sdp = sdirs; *sdp && !found; sdp++) { #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp); + DEBUG3(META, "%s: %d: looking for: %s\n", + fname, lineno, *sdp); #endif if (cached_stat(*sdp, &mst) == 0) { found = 1; @@ -1465,13 +1453,13 @@ meta_oodate(GNode *gn, Boolean oodate) } if (found) { #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p); + DEBUG3(META, "%s: %d: found: %s\n", + fname, lineno, p); #endif if (!S_ISDIR(mst.mst_mode) && mst.mst_mtime > gn->mtime) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); + DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n", + fname, lineno, p); oodate = TRUE; } else if (S_ISDIR(mst.mst_mode)) { /* Update the latest directory. */ @@ -1483,8 +1471,7 @@ meta_oodate(GNode *gn, Boolean oodate) * A referenced file outside of CWD is missing. * We cannot catch every eventuality here... */ - if (Lst_Find(missingFiles, string_match, p) == NULL) - Lst_Append(missingFiles, bmake_strdup(p)); + append_if_new(missingFiles, p); } } if (buf[0] == 'E') { @@ -1502,12 +1489,12 @@ meta_oodate(GNode *gn, Boolean oodate) * Compare the current command with the one in the * meta data file. */ - if (ln == NULL) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno); + if (cmdNode == NULL) { + DEBUG2(META, "%s: %d: there were more build commands in the meta data file than there are now...\n", + fname, lineno); oodate = TRUE; } else { - char *cmd = LstNode_Datum(ln); + char *cmd = cmdNode->datum; Boolean hasOODATE = FALSE; if (strstr(cmd, "$?")) @@ -1519,10 +1506,11 @@ meta_oodate(GNode *gn, Boolean oodate) } if (hasOODATE) { needOODATE = TRUE; - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno); + DEBUG2(META, "%s: %d: cannot compare command using .OODATE\n", + fname, lineno); } - cmd = Var_Subst(cmd, gn, VARE_WANTRES|VARE_UNDEFERR); + (void)Var_Subst(cmd, gn, VARE_WANTRES|VARE_UNDEFERR, &cmd); + /* TODO: handle errors */ if ((cp = strchr(cmd, '\n'))) { int n; @@ -1553,28 +1541,28 @@ meta_oodate(GNode *gn, Boolean oodate) !hasOODATE && !(gn->type & OP_NOMETA_CMP) && strcmp(p, cmd) != 0) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); + DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n", + fname, lineno, p, cmd); if (!metaIgnoreCMDs) oodate = TRUE; } free(cmd); - ln = LstNode_Next(ln); + 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 && ln != NULL) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno); + if (!oodate && cmdNode != NULL) { + DEBUG2(META, "%s: %d: there are extra build commands now that weren't in the meta data file\n", + fname, lineno); oodate = TRUE; } CHECK_VALID_META(p); if (strcmp(p, cwd) != 0) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir); + DEBUG4(META, "%s: %d: the current working directory has changed from '%s' to '%s'\n", + fname, lineno, p, curdir); oodate = TRUE; } } @@ -1582,14 +1570,12 @@ meta_oodate(GNode *gn, Boolean oodate) fclose(fp); if (!Lst_IsEmpty(missingFiles)) { - if (DEBUG(META)) - fprintf(debug_file, "%s: missing files: %s...\n", - fname, (char *)LstNode_Datum(Lst_First(missingFiles))); + DEBUG2(META, "%s: missing files: %s...\n", + fname, (char *)missingFiles->first->datum); oodate = TRUE; } if (!oodate && !have_filemon && filemonMissing) { - if (DEBUG(META)) - fprintf(debug_file, "%s: missing filemon data\n", fname); + DEBUG1(META, "%s: missing filemon data\n", fname); oodate = TRUE; } } else { @@ -1598,13 +1584,12 @@ meta_oodate(GNode *gn, Boolean oodate) /* if target is in .CURDIR we do not need a meta file */ if (gn->path && (cp = strrchr(gn->path, '/')) && cp > gn->path) { - if (strncmp(curdir, gn->path, (cp - gn->path)) != 0) { + if (strncmp(curdir, gn->path, (size_t)(cp - gn->path)) != 0) { cp = NULL; /* not in .CURDIR */ } } if (!cp) { - if (DEBUG(META)) - fprintf(debug_file, "%s: required but missing\n", fname); + DEBUG1(META, "%s: required but missing\n", fname); oodate = TRUE; needOODATE = TRUE; /* assume the worst */ } @@ -1620,14 +1605,11 @@ meta_oodate(GNode *gn, Boolean oodate) * All we can sanely do is set it to .ALLSRC. */ Var_Delete(OODATE, gn); - Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn); - bmake_free(cp); + Var_Set(OODATE, GNode_VarAllsrc(gn), gn); } oodate_out: - for (i--; i >= 0; i--) { - bmake_free(pa[i]); - } + bmake_free(objdir_freeIt); return oodate; } @@ -1662,11 +1644,8 @@ void meta_compat_child(void) { meta_job_child(NULL); - if (dup2(childPipe[1], 1) < 0 || - dup2(1, 2) < 0) { - execError("dup2", "pipe"); - _exit(1); - } + if (dup2(childPipe[1], 1) < 0 || dup2(1, 2) < 0) + execDie("dup2", "pipe"); } void @@ -1,4 +1,4 @@ -/* $NetBSD: meta.h,v 1.7 2020/07/03 08:13:23 rillig Exp $ */ +/* $NetBSD: meta.h,v 1.8 2020/10/19 23:43:55 rillig Exp $ */ /* * Things needed for 'meta' mode. @@ -38,9 +38,8 @@ typedef struct BuildMon { FILE *mfp; } BuildMon; -extern Boolean useMeta; +struct Job; -struct Job; /* not defined yet */ void meta_init(void); void meta_finish(void); void meta_mode_init(const char *); @@ -57,3 +56,5 @@ Boolean meta_oodate(GNode *, Boolean); void meta_compat_start(void); void meta_compat_child(void); void meta_compat_parent(pid_t); + +extern Boolean useMeta; diff --git a/metachar.c b/metachar.c index 971962272a4a..e510eeb66609 100644 --- a/metachar.c +++ b/metachar.c @@ -1,4 +1,4 @@ -/* $NetBSD: metachar.c,v 1.6 2020/08/03 20:43:41 rillig Exp $ */ +/* $NetBSD: metachar.c,v 1.8 2020/10/30 19:14:20 rillig Exp $ */ /*- * Copyright (c) 2015 The NetBSD Foundation, Inc. @@ -37,18 +37,15 @@ #include <sys/cdefs.h> #endif -#if defined(__RCSID) && !defined(lint) -__RCSID("$NetBSD: metachar.c,v 1.6 2020/08/03 20:43:41 rillig Exp $"); -#endif - #include "metachar.h" + +MAKE_RCSID("$NetBSD: metachar.c,v 1.8 2020/10/30 19:14:20 rillig Exp $"); + /* * The following array is used to make a fast determination of which * characters are interpreted specially by the shell. If a command * contains any of these characters, it is executed by the shell, not * directly by us. - * - * perhaps move it to ctype? */ unsigned char _metachar[128] = { diff --git a/metachar.h b/metachar.h index 98408ab3fb58..f180b483f8c3 100644 --- a/metachar.h +++ b/metachar.h @@ -1,4 +1,4 @@ -/* $NetBSD: metachar.h,v 1.7 2020/08/25 17:37:09 rillig Exp $ */ +/* $NetBSD: metachar.h,v 1.11 2020/10/31 18:20:00 rillig Exp $ */ /*- * Copyright (c) 2015 The NetBSD Foundation, Inc. @@ -35,17 +35,13 @@ extern unsigned char _metachar[]; -#define ismeta(c) _metachar[(c) & 0x7f] +#define is_shell_metachar(c) _metachar[(c) & 0x7f] -static inline int MAKE_ATTR_UNUSED -needshell(const char *cmd, int white) +static inline MAKE_ATTR_UNUSED int +needshell(const char *cmd) { - while (!ismeta(*cmd) && *cmd != ':' && *cmd != '=') { - if (white && isspace((unsigned char)*cmd)) - break; + while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=') cmd++; - } - return *cmd != '\0'; } diff --git a/mk/ChangeLog b/mk/ChangeLog index 9ea9d42a7776..d72f9ff2bb6f 100644 --- a/mk/ChangeLog +++ b/mk/ChangeLog @@ -1,3 +1,31 @@ +2020-11-01 Simon J Gerraty <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * 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 <sjg@beast.crufty.net> + + * install-mk (MK_VERSION): 20201001 + + * meta2deps.{py,sh}: throw an error if we don't see filemon version + +2020-09-09 Simon J Gerraty <sjg@beast.crufty.net> + + * install-mk (MK_VERSION): 20200909 + + * dirdeps-cache-update.mk: use cache_update_dirdep as guard target + 2020-08-26 Simon J Gerraty <sjg@beast.crufty.net> * dirdeps.mk: ensure we cannot confuse a static cache for dynamic diff --git a/mk/dirdeps-cache-update.mk b/mk/dirdeps-cache-update.mk index eb992e936eb8..e442efd1d497 100644 --- a/mk/dirdeps-cache-update.mk +++ b/mk/dirdeps-cache-update.mk @@ -1,4 +1,4 @@ -# $Id: dirdeps-cache-update.mk,v 1.21 2020/08/19 17:51:53 sjg Exp $ +# $Id: dirdeps-cache-update.mk,v 1.22 2020/09/10 00:14:38 sjg Exp $ # # @(#) Copyright (c) 2020, Simon J. Gerraty # @@ -86,7 +86,7 @@ _debug_cache = 0 .endif .if ${MK_STATIC_DIRDEPS_CACHE} == "yes" && defined(STATIC_DIRDEPS_CACHE) && exists(${STATIC_DIRDEPS_CACHE}) -.if !make(dirdeps) +.if !make(dirdeps) && !target(cache_update_dirdep) # We are using static cache and this is the only look we will get. # We want to generate an updated cache while we build # so need to hook cache-update to dirdeps now. @@ -99,12 +99,10 @@ _debug_cache = 0 cache_update_dirdep ?= $d.${TARGET_SPEC} .endif .endfor -.if !target(${cache_update_dirdep}) -dirdeps: ${cache_update_dirdep} +dirdeps cache_update_dirdep: ${cache_update_dirdep} ${cache_update_dirdep}: _DIRDEP_USE DYNAMIC_DIRDEPS_CACHE := ${OBJTOP}/dirdeps.cache.${STATIC_DIRDEPS_CACHE:H:T}-update .export DYNAMIC_DIRDEPS_CACHE STATIC_DIRDEPS_CACHE -.endif .endif # make(dirdeps) .endif # MK_* diff --git a/mk/dirdeps.mk b/mk/dirdeps.mk index 16673a04c07b..237d4c7e74aa 100644 --- a/mk/dirdeps.mk +++ b/mk/dirdeps.mk @@ -1,4 +1,4 @@ -# $Id: dirdeps.mk,v 1.125 2020/08/26 21:49:45 sjg Exp $ +# $Id: dirdeps.mk,v 1.130 2020/11/02 00:34:30 sjg Exp $ # Copyright (c) 2010-2020, Simon J. Gerraty # Copyright (c) 2010-2018, Juniper Networks, Inc. @@ -209,12 +209,9 @@ DEP_$v ?= ${$v} # so we need to construct a set of modifiers to fill in the gaps. .if ${MAKE_VERSION} >= 20170130 _tspec_x := ${TARGET_SPEC_VARS:range} -.elif ${TARGET_SPEC_VARS:[#]} > 10 -# seriously? better have jot(1) or equivalent to produce suitable sequence -_tspec_x := ${${JOT:Ujot} ${TARGET_SPEC_VARS:[#]}:L:sh} .else -# we can provide the sequence ourselves -_tspec_x := ${1 2 3 4 5 6 7 8 9 10:L:[1..${TARGET_SPEC_VARS:[#]}]} +# 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}; @@ -494,6 +491,11 @@ dirdeps-cached: ${DIRDEPS_CACHE} .MAKE @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} @@ -517,6 +519,7 @@ ${DIRDEPS_CACHE}: .META .NOMETA_CMP ${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 && \ @@ -627,6 +630,7 @@ __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} diff --git a/mk/install-mk b/mk/install-mk index 66185e42cc6e..01680f401745 100644 --- a/mk/install-mk +++ b/mk/install-mk @@ -55,7 +55,7 @@ # Simon J. Gerraty <sjg@crufty.net> # RCSid: -# $Id: install-mk,v 1.179 2020/08/26 21:49:45 sjg Exp $ +# $Id: install-mk,v 1.183 2020/11/02 16:34:12 sjg Exp $ # # @(#) Copyright (c) 1994 Simon J. Gerraty # @@ -70,7 +70,7 @@ # sjg@crufty.net # -MK_VERSION=20200826 +MK_VERSION=20201101 OWNER= GROUP= MODE=444 diff --git a/mk/meta2deps.py b/mk/meta2deps.py index 9231003b70df..4627e08d7c11 100755 --- a/mk/meta2deps.py +++ b/mk/meta2deps.py @@ -37,7 +37,7 @@ We only pay attention to a subset of the information in the """ RCSid: - $Id: meta2deps.py,v 1.33 2020/08/19 17:51:53 sjg Exp $ + $Id: meta2deps.py,v 1.34 2020/10/02 03:11:17 sjg Exp $ Copyright (c) 2011-2020, Simon J. Gerraty Copyright (c) 2011-2017, Juniper Networks, Inc. @@ -510,6 +510,7 @@ class MetaFile: continue self.parse_path(path, cwd, w[0], w) + assert(version > 0) if not file: f.close() diff --git a/mk/meta2deps.sh b/mk/meta2deps.sh index e56e52a89675..f01d54774a04 100755 --- a/mk/meta2deps.sh +++ b/mk/meta2deps.sh @@ -77,7 +77,7 @@ # RCSid: -# $Id: meta2deps.sh,v 1.13 2020/08/19 17:51:53 sjg Exp $ +# $Id: meta2deps.sh,v 1.14 2020/10/02 03:11:17 sjg Exp $ # Copyright (c) 2010-2013, Juniper Networks, Inc. # All rights reserved. @@ -143,6 +143,11 @@ _excludes_f() { egrep -v "$EXCLUDES" } +error() { + echo "ERROR: $@" >&2 + exit 1 +} + meta2deps() { DPDEPS= SRCTOPS=$SRCTOP @@ -236,8 +241,8 @@ meta2deps() { ;; *) cat /dev/null "$@";; esac 2> /dev/null | - sed -e 's,^CWD,C C,;/^[CREFLM] /!d' -e "s,',,g" | - $_excludes | + sed -e 's,^CWD,C C,;/^[CREFLMV] /!d' -e "s,',,g" | + $_excludes | ( version=no while read op pid path junk do : op=$op pid=$pid path=$path @@ -249,6 +254,12 @@ meta2deps() { SB=`echo $CWD | sed 's,/obj.*,,'` fi SRCTOP=${SRCTOP:-$SB/src} + case "$verion" in + no) ;; # ignore + 0) error "no filemon data";; + *) ;; + esac + version=0 continue ;; $pid,$pid) ;; @@ -263,6 +274,7 @@ meta2deps() { esac case "$op,$path" in + V,*) version=$path; continue;; W,*srcrel|*.dirdep) continue;; C,*) case "$path" in @@ -368,6 +380,9 @@ meta2deps() { echo $dir;; esac done > $tf.dirdep + case "$version" in + 0) error "no filemon data";; + esac ) || exit 1 _nl=echo for f in $tf.dirdep $tf.qual $tf.srcdep do diff --git a/mk/sys.vars.mk b/mk/sys.vars.mk index 24e0ed26a15f..592cbdc644dc 100644 --- a/mk/sys.vars.mk +++ b/mk/sys.vars.mk @@ -1,4 +1,4 @@ -# $Id: sys.vars.mk,v 1.5 2020/08/19 17:51:53 sjg Exp $ +# $Id: sys.vars.mk,v 1.6 2020/10/28 20:50:04 sjg Exp $ # # @(#) Copyright (c) 2003-2009, Simon J. Gerraty # @@ -57,6 +57,20 @@ _type_sh = which M_type = @x@(${_type_sh:Utype} $$x) 2> /dev/null; echo;@:sh:[0]:N* found*:[@]:C,[()],,g M_whence = ${M_type}:M/*:[1] +# produce similar output to jot(1) +# eg. ${LIST:[#]:${M_JOT}} +# would be 1 2 3 4 5 if LIST has 5 words +# ${9:L:${M_JOT}} +# would be 1 2 3 4 5 6 7 8 9 +M_JOT = @x@i=1;while [ $$$$i -le $$x ]; do echo $$$$i; i=$$$$((i + 1)); done;@:sh + +# ${LIST:${M_RANGE}} is 1 2 3 4 5 if LIST has 5 words +.if ${MAKE_VERSION} >= 20170130 +M_RANGE = range +.else +M_RANGE = [#]:${M_JOT} +.endif + # convert a path to a valid shell variable M_P2V = tu:C,[./-],_,g diff --git a/nonints.h b/nonints.h index 6bcbd7690adb..ba2e3bbbe4ed 100644 --- a/nonints.h +++ b/nonints.h @@ -1,4 +1,4 @@ -/* $NetBSD: nonints.h,v 1.102 2020/08/30 19:56:02 rillig Exp $ */ +/* $NetBSD: nonints.h,v 1.149 2020/11/01 00:24:57 rillig Exp $ */ /*- * Copyright (c) 1988, 1989, 1990, 1993 @@ -73,32 +73,32 @@ */ /* arch.c */ -Boolean Arch_ParseArchive(char **, Lst, GNode *); +void Arch_Init(void); +void Arch_End(void); + +Boolean Arch_ParseArchive(char **, GNodeList *, GNode *); void Arch_Touch(GNode *); void Arch_TouchLib(GNode *); time_t Arch_MTime(GNode *); time_t Arch_MemMTime(GNode *); -void Arch_FindLib(GNode *, Lst); +void Arch_FindLib(GNode *, SearchPath *); Boolean Arch_LibOODate(GNode *); -void Arch_Init(void); -void Arch_End(void); Boolean Arch_IsLib(GNode *); /* compat.c */ -int CompatRunCommand(void *, void *); -void Compat_Run(Lst); -int Compat_Make(void *, void *); +int Compat_RunCommand(const char *, GNode *); +void Compat_Run(GNodeList *); +void Compat_Make(GNode *, GNode *); /* cond.c */ -struct If; -CondEvalResult Cond_EvalExpression(const struct If *, char *, Boolean *, int, Boolean); -CondEvalResult Cond_Eval(char *); +CondEvalResult Cond_EvalCondition(const char *, Boolean *); +CondEvalResult Cond_EvalLine(const char *); void Cond_restore_depth(unsigned int); unsigned int Cond_save_depth(void); /* for.c */ -int For_Eval(char *); -int For_Accum(char *); +int For_Eval(const char *); +Boolean For_Accum(const char *); void For_Run(int); /* job.c */ @@ -114,35 +114,52 @@ void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; void DieHorribly(void) MAKE_ATTR_DEAD; -int PrintAddr(void *, void *); void Finish(int) MAKE_ATTR_DEAD; int eunlink(const char *); -void execError(const char *, const char *); +void execDie(const char *, const char *); char *getTmpdir(void); Boolean s2Boolean(const char *, Boolean); Boolean getBoolean(const char *, Boolean); char *cached_realpath(const char *, char *); /* parse.c */ -void Parse_Error(int, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); -Boolean Parse_IsVar(char *); -void Parse_DoVar(char *, GNode *); -void Parse_AddIncludeDir(char *); -void Parse_File(const char *, int); void Parse_Init(void); void Parse_End(void); -void Parse_SetInput(const char *, int, int, char *(*)(void *, size_t *), void *); -Lst Parse_MainName(void); + +typedef enum VarAssignOp { + VAR_NORMAL, /* = */ + VAR_SUBST, /* := */ + VAR_SHELL, /* != or :sh= */ + VAR_APPEND, /* += */ + VAR_DEFAULT /* ?= */ +} VarAssignOp; + +typedef struct VarAssign { + char *varname; /* unexpanded */ + VarAssignOp op; + const char *value; /* unexpanded */ +} VarAssign; + +typedef char *(*NextBufProc)(void *, size_t *); + +void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); +Boolean Parse_IsVar(const char *, VarAssign *out_var); +void Parse_DoVar(VarAssign *, GNode *); +void Parse_AddIncludeDir(const char *); +void Parse_File(const char *, int); +void Parse_SetInput(const char *, int, int, NextBufProc, void *); +GNodeList *Parse_MainName(void); +int Parse_GetFatals(void); /* str.c */ -typedef struct { +typedef struct Words { char **words; size_t len; void *freeIt; } Words; Words Str_Words(const char *, Boolean); -static inline void MAKE_ATTR_UNUSED +static inline MAKE_ATTR_UNUSED void Words_Free(Words w) { free(w.words); free(w.freeIt); @@ -151,7 +168,6 @@ Words_Free(Words w) { 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 *); -char *Str_FindSubstring(const char *, const char *); Boolean Str_Match(const char *, const char *); #ifndef HAVE_STRLCPY @@ -160,69 +176,134 @@ 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(char *); -GNode *Suff_AddTransform(char *); -int Suff_EndTransform(void *, void *); +Boolean Suff_IsTransform(const char *); +GNode *Suff_AddTransform(const char *); +void Suff_EndTransform(GNode *); void Suff_AddSuffix(const char *, GNode **); -Lst Suff_GetPath(char *); +SearchPath *Suff_GetPath(const char *); void Suff_DoPaths(void); -void Suff_AddInclude(char *); +void Suff_AddInclude(const char *); void Suff_AddLib(const char *); void Suff_FindDeps(GNode *); -Lst Suff_FindPath(GNode *); -void Suff_SetNull(char *); -void Suff_Init(void); -void Suff_End(void); +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); -Lst Targ_List(void); +GNodeList *Targ_List(void); GNode *Targ_NewGN(const char *); -GNode *Targ_FindNode(const char *, int); -Lst Targ_FindList(Lst, int); +GNode *Targ_FindNode(const char *); +GNode *Targ_GetNode(const char *); +GNode *Targ_NewInternalNode(const char *); +GNode *Targ_GetEndNode(void); +GNodeList *Targ_FindList(StringList *); Boolean Targ_Ignore(GNode *); Boolean Targ_Silent(GNode *); Boolean Targ_Precious(GNode *); void Targ_SetMain(GNode *); -int Targ_PrintCmd(void *, void *); -int Targ_PrintNode(void *, void *); +void Targ_PrintCmds(GNode *); +void Targ_PrintNode(GNode *, int); +void Targ_PrintNodes(GNodeList *, int); char *Targ_FmtTime(time_t); void Targ_PrintType(int); void Targ_PrintGraph(int); void Targ_Propagate(void); /* var.c */ +void Var_Init(void); +void Var_End(void); -typedef enum { +typedef enum VarEvalFlags { + VARE_NONE = 0, /* Treat undefined variables as errors. */ VARE_UNDEFERR = 0x01, /* Expand and evaluate variables during parsing. */ VARE_WANTRES = 0x02, + /* In an assignment using the ':=' operator, keep '$$' as '$$' instead + * of reducing it to a single '$'. */ VARE_ASSIGN = 0x04 } VarEvalFlags; -typedef enum { +typedef enum VarSet_Flags { VAR_NO_EXPORT = 0x01, /* do not export */ /* Make the variable read-only. No further modification is possible, * except for another call to Var_Set with the same flag. */ VAR_SET_READONLY = 0x02 } VarSet_Flags; +/* The state of error handling returned by Var_Parse. + * + * As of 2020-09-13, this bitset looks quite bloated, + * with all the constants doubled. + * + * Its purpose is to first document the existing behavior, + * and then migrate away from the SILENT constants, step by step, + * as these are not suited for reliable, consistent error handling + * and reporting. */ +typedef enum VarParseResult { + + /* Both parsing and evaluation succeeded. */ + VPR_OK = 0x0000, + + /* See if a message has already been printed for this error. */ + VPR_ANY_MSG = 0x0001, + + /* Parsing failed. + * No error message has been printed yet. + * Deprecated, migrate to VPR_PARSE_MSG instead. */ + VPR_PARSE_SILENT = 0x0002, + + /* Parsing failed. + * An error message has already been printed. */ + VPR_PARSE_MSG = VPR_PARSE_SILENT | VPR_ANY_MSG, + + /* Parsing succeeded. + * During evaluation, VARE_UNDEFERR was set and there was an undefined + * variable. + * No error message has been printed yet. + * Deprecated, migrate to VPR_UNDEF_MSG instead. */ + VPR_UNDEF_SILENT = 0x0004, + + /* Parsing succeeded. + * During evaluation, VARE_UNDEFERR was set and there was an undefined + * variable. + * An error message has already been printed. */ + VPR_UNDEF_MSG = VPR_UNDEF_SILENT | VPR_ANY_MSG, + + /* Parsing succeeded. + * Evaluation failed. + * No error message has been printed yet. + * Deprecated, migrate to VPR_EVAL_MSG instead. */ + VPR_EVAL_SILENT = 0x0006, + + /* Parsing succeeded. + * Evaluation failed. + * An error message has already been printed. */ + VPR_EVAL_MSG = VPR_EVAL_SILENT | VPR_ANY_MSG, + + /* The exact error handling status is not known yet. + * Deprecated, migrate to VPR_OK or any VPE_*_MSG instead. */ + VPR_UNKNOWN = 0x0008 +} VarParseResult; void Var_Delete(const char *, GNode *); void Var_Set(const char *, const char *, GNode *); void Var_Set_with_flags(const char *, const char *, GNode *, VarSet_Flags); void Var_Append(const char *, const char *, GNode *); Boolean Var_Exists(const char *, GNode *); -const char *Var_Value(const char *, GNode *, char **); -const char *Var_Parse(const char *, GNode *, VarEvalFlags, int *, void **); -char *Var_Subst(const char *, GNode *, VarEvalFlags); -void Var_Init(void); -void Var_End(void); +const char *Var_Value(const char *, GNode *, void **); +const char *Var_ValueDirect(const char *, GNode *); +VarParseResult Var_Parse(const char **, GNode *, VarEvalFlags, + const char **, void **); +VarParseResult Var_Subst(const char *, GNode *, VarEvalFlags, char **); void Var_Stats(void); void Var_Dump(GNode *); void Var_ExportVars(void); @@ -230,4 +311,5 @@ void Var_Export(const char *, Boolean); void Var_UnExport(const char *); /* util.c */ -void (*bmake_signal(int, void (*)(int)))(int); +typedef void (*SignalProc)(int); +SignalProc bmake_signal(int, SignalProc); @@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.275 2020/09/01 17:38:26 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,71 +68,41 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: parse.c,v 1.275 2020/09/01 17:38:26 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)parse.c 8.3 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: parse.c,v 1.275 2020/09/01 17:38:26 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * parse.c -- - * Functions to parse a makefile. - * - * One function, Parse_Init, must be called before any functions - * in this module are used. After that, the function Parse_File is the - * main entry point and controls most of the other functions in this - * module. +/* + * Parsing of makefiles. * - * Most important structures are kept in Lsts. Directories for - * the .include "..." function are kept in the 'parseIncPath' Lst, while - * those for the .include <...> are kept in the 'sysIncPath' Lst. The - * targets currently being defined are kept in the 'targets' Lst. + * Parse_File is the main entry point and controls most of the other + * functions in this module. * - * The variables 'fname' and 'lineno' are used to track the name - * of the current file and the line number in that file so that error - * messages can be more meaningful. + * 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 Initialization function which must be - * called before anything else in this module - * is used. + * Parse_Init Initialize the module + * + * Parse_End Clean up the module * - * Parse_End Cleanup the module + * Parse_File Parse a top-level makefile. Included files are + * handled by Parse_include_file though. * - * Parse_File Function used to parse a makefile. It must - * be given the name of the file, which should - * already have been opened, and a function - * to call to read a character from the file. + * Parse_IsVar 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_IsVar Returns TRUE if the given line is a - * variable assignment. Used by MainParseArgs - * to determine if an argument is a target - * or a variable assignment. Used internally - * for pretty much the same thing... + * Parse_Error Report a parse error, a warning or an informational + * message. * - * Parse_Error Function called when an error occurs in - * parsing. Used by the variable and - * conditional modules. - * Parse_MainName Returns a Lst of the main target to create. + * Parse_MainName Returns a list of the main target to create. */ #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <stdarg.h> -#include <stdio.h> #include "make.h" -#include "dir.h" -#include "job.h" -#include "pathnames.h" #ifdef HAVE_STDINT_H #include <stdint.h> @@ -149,82 +119,79 @@ __RCSID("$NetBSD: parse.c,v 1.275 2020/09/01 17:38:26 rillig Exp $"); #endif #endif +#include "dir.h" +#include "job.h" +#include "pathnames.h" + +/* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $"); + /* types and constants */ /* * Structure for a file being read ("included file") */ typedef struct IFile { - char *fname; /* name of file */ - int lineno; /* current line number in file */ - int first_lineno; /* line number of start of text */ - int cond_depth; /* 'if' nesting when file opened */ - Boolean depending; /* state of doing_depend on EOF */ - char *P_str; /* point to base of string buffer */ - char *P_ptr; /* point to next char of string buffer */ - char *P_end; /* point to the end of string buffer */ - char *(*nextbuf)(void *, size_t *); /* Function to get more data */ - void *nextbuf_arg; /* Opaque arg for nextbuf() */ - struct loadedfile *lf; /* loadedfile object, if any */ + char *fname; /* name of file */ + Boolean fromForLoop; /* simulated .include by the .for loop */ + int lineno; /* current line number in file */ + int first_lineno; /* line number of start of text */ + unsigned int cond_depth; /* 'if' nesting when file opened */ + Boolean depending; /* state of doing_depend on EOF */ + + /* The buffer from which the file's content is read. */ + char *buf_freeIt; + char *buf_ptr; /* next char to be read */ + char *buf_end; + + char *(*nextbuf)(void *, size_t *); /* Function to get more data */ + void *nextbuf_arg; /* Opaque arg for nextbuf() */ + struct loadedfile *lf; /* loadedfile object, if any */ } IFile; - -/* - * These values are returned by ParseEOF to tell Parse_File whether to - * CONTINUE parsing, i.e. it had only reached the end of an include file, - * or if it's DONE. - */ -#define CONTINUE 1 -#define DONE 0 - /* * Tokens for target attributes */ -typedef enum { - Begin, /* .BEGIN */ - Default, /* .DEFAULT */ - DeleteOnError, /* .DELETE_ON_ERROR */ - End, /* .END */ - dotError, /* .ERROR */ - Ignore, /* .IGNORE */ - Includes, /* .INCLUDES */ - Interrupt, /* .INTERRUPT */ - Libs, /* .LIBS */ - Meta, /* .META */ - MFlags, /* .MFLAGS or .MAKEFLAGS */ - Main, /* .MAIN and we don't have anything user-specified to - * make */ - NoExport, /* .NOEXPORT */ - NoMeta, /* .NOMETA */ - NoMetaCmp, /* .NOMETA_CMP */ - NoPath, /* .NOPATH */ - Not, /* Not special */ - NotParallel, /* .NOTPARALLEL */ - Null, /* .NULL */ - ExObjdir, /* .OBJDIR */ - Order, /* .ORDER */ - Parallel, /* .PARALLEL */ - ExPath, /* .PATH */ - Phony, /* .PHONY */ +typedef enum ParseSpecial { + SP_ATTRIBUTE, /* Generic attribute */ + SP_BEGIN, /* .BEGIN */ + SP_DEFAULT, /* .DEFAULT */ + SP_DELETE_ON_ERROR, /* .DELETE_ON_ERROR */ + SP_END, /* .END */ + SP_ERROR, /* .ERROR */ + SP_IGNORE, /* .IGNORE */ + SP_INCLUDES, /* .INCLUDES; not mentioned in the manual page */ + SP_INTERRUPT, /* .INTERRUPT */ + SP_LIBS, /* .LIBS; not mentioned in the manual page */ + SP_MAIN, /* .MAIN and we don't have anything user-specified to + * make */ + SP_META, /* .META */ + SP_MFLAGS, /* .MFLAGS or .MAKEFLAGS */ + SP_NOMETA, /* .NOMETA */ + SP_NOMETA_CMP, /* .NOMETA_CMP */ + SP_NOPATH, /* .NOPATH */ + SP_NOT, /* Not special */ + SP_NOTPARALLEL, /* .NOTPARALLEL or .NO_PARALLEL */ + SP_NULL, /* .NULL; not mentioned in the manual page */ + SP_OBJDIR, /* .OBJDIR */ + SP_ORDER, /* .ORDER */ + SP_PARALLEL, /* .PARALLEL; not mentioned in the manual page */ + SP_PATH, /* .PATH or .PATH.suffix */ + SP_PHONY, /* .PHONY */ #ifdef POSIX - Posix, /* .POSIX */ + SP_POSIX, /* .POSIX; not mentioned in the manual page */ #endif - Precious, /* .PRECIOUS */ - ExShell, /* .SHELL */ - Silent, /* .SILENT */ - SingleShell, /* .SINGLESHELL */ - Stale, /* .STALE */ - Suffixes, /* .SUFFIXES */ - Wait, /* .WAIT */ - Attribute /* Generic attribute */ + 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; -/* - * Other tokens - */ -#define LPAREN '(' -#define RPAREN ')' - +typedef List SearchPathList; +typedef ListNode SearchPathListNode; /* result data */ @@ -236,33 +203,29 @@ static GNode *mainNode; /* eval state */ -/* targets we're working on */ -static Lst targets; +/* 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 -/* command lines for targets */ -static Lst targCmds; +/* All shell commands for all targets, in no particular order and possibly + * with duplicates. Kept in a separate list since the commands from .USE or + * .USEBEFORE nodes are shared with other GNodes, thereby giving up the + * easily understandable ownership over the allocated strings. */ +static StringList *targCmds; #endif /* - * specType contains the SPECial TYPE of the current target. It is - * Not if the target is unspecial. If it *is* special, however, the children - * are linked as children of the parent but not vice versa. This variable is - * set in ParseDoDependency - */ -static ParseSpecial specType; - -/* * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER * seen, then set to each successive source on the line. */ -static GNode *predecessor; +static GNode *order_pred; /* parser state */ -/* true if currently in a dependency line or its commands */ -static Boolean inLine; - /* number of fatal errors */ static int fatals = 0; @@ -270,105 +233,125 @@ static int fatals = 0; * Variables for doing includes */ -/* current file being read */ -static IFile *curFile; +/* The include chain of makefiles. At the bottom is the top-level makefile + * from the command line, and on top of that, there are the included files or + * .for loops, up to and including the current file. + * + * This data could be used to print stack traces on parse errors. As of + * 2020-09-14, this is not done though. It seems quite simple to print the + * tuples (fname:lineno:fromForLoop), from top to bottom. This simple idea is + * made complicated by the fact that the .for loops also use this stack for + * storing information. + * + * The lineno fields of the IFiles with fromForLoop == TRUE look confusing, + * which is demonstrated by the test 'include-main.mk'. They seem sorted + * backwards since they tell the number of completely parsed lines, which for + * a .for loop is right after the terminating .endfor. To compensate for this + * confusion, there is another field first_lineno pointing at the start of the + * .for loop, 1-based for human consumption. + * + * To make the stack trace intuitive, the entry below the first .for loop must + * be ignored completely since neither its lineno nor its first_lineno is + * useful. Instead, the topmost of each chain of .for loop needs to be + * printed twice, once with its first_lineno and once with its lineno. + * + * As of 2020-10-28, using the above rules, the stack trace for the .info line + * in include-subsub.mk would be: + * + * includes[5]: include-subsub.mk:4 + * (lineno, from an .include) + * includes[4]: include-sub.mk:32 + * (lineno, from a .for loop below an .include) + * includes[4]: include-sub.mk:31 + * (first_lineno, from a .for loop, lineno == 32) + * includes[3]: include-sub.mk:30 + * (first_lineno, from a .for loop, lineno == 33) + * includes[2]: include-sub.mk:29 + * (first_lineno, from a .for loop, lineno == 34) + * includes[1]: include-sub.mk:35 + * (not printed since it is below a .for loop) + * includes[0]: include-main.mk:27 + */ +static Vector /* of IFile */ includes; -/* stack of IFiles generated by .includes */ -static Lst includes; +static IFile * +GetInclude(size_t i) +{ + return Vector_Get(&includes, i); +} -/* include paths (lists of directories) */ -Lst parseIncPath; /* dirs for "..." includes */ -Lst sysIncPath; /* dirs for <...> includes */ -Lst defIncPath; /* default for sysIncPath */ +/* The file that is currently being read. */ +static IFile * +CurFile(void) +{ + return GetInclude(includes.len - 1); +} + +/* include paths */ +SearchPath *parseIncPath; /* dirs for "..." includes */ +SearchPath *sysIncPath; /* dirs for <...> includes */ +SearchPath *defSysIncPath; /* default for sysIncPath */ /* parser tables */ /* * The parseKeywords table is searched using binary search when deciding * if a target or source is special. The 'spec' field is the ParseSpecial - * type of the keyword ("Not" if the keyword isn't special as a target) while + * 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 */ - int op; /* Operator when used as a source */ + const char *name; /* Name of keyword */ + ParseSpecial spec; /* Type when used as a target */ + GNodeType op; /* Operator when used as a source */ } parseKeywords[] = { -{ ".BEGIN", Begin, 0 }, -{ ".DEFAULT", Default, 0 }, -{ ".DELETE_ON_ERROR", DeleteOnError, 0 }, -{ ".END", End, 0 }, -{ ".ERROR", dotError, 0 }, -{ ".EXEC", Attribute, OP_EXEC }, -{ ".IGNORE", Ignore, OP_IGNORE }, -{ ".INCLUDES", Includes, 0 }, -{ ".INTERRUPT", Interrupt, 0 }, -{ ".INVISIBLE", Attribute, OP_INVISIBLE }, -{ ".JOIN", Attribute, OP_JOIN }, -{ ".LIBS", Libs, 0 }, -{ ".MADE", Attribute, OP_MADE }, -{ ".MAIN", Main, 0 }, -{ ".MAKE", Attribute, OP_MAKE }, -{ ".MAKEFLAGS", MFlags, 0 }, -{ ".META", Meta, OP_META }, -{ ".MFLAGS", MFlags, 0 }, -{ ".NOMETA", NoMeta, OP_NOMETA }, -{ ".NOMETA_CMP", NoMetaCmp, OP_NOMETA_CMP }, -{ ".NOPATH", NoPath, OP_NOPATH }, -{ ".NOTMAIN", Attribute, OP_NOTMAIN }, -{ ".NOTPARALLEL", NotParallel, 0 }, -{ ".NO_PARALLEL", NotParallel, 0 }, -{ ".NULL", Null, 0 }, -{ ".OBJDIR", ExObjdir, 0 }, -{ ".OPTIONAL", Attribute, OP_OPTIONAL }, -{ ".ORDER", Order, 0 }, -{ ".PARALLEL", Parallel, 0 }, -{ ".PATH", ExPath, 0 }, -{ ".PHONY", Phony, OP_PHONY }, + { ".BEGIN", SP_BEGIN, 0 }, + { ".DEFAULT", SP_DEFAULT, 0 }, + { ".DELETE_ON_ERROR", SP_DELETE_ON_ERROR, 0 }, + { ".END", SP_END, 0 }, + { ".ERROR", SP_ERROR, 0 }, + { ".EXEC", SP_ATTRIBUTE, OP_EXEC }, + { ".IGNORE", SP_IGNORE, OP_IGNORE }, + { ".INCLUDES", SP_INCLUDES, 0 }, + { ".INTERRUPT", SP_INTERRUPT, 0 }, + { ".INVISIBLE", SP_ATTRIBUTE, OP_INVISIBLE }, + { ".JOIN", SP_ATTRIBUTE, OP_JOIN }, + { ".LIBS", SP_LIBS, 0 }, + { ".MADE", SP_ATTRIBUTE, OP_MADE }, + { ".MAIN", SP_MAIN, 0 }, + { ".MAKE", SP_ATTRIBUTE, OP_MAKE }, + { ".MAKEFLAGS", SP_MFLAGS, 0 }, + { ".META", SP_META, OP_META }, + { ".MFLAGS", SP_MFLAGS, 0 }, + { ".NOMETA", SP_NOMETA, OP_NOMETA }, + { ".NOMETA_CMP", SP_NOMETA_CMP, OP_NOMETA_CMP }, + { ".NOPATH", SP_NOPATH, OP_NOPATH }, + { ".NOTMAIN", SP_ATTRIBUTE, OP_NOTMAIN }, + { ".NOTPARALLEL", SP_NOTPARALLEL, 0 }, + { ".NO_PARALLEL", SP_NOTPARALLEL, 0 }, + { ".NULL", SP_NULL, 0 }, + { ".OBJDIR", SP_OBJDIR, 0 }, + { ".OPTIONAL", SP_ATTRIBUTE, OP_OPTIONAL }, + { ".ORDER", SP_ORDER, 0 }, + { ".PARALLEL", SP_PARALLEL, 0 }, + { ".PATH", SP_PATH, 0 }, + { ".PHONY", SP_PHONY, OP_PHONY }, #ifdef POSIX -{ ".POSIX", Posix, 0 }, + { ".POSIX", SP_POSIX, 0 }, #endif -{ ".PRECIOUS", Precious, OP_PRECIOUS }, -{ ".RECURSIVE", Attribute, OP_MAKE }, -{ ".SHELL", ExShell, 0 }, -{ ".SILENT", Silent, OP_SILENT }, -{ ".SINGLESHELL", SingleShell, 0 }, -{ ".STALE", Stale, 0 }, -{ ".SUFFIXES", Suffixes, 0 }, -{ ".USE", Attribute, OP_USE }, -{ ".USEBEFORE", Attribute, OP_USEBEFORE }, -{ ".WAIT", Wait, 0 }, + { ".PRECIOUS", SP_PRECIOUS, OP_PRECIOUS }, + { ".RECURSIVE", SP_ATTRIBUTE, OP_MAKE }, + { ".SHELL", SP_SHELL, 0 }, + { ".SILENT", SP_SILENT, OP_SILENT }, + { ".SINGLESHELL", SP_SINGLESHELL, 0 }, + { ".STALE", SP_STALE, 0 }, + { ".SUFFIXES", SP_SUFFIXES, 0 }, + { ".USE", SP_ATTRIBUTE, OP_USE }, + { ".USEBEFORE", SP_ATTRIBUTE, OP_USEBEFORE }, + { ".WAIT", SP_WAIT, 0 }, }; -/* local functions */ - -static int ParseIsEscaped(const char *, const char *); -static void ParseErrorInternal(const char *, size_t, int, const char *, ...) - MAKE_ATTR_PRINTFLIKE(4,5); -static void ParseVErrorInternal(FILE *, const char *, size_t, int, const char *, va_list) - MAKE_ATTR_PRINTFLIKE(5, 0); -static int ParseFindKeyword(const char *); -static int ParseLinkSrc(void *, void *); -static int ParseDoOp(void *, void *); -static void ParseDoSrc(int, const char *); -static int ParseFindMain(void *, void *); -static int ParseAddDir(void *, void *); -static int ParseClearPath(void *, void *); -static void ParseDoDependency(char *); -static int ParseAddCmd(void *, void *); -static void ParseHasCommands(void *); -static void ParseDoInclude(char *); -static void ParseSetParseFile(const char *); -static void ParseSetIncludedFile(void); -#ifdef GMAKEEXPORT -static void ParseGmakeExport(char *); -#endif -static int ParseEOF(void); -static char *ParseReadLine(void); -static void ParseFinishLine(void); -static void ParseMark(GNode *); - /* file loader */ struct loadedfile { @@ -379,9 +362,6 @@ struct loadedfile { Boolean used; /* XXX: have we used the data yet */ }; -/* - * Constructor/destructor for loadedfile - */ static struct loadedfile * loadedfile_create(const char *path) { @@ -457,58 +437,28 @@ load_getsize(int fd, size_t *ret) return FALSE; } - *ret = (size_t) st.st_size; + *ret = (size_t)st.st_size; 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 and (to insure against fd leaks) - * assert that the caller passed in -1. - */ -static struct loadedfile * -loadfile(const char *path, int fd) -{ - struct loadedfile *lf; #ifdef HAVE_MMAP - static long pagesize = 0; -#endif - ssize_t result; - size_t bufpos; - - lf = loadedfile_create(path); - - if (path == NULL) { - assert(fd == -1); - fd = STDIN_FILENO; - } else { -#if 0 /* notyet */ - fd = open(path, O_RDONLY); - if (fd < 0) { - ... - Error("%s: %s", path, strerror(errno)); - exit(1); - } -#endif - } +static Boolean +loadedfile_mmap(struct loadedfile *lf, int fd) +{ + static unsigned long pagesize = 0; -#ifdef HAVE_MMAP if (load_getsize(fd, &lf->len)) { + /* found a size, try mmap */ #ifdef _SC_PAGESIZE if (pagesize == 0) - pagesize = sysconf(_SC_PAGESIZE); + pagesize = (unsigned long)sysconf(_SC_PAGESIZE); #endif - if (pagesize <= 0) { + if (pagesize == 0 || pagesize == (unsigned long)-1) { pagesize = 0x1000; } /* round size up to a page */ - lf->maplen = pagesize * ((lf->len + pagesize - 1)/pagesize); + lf->maplen = pagesize * ((lf->len + pagesize - 1) / pagesize); /* * XXX hack for dealing with empty files; remove when @@ -535,10 +485,51 @@ loadfile(const char *path, int fd) lf->maplen = 0; lf->buf = b; } - goto done; + return TRUE; + } + } + return FALSE; +} +#endif + +/* + * Read in a file. + * + * Until the path search logic can be moved under here instead of + * being in the caller in another source file, we need to have the fd + * passed in already open. Bleh. + * + * If the path is NULL use stdin and (to insure against fd leaks) + * assert that the caller passed in -1. + */ +static struct loadedfile * +loadfile(const char *path, int fd) +{ + struct loadedfile *lf; + ssize_t result; + size_t bufpos; + + lf = loadedfile_create(path); + + if (path == NULL) { + assert(fd == -1); + fd = STDIN_FILENO; + } else { +#if 0 /* notyet */ + fd = open(path, O_RDONLY); + if (fd < 0) { + ... + Error("%s: %s", path, strerror(errno)); + exit(1); } +#endif } + +#ifdef HAVE_MMAP + if (loadedfile_mmap(lf, fd)) + goto done; #endif + /* cannot mmap; load the traditional way */ lf->maplen = 0; @@ -566,7 +557,7 @@ loadfile(const char *path, int fd) if (result == 0) { break; } - bufpos += result; + bufpos += (size_t)result; } assert(bufpos <= lf->len); lf->len = bufpos; @@ -591,22 +582,11 @@ done: /* old code */ -/*- - *---------------------------------------------------------------------- - * ParseIsEscaped -- - * Check if the current character is escaped on the current line - * - * Results: - * 0 if the character is not backslash escaped, 1 otherwise - * - * Side Effects: - * None - *---------------------------------------------------------------------- - */ -static int +/* Check if the current character is escaped on the current line. */ +static Boolean ParseIsEscaped(const char *line, const char *c) { - int active = 0; + Boolean active = FALSE; for (;;) { if (line == c) return active; @@ -616,32 +596,29 @@ ParseIsEscaped(const char *line, const char *c) } } -/*- - *---------------------------------------------------------------------- - * ParseFindKeyword -- - * Look in the table of keywords for one matching the given string. - * - * Input: - * str String to find - * - * Results: - * The index of the keyword, or -1 if it isn't there. - * - * Side Effects: - * None - *---------------------------------------------------------------------- - */ +/* Add the filename and lineno to the GNode so that we remember where it + * was first defined. */ +static void +ParseMark(GNode *gn) +{ + IFile *curFile = CurFile(); + gn->fname = curFile->fname; + gn->lineno = curFile->lineno; +} + +/* Look in the table of keywords for one matching the given string. + * Return the index of the keyword, or -1 if it isn't there. */ static int ParseFindKeyword(const char *str) { - int start, end, cur; - int diff; + int start, end, cur; + int diff; start = 0; - end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1; + end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; do { - cur = start + ((end - start) / 2); + cur = start + (end - start) / 2; diff = strcmp(str, parseKeywords[cur].name); if (diff == 0) { @@ -655,89 +632,72 @@ ParseFindKeyword(const char *str) return -1; } -/*- - * ParseVErrorInternal -- - * Error message abort function for parsing. Prints out the context - * of the error (line number and file) as well as the message with - * two optional arguments. - * - * Results: - * None - * - * Side Effects: - * "fatals" is incremented if the level is PARSE_FATAL. - */ -/* VARARGS */ static void -ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type, - const char *fmt, va_list ap) +PrintLocation(FILE *f, const char *filename, size_t lineno) { - static Boolean fatal_warning_error_printed = FALSE; char dirbuf[MAXPATHLEN+1]; + const char *dir, *base; + void *dir_freeIt, *base_freeIt; - (void)fprintf(f, "%s: ", progname); + if (*filename == '/' || strcmp(filename, "(stdin)") == 0) { + (void)fprintf(f, "\"%s\" line %zu: ", filename, lineno); + return; + } - if (cfname != NULL) { - (void)fprintf(f, "\""); - if (*cfname != '/' && strcmp(cfname, "(stdin)") != 0) { - char *cp, *cp2; - const char *dir, *fname; - - /* - * Nothing is more annoying than not knowing - * which Makefile is the culprit; we try ${.PARSEDIR} - * and apply realpath(3) if not absolute. - */ - dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &cp); - if (dir == NULL) - dir = "."; - if (*dir != '/') { - dir = realpath(dir, dirbuf); - } - fname = Var_Value(".PARSEFILE", VAR_GLOBAL, &cp2); - if (fname == NULL) { - if ((fname = strrchr(cfname, '/'))) - fname++; - else - fname = cfname; - } - (void)fprintf(f, "%s/%s", dir, fname); - bmake_free(cp2); - bmake_free(cp); - } else - (void)fprintf(f, "%s", cfname); + /* Find out which makefile is the culprit. + * We try ${.PARSEDIR} and apply realpath(3) if not absolute. */ + + dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &dir_freeIt); + if (dir == NULL) + dir = "."; + if (*dir != '/') + dir = realpath(dir, dirbuf); - (void)fprintf(f, "\" line %d: ", (int)clineno); + base = Var_Value(".PARSEFILE", VAR_GLOBAL, &base_freeIt); + if (base == NULL) { + const char *slash = strrchr(filename, '/'); + base = slash != NULL ? slash + 1 : filename; } + + (void)fprintf(f, "\"%s/%s\" line %zu: ", dir, base, lineno); + bmake_free(base_freeIt); + bmake_free(dir_freeIt); +} + +/* Print a parse error message, including location information. + * + * Increment "fatals" if the level is PARSE_FATAL, and continue parsing + * until the end of the current top-level makefile, then exit (see + * Parse_File). */ +static void +ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, + ParseErrorLevel type, const char *fmt, va_list ap) +{ + static Boolean fatal_warning_error_printed = FALSE; + + (void)fprintf(f, "%s: ", progname); + + if (cfname != NULL) + PrintLocation(f, cfname, clineno); if (type == PARSE_WARNING) (void)fprintf(f, "warning: "); (void)vfprintf(f, fmt, ap); (void)fprintf(f, "\n"); (void)fflush(f); + if (type == PARSE_INFO) return; - if (type == PARSE_FATAL || parseWarnFatal) - fatals += 1; - if (parseWarnFatal && !fatal_warning_error_printed) { + if (type == PARSE_FATAL || opts.parseWarnFatal) + fatals++; + if (opts.parseWarnFatal && !fatal_warning_error_printed) { Error("parsing warnings being treated as errors"); fatal_warning_error_printed = TRUE; } } -/*- - * ParseErrorInternal -- - * Error function - * - * Results: - * None - * - * Side Effects: - * None - */ -/* VARARGS */ static void -ParseErrorInternal(const char *cfname, size_t clineno, int type, - const char *fmt, ...) +ParseErrorInternal(const char *cfname, size_t clineno, ParseErrorLevel type, + const char *fmt, ...) { va_list ap; @@ -746,38 +706,32 @@ ParseErrorInternal(const char *cfname, size_t clineno, int type, ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap); va_end(ap); - if (debug_file != stderr && debug_file != stdout) { + if (opts.debug_file != stderr && opts.debug_file != stdout) { va_start(ap, fmt); - ParseVErrorInternal(debug_file, cfname, clineno, type, fmt, ap); + ParseVErrorInternal(opts.debug_file, cfname, clineno, type, + fmt, ap); va_end(ap); } } -/*- - * Parse_Error -- - * External interface to ParseErrorInternal; uses the default filename - * Line number. +/* External interface to ParseErrorInternal; uses the default filename and + * line number. * - * Results: - * None - * - * Side Effects: - * None - */ -/* VARARGS */ + * Fmt is given without a trailing newline. */ void -Parse_Error(int type, const char *fmt, ...) +Parse_Error(ParseErrorLevel type, const char *fmt, ...) { va_list ap; const char *fname; size_t lineno; - if (curFile == NULL) { + if (includes.len == 0) { fname = NULL; lineno = 0; } else { + IFile *curFile = CurFile(); fname = curFile->fname; - lineno = curFile->lineno; + lineno = (size_t)curFile->lineno; } va_start(ap, fmt); @@ -785,136 +739,95 @@ Parse_Error(int type, const char *fmt, ...) ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); va_end(ap); - if (debug_file != stderr && debug_file != stdout) { + if (opts.debug_file != stderr && opts.debug_file != stdout) { va_start(ap, fmt); - ParseVErrorInternal(debug_file, fname, lineno, type, fmt, ap); + ParseVErrorInternal(opts.debug_file, fname, lineno, type, + fmt, ap); va_end(ap); } } -/* - * ParseMessage - * Parse a .info .warning or .error directive +/* Parse a .info .warning or .error directive. * - * The input is the line minus the ".". We substitute - * variables, print the message and exit(1) (for .error) or just print - * a warning if the directive is malformed. + * The input is the line minus the ".". We substitute variables, print the + * message and exit(1) (for .error) or just print a warning if the directive + * is malformed. */ static Boolean -ParseMessage(char *line) +ParseMessage(const char *directive) { - int mtype; + const char *p = directive; + int mtype = *p == 'i' ? PARSE_INFO : + *p == 'w' ? PARSE_WARNING : PARSE_FATAL; + char *arg; - switch(*line) { - case 'i': - mtype = PARSE_INFO; - break; - case 'w': - mtype = PARSE_WARNING; - break; - case 'e': - mtype = PARSE_FATAL; - break; - default: - Parse_Error(PARSE_WARNING, "invalid syntax: \".%s\"", line); - return FALSE; - } + while (ch_isalpha(*p)) + p++; + if (!ch_isspace(*p)) + return FALSE; /* missing argument */ - while (isalpha((unsigned char)*line)) - line++; - if (!isspace((unsigned char)*line)) - return FALSE; /* not for us */ - while (isspace((unsigned char)*line)) - line++; + cpp_skip_whitespace(&p); + (void)Var_Subst(p, VAR_CMDLINE, VARE_WANTRES, &arg); + /* TODO: handle errors */ - line = Var_Subst(line, VAR_CMD, VARE_WANTRES); - Parse_Error(mtype, "%s", line); - free(line); + Parse_Error(mtype, "%s", arg); + free(arg); if (mtype == PARSE_FATAL) { - /* Terminate immediately. */ + PrintOnError(NULL, NULL); exit(1); } return TRUE; } -/*- - *--------------------------------------------------------------------- - * ParseLinkSrc -- - * Link the parent node to its new child. Used in a Lst_ForEach by - * ParseDoDependency. If the specType isn't 'Not', the parent - * isn't linked as a parent of the child. - * - * Input: - * pgnp The parent node - * cgpn The child node - * - * Results: - * Always = 0 +/* Add the child to the parent's children. * - * Side Effects: - * New elements are added to the parents list of cgn and the - * children list of cgn. the unmade field of pgn is updated - * to reflect the additional child. - *--------------------------------------------------------------------- - */ -static int -ParseLinkSrc(void *pgnp, void *cgnp) + * 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) { - GNode *pgn = (GNode *)pgnp; - GNode *cgn = (GNode *)cgnp; - if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(pgn->cohorts)) - pgn = LstNode_Datum(Lst_Last(pgn->cohorts)); + pgn = pgn->cohorts->last->datum; + Lst_Append(pgn->children, cgn); - if (specType == Not) + pgn->unmade++; + + /* Special targets like .END don't need any children. */ + if (!isSpecial) Lst_Append(cgn->parents, pgn); - pgn->unmade += 1; + if (DEBUG(PARSE)) { - fprintf(debug_file, "# %s: added child %s - %s\n", __func__, - pgn->name, cgn->name); + debug_printf("# %s: added child %s - %s\n", + __func__, pgn->name, cgn->name); Targ_PrintNode(pgn, 0); Targ_PrintNode(cgn, 0); } - return 0; } -/*- - *--------------------------------------------------------------------- - * ParseDoOp -- - * Apply the parsed operator to the given target node. Used in a - * Lst_ForEach call by ParseDoDependency once all targets have - * been found and their operator parsed. If the previous and new - * operators are incompatible, a major error is taken. - * - * Input: - * gnp The node to which the operator is to be applied - * opp The operator to apply - * - * Results: - * Always 0 - * - * Side Effects: - * The type field of the node is altered to reflect any new bits in - * the op. - *--------------------------------------------------------------------- - */ -static int -ParseDoOp(void *gnp, void *opp) +/* Add the node to each target from the current dependency group. */ +static void +LinkToTargets(GNode *gn, Boolean isSpecial) +{ + GNodeListNode *ln; + for (ln = targets->first; ln != NULL; ln = ln->next) + LinkSource(ln->datum, gn, isSpecial); +} + +static Boolean +TryApplyDependencyOperator(GNode *gn, GNodeType op) { - GNode *gn = (GNode *)gnp; - int op = *(int *)opp; /* - * If the dependency mask of the operator and the node don't match and - * the node has actually had an operator applied to it before, and - * the operator actually has some dependency information in it, complain. + * If 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_NOP(gn->type) && !OP_NOP(op)) + 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 1; + return FALSE; } if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) { @@ -926,7 +839,7 @@ ParseDoOp(void *gnp, void *opp) * and the new instance is linked to all parents of the initial * instance. */ - GNode *cohort; + GNode *cohort; /* * Propagate copied bits to the initial node. They'll be propagated @@ -934,7 +847,7 @@ ParseDoOp(void *gnp, void *opp) */ gn->type |= op & ~OP_OPMASK; - cohort = Targ_FindNode(gn->name, TARG_NOHASH); + cohort = Targ_NewInternalNode(gn->name); if (doing_depend) ParseMark(cohort); /* @@ -947,9 +860,9 @@ ParseDoOp(void *gnp, void *opp) cohort->type = op | OP_INVISIBLE; Lst_Append(gn->cohorts, cohort); cohort->centurion = gn; - gn->unmade_cohorts += 1; + gn->unmade_cohorts++; snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", - gn->unmade_cohorts); + (unsigned int)gn->unmade_cohorts % 1000000); } else { /* * We don't want to nuke any previous flags (whatever they were) so we @@ -958,47 +871,34 @@ ParseDoOp(void *gnp, void *opp) gn->type |= op; } - return 0; + return TRUE; } -/*- - *--------------------------------------------------------------------- - * ParseDoSrc -- - * Given the name of a source, figure out if it is an attribute - * and apply it to the targets if it is. Else decide if there is - * some attribute which should be applied *to* the source because - * of some special target and apply it if so. Otherwise, make the - * source be a child of the targets in the list 'targets' - * - * Input: - * tOp operator (if any) from special targets - * src name of the source to handle - * - * Results: - * None - * - * Side Effects: - * Operator bits may be added to the list of targets or to the source. - * The targets may have a new source added to their lists of children. - *--------------------------------------------------------------------- - */ static void -ParseDoSrc(int tOp, const char *src) +ApplyDependencyOperator(GNodeType op) +{ + GNodeListNode *ln; + for (ln = targets->first; ln != NULL; ln = ln->next) + if (!TryApplyDependencyOperator(ln->datum, op)) + break; +} + +static Boolean +ParseDoSrcKeyword(const char *src, ParseSpecial specType) { - GNode *gn = NULL; static int wait_number = 0; char wait_src[16]; + GNode *gn; - if (*src == '.' && isupper ((unsigned char)src[1])) { + if (*src == '.' && ch_isupper(src[1])) { int keywd = ParseFindKeyword(src); if (keywd != -1) { int op = parseKeywords[keywd].op; if (op != 0) { - if (targets != NULL) - Lst_ForEach(targets, ParseDoOp, &op); - return; + ApplyDependencyOperator(op); + return TRUE; } - if (parseKeywords[keywd].spec == Wait) { + if (parseKeywords[keywd].spec == SP_WAIT) { /* * We add a .WAIT node in the dependency list. * After any dynamic dependencies (and filename globbing) @@ -1009,257 +909,591 @@ ParseDoSrc(int tOp, const char *src) * We give each .WAIT node a unique name (mainly for diag). */ snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number); - gn = Targ_FindNode(wait_src, TARG_NOHASH); + gn = Targ_NewInternalNode(wait_src); if (doing_depend) ParseMark(gn); gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN; - if (targets != NULL) - Lst_ForEach(targets, ParseLinkSrc, gn); - return; + LinkToTargets(gn, specType != SP_NOT); + return TRUE; } } } + return FALSE; +} - switch (specType) { - case Main: - /* - * If we have noted the existence of a .MAIN, it means we need - * to add the sources of said target to the list of things - * to create. The string 'src' is likely to be free, so we - * must make a new copy of it. Note that this will only be - * invoked if the user didn't specify a target on the command - * line. This is to allow #ifmake's to succeed, or something... - */ - Lst_Append(create, bmake_strdup(src)); - /* - * Add the name to the .TARGETS variable as well, so the user can - * employ that, if desired. - */ - Var_Append(".TARGETS", src, VAR_GLOBAL); +static void +ParseDoSrcMain(const char *src) +{ + /* + * If we have noted the existence of a .MAIN, it means we need + * to add the sources of said target to the list of things + * to create. The string 'src' is likely to be free, so we + * must make a new copy of it. Note that this will only be + * invoked if the user didn't specify a target on the command + * line. This is to allow #ifmake's to succeed, or something... + */ + Lst_Append(opts.create, bmake_strdup(src)); + /* + * Add the name to the .TARGETS variable as well, so the user can + * employ that, if desired. + */ + Var_Append(".TARGETS", src, VAR_GLOBAL); +} + +static void +ParseDoSrcOrder(const char *src) +{ + GNode *gn; + /* + * Create proper predecessor/successor links between the previous + * source and the current one. + */ + gn = Targ_GetNode(src); + if (doing_depend) + ParseMark(gn); + if (order_pred != NULL) { + Lst_Append(order_pred->order_succ, gn); + Lst_Append(gn->order_pred, order_pred); + if (DEBUG(PARSE)) { + debug_printf("# %s: added Order dependency %s - %s\n", + __func__, order_pred->name, gn->name); + Targ_PrintNode(order_pred, 0); + Targ_PrintNode(gn, 0); + } + } + /* + * The current source now becomes the predecessor for the next one. + */ + order_pred = gn; +} + +static void +ParseDoSrcOther(const char *src, GNodeType tOp, ParseSpecial specType) +{ + GNode *gn; + + /* + * If the source is not an attribute, we need to find/create + * a node for it. After that we can apply any operator to it + * from a special target or link it to its parents, as + * appropriate. + * + * In the case of a source that was the object of a :: operator, + * the attribute is applied to all of its instances (as kept in + * the 'cohorts' list of the node) or all the cohorts are linked + * to all the targets. + */ + + /* Find/create the 'src' node and attach to all targets */ + gn = Targ_GetNode(src); + if (doing_depend) + ParseMark(gn); + if (tOp) { + 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 +ParseDoSrc(GNodeType tOp, const char *src, ParseSpecial specType) +{ + if (ParseDoSrcKeyword(src, specType)) return; - case Order: - /* - * Create proper predecessor/successor links between the previous - * source and the current one. - */ - gn = Targ_FindNode(src, TARG_CREATE); + if (specType == SP_MAIN) + ParseDoSrcMain(src); + else if (specType == SP_ORDER) + ParseDoSrcOrder(src); + else + ParseDoSrcOther(src, tOp, specType); +} + +/* If we have yet to decide on a main target to make, in the absence of any + * user input, we want the first target on the first dependency line that is + * actually a real target (i.e. isn't a .USE or .EXEC rule) to be made. */ +static void +FindMainTarget(void) +{ + GNodeListNode *ln; + + if (mainNode != NULL) + return; + + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + if (!(gn->type & OP_NOTARGET)) { + mainNode = gn; + Targ_SetMain(gn); + return; + } + } +} + +/* + * We got to the end of the line while we were still looking at targets. + * + * Ending a dependency line without an operator is a Bozo no-no. As a + * heuristic, this is also often triggered by undetected conflicts from + * cvs/rcs merges. + */ +static void +ParseErrorNoDependency(const char *lstart) +{ + if ((strncmp(lstart, "<<<<<<", 6) == 0) || + (strncmp(lstart, "======", 6) == 0) || + (strncmp(lstart, ">>>>>>", 6) == 0)) + Parse_Error(PARSE_FATAL, + "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); + else if (lstart[0] == '.') { + const char *dirstart = lstart + 1; + const char *dirend; + cpp_skip_whitespace(&dirstart); + dirend = dirstart; + while (ch_isalnum(*dirend) || *dirend == '-') + dirend++; + Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"", + (int)(dirend - dirstart), dirstart); + } else + Parse_Error(PARSE_FATAL, "Need an operator"); +} + +static void +ParseDependencyTargetWord(/*const*/ char **pp, const char *lstart) +{ + /*const*/ char *cp = *pp; + + while (*cp != '\0') { + if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' || *cp == '(') && + !ParseIsEscaped(lstart, cp)) + break; + + if (*cp == '$') { + /* + * Must be a dynamic source (would have been expanded + * otherwise), so call the Var module to parse the puppy + * so we can safely advance beyond it...There should be + * no errors in this, as they would have been discovered + * in the initial Var_Subst and we wouldn't be here. + */ + const char *nested_p = cp; + const char *nested_val; + void *freeIt; + + (void)Var_Parse(&nested_p, VAR_CMDLINE, VARE_UNDEFERR|VARE_WANTRES, + &nested_val, &freeIt); + /* TODO: handle errors */ + free(freeIt); + cp += nested_p - cp; + } else + cp++; + } + + *pp = cp; +} + +/* + * Certain special targets have special semantics: + * .PATH Have to set the dirSearchPath + * variable too + * .MAIN Its sources are only used if + * nothing has been specified to + * create. + * .DEFAULT Need to create a node to hang + * commands on, but we don't want + * it in the graph, nor do we want + * it to be the Main Target, so we + * create it, set OP_NOTMAIN and + * add it to the list, setting + * DEFAULT to the new node for + * later use. We claim the node is + * A transformation rule to make + * life easier later, when we'll + * use Make_HandleUse to actually + * apply the .DEFAULT commands. + * .PHONY The list of targets + * .NOPATH Don't search for file in the path + * .STALE + * .BEGIN + * .END + * .ERROR + * .DELETE_ON_ERROR + * .INTERRUPT Are not to be considered the + * main target. + * .NOTPARALLEL Make only one target at a time. + * .SINGLESHELL Create a shell for each command. + * .ORDER Must set initial predecessor to NULL + */ +static void +ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, + const char *line, + 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: + if (!Lst_IsEmpty(opts.create)) { + *inout_specType = SP_NOT; + } + break; + case SP_BEGIN: + case SP_END: + case SP_STALE: + case SP_ERROR: + case SP_INTERRUPT: { + GNode *gn = Targ_GetNode(line); if (doing_depend) ParseMark(gn); - if (predecessor != NULL) { - Lst_Append(predecessor->order_succ, gn); - Lst_Append(gn->order_pred, predecessor); - if (DEBUG(PARSE)) { - fprintf(debug_file, "# %s: added Order dependency %s - %s\n", - __func__, predecessor->name, gn->name); - Targ_PrintNode(predecessor, 0); - Targ_PrintNode(gn, 0); - } + gn->type |= OP_NOTMAIN|OP_SPECIAL; + Lst_Append(targets, gn); + break; + } + case SP_DEFAULT: { + GNode *gn = Targ_NewGN(".DEFAULT"); + gn->type |= OP_NOTMAIN|OP_TRANSFORM; + Lst_Append(targets, gn); + DEFAULT = gn; + break; + } + case SP_DELETE_ON_ERROR: + deleteOnError = TRUE; + break; + case SP_NOTPARALLEL: + opts.maxJobs = 1; + break; + case SP_SINGLESHELL: + opts.compatMake = TRUE; + break; + case SP_ORDER: + order_pred = NULL; + break; + default: + break; + } +} + +/* + * .PATH<suffix> has to be handled specially. + * Call on the suffix module to give us a path to modify. + */ +static Boolean +ParseDoDependencyTargetPath(const char *line, SearchPathList **inout_paths) +{ + SearchPath *path; + + path = Suff_GetPath(&line[5]); + if (path == NULL) { + Parse_Error(PARSE_FATAL, + "Suffix '%s' not defined (yet)", + &line[5]); + return FALSE; + } else { + if (*inout_paths == NULL) { + *inout_paths = Lst_New(); } + Lst_Append(*inout_paths, path); + } + return TRUE; +} + +/* + * See if it's a special target and if so set specType to match it. + */ +static Boolean +ParseDoDependencyTarget(const char *line, ParseSpecial *inout_specType, + GNodeType *out_tOp, SearchPathList **inout_paths) +{ + int keywd; + + if (!(*line == '.' && ch_isupper(line[1]))) + return TRUE; + + /* + * See if the target is a special target that must have it + * or its sources handled specially. + */ + keywd = ParseFindKeyword(line); + if (keywd != -1) { + if (*inout_specType == SP_PATH && parseKeywords[keywd].spec != SP_PATH) { + Parse_Error(PARSE_FATAL, "Mismatched special targets"); + return FALSE; + } + + *inout_specType = parseKeywords[keywd].spec; + *out_tOp = parseKeywords[keywd].op; + + ParseDoDependencyTargetSpecial(inout_specType, line, inout_paths); + + } else if (strncmp(line, ".PATH", 5) == 0) { + *inout_specType = SP_PATH; + if (!ParseDoDependencyTargetPath(line, inout_paths)) + return FALSE; + } + return TRUE; +} + +static void +ParseDoDependencyTargetMundane(char *line, StringList *curTargs) +{ + if (Dir_HasWildcards(line)) { /* - * The current source now becomes the predecessor for the next one. + * 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... */ - predecessor = gn; - break; + SearchPath *emptyPath = Lst_New(); - default: + Dir_Expand(line, emptyPath, curTargs); + + Lst_Destroy(emptyPath, Dir_Destroy); + } else { /* - * If the source is not an attribute, we need to find/create - * a node for it. After that we can apply any operator to it - * from a special target or link it to its parents, as - * appropriate. - * - * In the case of a source that was the object of a :: operator, - * the attribute is applied to all of its instances (as kept in - * the 'cohorts' list of the node) or all the cohorts are linked - * to all the targets. + * No wildcards, but we want to avoid code duplication, + * so create a list with the word on it. */ + Lst_Append(curTargs, line); + } + + /* Apply the targets. */ - /* Find/create the 'src' node and attach to all targets */ - gn = Targ_FindNode(src, TARG_CREATE); + 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); - if (tOp) { - gn->type |= tOp; - } else { - if (targets != NULL) - Lst_ForEach(targets, ParseLinkSrc, gn); + + Lst_Append(targets, gn); + } +} + +static void +ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart) +{ + Boolean warning = FALSE; + char *cp = *pp; + + while (*cp && (ParseIsEscaped(lstart, cp) || + (*cp != '!' && *cp != ':'))) { + if (ParseIsEscaped(lstart, cp) || + (*cp != ' ' && *cp != '\t')) { + warning = TRUE; } + cp++; + } + if (warning) { + Parse_Error(PARSE_WARNING, "Extra target ignored"); + } + *pp = cp; +} + +static void +ParseDoDependencyCheckSpec(ParseSpecial specType) +{ + switch (specType) { + default: + Parse_Error(PARSE_WARNING, + "Special and mundane targets don't mix. Mundane ones ignored"); + break; + case SP_DEFAULT: + case SP_STALE: + case SP_BEGIN: + case SP_END: + case SP_ERROR: + case SP_INTERRUPT: + /* + * These four create nodes on which to hang commands, so + * targets shouldn't be empty... + */ + case SP_NOT: + /* + * Nothing special here -- targets can be empty if it wants. + */ break; } } -/*- - *----------------------------------------------------------------------- - * ParseFindMain -- - * Find a real target in the list and set it to be the main one. - * Called by ParseDoDependency when a main target hasn't been found - * yet. - * - * Input: - * gnp Node to examine - * - * Results: - * 0 if main not found yet, 1 if it is. - * - * Side Effects: - * mainNode is changed and Targ_SetMain is called. - * - *----------------------------------------------------------------------- - */ -static int -ParseFindMain(void *gnp, void *dummy MAKE_ATTR_UNUSED) +static Boolean +ParseDoDependencyParseOp(char **pp, const char *lstart, GNodeType *out_op) { - GNode *gn = (GNode *)gnp; - if (!(gn->type & OP_NOTARGET)) { - mainNode = gn; - Targ_SetMain(gn); - return 1; - } else { - return 0; + const char *cp = *pp; + + if (*cp == '!') { + *out_op = OP_FORCE; + (*pp)++; + return TRUE; + } + + if (*cp == ':') { + if (cp[1] == ':') { + *out_op = OP_DOUBLEDEP; + (*pp) += 2; + } else { + *out_op = OP_DEPENDS; + (*pp)++; + } + return TRUE; + } + + { + const char *msg = lstart[0] == '.' ? "Unknown directive" + : "Missing dependency operator"; + Parse_Error(PARSE_FATAL, "%s", msg); + return FALSE; } } -/*- - *----------------------------------------------------------------------- - * ParseAddDir -- - * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going - * - * Results: - * === 0 - * - * Side Effects: - * See Dir_AddDir. - * - *----------------------------------------------------------------------- - */ -static int -ParseAddDir(void *path, void *name) +static void +ClearPaths(SearchPathList *paths) { - (void)Dir_AddDir((Lst) path, (char *)name); - return 0; + if (paths != NULL) { + SearchPathListNode *ln; + for (ln = paths->first; ln != NULL; ln = ln->next) + Dir_ClearPath(ln->datum); + } + + Dir_SetPATH(); } -/*- - *----------------------------------------------------------------------- - * ParseClearPath -- - * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going - * - * Results: - * === 0 - * - * Side Effects: - * See Dir_ClearPath - * - *----------------------------------------------------------------------- - */ -static int -ParseClearPath(void *path, void *dummy MAKE_ATTR_UNUSED) +static void +ParseDoDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) { - Dir_ClearPath((Lst) path); - return 0; + switch (specType) { + case SP_SUFFIXES: + Suff_ClearSuffixes(); + break; + case SP_PRECIOUS: + allPrecious = TRUE; + break; + case SP_IGNORE: + opts.ignoreErrors = TRUE; + break; + case SP_SILENT: + opts.beSilent = TRUE; + break; + case SP_PATH: + ClearPaths(paths); + break; +#ifdef POSIX + case SP_POSIX: + Var_Set("%POSIX", "1003.2", VAR_GLOBAL); + break; +#endif + default: + break; + } } -/*- - *--------------------------------------------------------------------- - * ParseDoDependency -- - * Parse the dependency line in line. +static void +AddToPaths(const char *dir, SearchPathList *paths) +{ + if (paths != NULL) { + SearchPathListNode *ln; + for (ln = paths->first; ln != NULL; ln = ln->next) + (void)Dir_AddDir(ln->datum, dir); + } +} + +/* + * If the target was one that doesn't take files as its sources + * but takes something like suffixes, we take each + * space-separated word on the line as a something and deal + * with it accordingly. * - * Input: - * line the line to parse + * 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. * - * Results: - * None + * If the target was a .PATH, we add the source as a directory + * to search on the search path. * - * Side Effects: - * The nodes of the sources are linked as children to the nodes of the - * targets. Some nodes may be created. + * 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. * - * We parse a dependency line by first extracting words from the line and - * finding nodes in the list of all targets with that name. This is done - * until a character is encountered which is an operator character. Currently - * these are only ! and :. At this point the operator is parsed and the - * pointer into the line advanced until the first source is encountered. - * The parsed operator is applied to each node in the 'targets' list, - * which is where the nodes found for the targets are kept, by means of - * the ParseDoOp function. - * The sources are read in much the same way as the targets were except - * that now they are expanded using the wildcarding scheme of the C-Shell - * and all instances of the resulting words in the list of all targets - * are found. Each of the resulting nodes is then linked to each of the - * targets as one of its children. - * Certain targets are handled specially. These are the ones detailed - * by the specType variable. - * The storing of transformation rules is also taken care of here. - * A target is recognized as a transformation rule by calling - * Suff_IsTransform. If it is a transformation rule, its node is gotten - * from the suffix module via Suff_AddTransform rather than the standard - * Targ_FindNode in the target module. - *--------------------------------------------------------------------- + * 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 -ParseDoDependency(char *line) +ParseDoDependencySourceSpecial(ParseSpecial specType, char *word, + SearchPathList *paths) { - char *cp; /* our current position */ - GNode *gn = NULL; /* a general purpose temporary node */ - int op; /* the operator on the line */ - char savec; /* a place to save a character */ - Lst paths; /* List of search paths to alter when parsing - * a list of .PATH targets */ - int tOp; /* operator from special target */ - Lst sources; /* list of archive source names after - * expansion */ - Lst curTargs; /* list of target names to be found and added - * to the targets list */ - char *lstart = line; - - if (DEBUG(PARSE)) - fprintf(debug_file, "ParseDoDependency(%s)\n", line); - tOp = 0; - - specType = Not; - paths = NULL; - - curTargs = Lst_Init(); + switch (specType) { + case SP_SUFFIXES: + Suff_AddSuffix(word, &mainNode); + break; + case SP_PATH: + AddToPaths(word, paths); + break; + case SP_INCLUDES: + Suff_AddInclude(word); + break; + case SP_LIBS: + Suff_AddLib(word); + break; + case SP_NULL: + Suff_SetNull(word); + break; + case SP_OBJDIR: + Main_SetObjdir("%s", word); + break; + default: + break; + } +} - /* - * First, grind through the targets. - */ +static Boolean +ParseDoDependencyTargets(char **inout_cp, + char **inout_line, + const char *lstart, + ParseSpecial *inout_specType, + GNodeType *inout_tOp, + SearchPathList **inout_paths, + StringList *curTargs) +{ + char *cp = *inout_cp; + char *line = *inout_line; + char savec; - do { + 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. */ - for (cp = line; *cp && (ParseIsEscaped(lstart, cp) || - !(isspace((unsigned char)*cp) || - *cp == '!' || *cp == ':' || *cp == LPAREN)); - cp++) { - if (*cp == '$') { - /* - * Must be a dynamic source (would have been expanded - * otherwise), so call the Var module to parse the puppy - * so we can safely advance beyond it...There should be - * no errors in this, as they would have been discovered - * in the initial Var_Subst and we wouldn't be here. - */ - int length; - void *freeIt; - - (void)Var_Parse(cp, VAR_CMD, VARE_UNDEFERR|VARE_WANTRES, - &length, &freeIt); - free(freeIt); - cp += length - 1; - } - } + cp = line; + ParseDependencyTargetWord(&cp, lstart); /* * If the word is followed by a left parenthesis, it's the * name of an object file inside an archive (ar file). */ - if (!ParseIsEscaped(lstart, cp) && *cp == LPAREN) { + 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 @@ -1270,10 +1504,10 @@ ParseDoDependency(char *line) * went well and FALSE if there was an error in the * specification. On error, line should remain untouched. */ - if (!Arch_ParseArchive(&line, targets, VAR_CMD)) { + if (!Arch_ParseArchive(&line, targets, VAR_CMDLINE)) { Parse_Error(PARSE_FATAL, - "Error in archive specification: \"%s\"", line); - goto out; + "Error in archive specification: \"%s\"", line); + return FALSE; } else { /* Done with this word; on to the next. */ cp = line; @@ -1282,198 +1516,25 @@ ParseDoDependency(char *line) } if (!*cp) { - /* - * We got to the end of the line while we were still - * looking at targets. - * - * Ending a dependency line without an operator is a Bozo - * no-no. As a heuristic, this is also often triggered by - * undetected conflicts from cvs/rcs merges. - */ - if ((strncmp(line, "<<<<<<", 6) == 0) || - (strncmp(line, "======", 6) == 0) || - (strncmp(line, ">>>>>>", 6) == 0)) - Parse_Error(PARSE_FATAL, - "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); - else if (lstart[0] == '.') { - const char *dirstart = lstart + 1; - const char *dirend; - while (isspace((unsigned char)*dirstart)) - dirstart++; - dirend = dirstart; - while (isalnum((unsigned char)*dirend) || *dirend == '-') - dirend++; - Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"", - (int)(dirend - dirstart), dirstart); - } else - Parse_Error(PARSE_FATAL, "Need an operator"); - goto out; + ParseErrorNoDependency(lstart); + return FALSE; } /* Insert a null terminator. */ savec = *cp; *cp = '\0'; - /* - * Got the word. See if it's a special target and if so set - * specType to match it. - */ - if (*line == '.' && isupper ((unsigned char)line[1])) { - /* - * See if the target is a special target that must have it - * or its sources handled specially. - */ - int keywd = ParseFindKeyword(line); - if (keywd != -1) { - if (specType == ExPath && parseKeywords[keywd].spec != ExPath) { - Parse_Error(PARSE_FATAL, "Mismatched special targets"); - goto out; - } - - specType = parseKeywords[keywd].spec; - tOp = parseKeywords[keywd].op; - - /* - * Certain special targets have special semantics: - * .PATH Have to set the dirSearchPath - * variable too - * .MAIN Its sources are only used if - * nothing has been specified to - * create. - * .DEFAULT Need to create a node to hang - * commands on, but we don't want - * it in the graph, nor do we want - * it to be the Main Target, so we - * create it, set OP_NOTMAIN and - * add it to the list, setting - * DEFAULT to the new node for - * later use. We claim the node is - * A transformation rule to make - * life easier later, when we'll - * use Make_HandleUse to actually - * apply the .DEFAULT commands. - * .PHONY The list of targets - * .NOPATH Don't search for file in the path - * .STALE - * .BEGIN - * .END - * .ERROR - * .DELETE_ON_ERROR - * .INTERRUPT Are not to be considered the - * main target. - * .NOTPARALLEL Make only one target at a time. - * .SINGLESHELL Create a shell for each command. - * .ORDER Must set initial predecessor to NULL - */ - switch (specType) { - case ExPath: - if (paths == NULL) { - paths = Lst_Init(); - } - Lst_Append(paths, dirSearchPath); - break; - case Main: - if (!Lst_IsEmpty(create)) { - specType = Not; - } - break; - case Begin: - case End: - case Stale: - case dotError: - case Interrupt: - gn = Targ_FindNode(line, TARG_CREATE); - if (doing_depend) - ParseMark(gn); - gn->type |= OP_NOTMAIN|OP_SPECIAL; - Lst_Append(targets, gn); - break; - case Default: - gn = Targ_NewGN(".DEFAULT"); - gn->type |= (OP_NOTMAIN|OP_TRANSFORM); - Lst_Append(targets, gn); - DEFAULT = gn; - break; - case DeleteOnError: - deleteOnError = TRUE; - break; - case NotParallel: - maxJobs = 1; - break; - case SingleShell: - compatMake = TRUE; - break; - case Order: - predecessor = NULL; - break; - default: - break; - } - } else if (strncmp(line, ".PATH", 5) == 0) { - /* - * .PATH<suffix> has to be handled specially. - * Call on the suffix module to give us a path to - * modify. - */ - Lst path; - - specType = ExPath; - path = Suff_GetPath(&line[5]); - if (path == NULL) { - Parse_Error(PARSE_FATAL, - "Suffix '%s' not defined (yet)", - &line[5]); - goto out; - } else { - if (paths == NULL) { - paths = Lst_Init(); - } - Lst_Append(paths, path); - } - } - } + if (!ParseDoDependencyTarget(line, inout_specType, inout_tOp, + inout_paths)) + return FALSE; /* * Have word in line. Get or create its node and stick it at * the end of the targets list */ - if (specType == Not && *line != '\0') { - if (Dir_HasWildcards(line)) { - /* - * Targets are to be sought only in the current directory, - * so create an empty path for the thing. Note we need to - * use Dir_Destroy in the destruction of the path as the - * Dir module could have added a directory to the path... - */ - Lst emptyPath = Lst_Init(); - - Dir_Expand(line, emptyPath, curTargs); - - Lst_Destroy(emptyPath, Dir_Destroy); - } else { - /* - * No wildcards, but we want to avoid code duplication, - * so create a list with the word on it. - */ - Lst_Append(curTargs, line); - } - - /* Apply the targets. */ - - while(!Lst_IsEmpty(curTargs)) { - char *targName = Lst_Dequeue(curTargs); - - if (!Suff_IsTransform (targName)) { - gn = Targ_FindNode(targName, TARG_CREATE); - } else { - gn = Suff_AddTransform(targName); - } - if (doing_depend) - ParseMark(gn); - - Lst_Append(targets, gn); - } - } else if (specType == ExPath && *line != '.' && *line != '\0') { + if (*inout_specType == SP_NOT && *line != '\0') { + ParseDoDependencyTargetMundane(line, curTargs); + } else if (*inout_specType == SP_PATH && *line != '.' && *line != '\0') { Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); } @@ -1484,28 +1545,150 @@ ParseDoDependency(char *line) * If it is a special type and not .PATH, it's the only target we * allow on this line... */ - if (specType != Not && specType != ExPath) { - Boolean warning = FALSE; - - while (*cp && (ParseIsEscaped(lstart, cp) || - (*cp != '!' && *cp != ':'))) { - if (ParseIsEscaped(lstart, cp) || - (*cp != ' ' && *cp != '\t')) { - warning = TRUE; - } - cp++; + if (*inout_specType != SP_NOT && *inout_specType != SP_PATH) { + ParseDoDependencyTargetExtraWarn(&cp, lstart); + } else { + pp_skip_whitespace(&cp); + } + line = cp; + if (*line == '\0') + break; + if ((*line == '!' || *line == ':') && !ParseIsEscaped(lstart, line)) + break; + } + + *inout_cp = cp; + *inout_line = line; + return TRUE; +} + +static void +ParseDoDependencySourcesSpecial(char *start, char *end, + ParseSpecial specType, SearchPathList *paths) +{ + char savec; + + while (*start) { + while (*end && !ch_isspace(*end)) + end++; + savec = *end; + *end = '\0'; + ParseDoDependencySourceSpecial(specType, start, paths); + *end = savec; + if (savec != '\0') + end++; + pp_skip_whitespace(&end); + start = end; + } +} + +static Boolean +ParseDoDependencySourcesMundane(char *start, char *end, + ParseSpecial specType, GNodeType tOp) +{ + while (*start) { + /* + * The targets take real sources, so we must beware of archive + * specifications (i.e. things with left parentheses in them) + * and handle them accordingly. + */ + for (; *end && !ch_isspace(*end); end++) { + if (*end == '(' && end > start && end[-1] != '$') { + /* + * Only stop for a left parenthesis if it isn't at the + * start of a word (that'll be for variable changes + * later) and isn't preceded by a dollar sign (a dynamic + * source). + */ + break; + } + } + + if (*end == '(') { + GNodeList *sources = Lst_New(); + if (!Arch_ParseArchive(&start, sources, VAR_CMDLINE)) { + Parse_Error(PARSE_FATAL, + "Error in source archive spec \"%s\"", start); + return FALSE; } - if (warning) { - Parse_Error(PARSE_WARNING, "Extra target ignored"); + + while (!Lst_IsEmpty(sources)) { + GNode *gn = Lst_Dequeue(sources); + ParseDoSrc(tOp, gn->name, specType); } + Lst_Free(sources); + end = start; } else { - while (*cp && isspace ((unsigned char)*cp)) { - cp++; + if (*end) { + *end = '\0'; + end++; } + + ParseDoSrc(tOp, start, specType); } - line = cp; - } while (*line && (ParseIsEscaped(lstart, line) || - (*line != '!' && *line != ':'))); + pp_skip_whitespace(&end); + start = end; + } + return TRUE; +} + +/* Parse a dependency line consisting of targets, followed by a dependency + * operator, optionally followed by sources. + * + * The nodes of the sources are linked as children to the nodes of the + * targets. Nodes are created as necessary. + * + * The operator is applied to each node in the global 'targets' list, + * which is where the nodes found for the targets are kept, by means of + * the ParseDoOp function. + * + * The sources are parsed in much the same way as the targets, except + * that they are expanded using the wildcarding scheme of the C-Shell, + * and all instances of the resulting words in the list of all targets + * are found. Each of the resulting nodes is then linked to each of the + * targets as one of its children. + * + * 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. + */ +static void +ParseDoDependency(char *line) +{ + char *cp; /* our current position */ + GNodeType op; /* the operator on the line */ + SearchPathList *paths; /* search paths to alter when parsing + * a list of .PATH targets */ + int tOp; /* operator from special target */ + StringList *curTargs; /* target names to be found and added + * to the targets list */ + char *lstart = line; + + /* + * specType contains the SPECial TYPE of the current target. It is SP_NOT + * if the target is unspecial. If it *is* special, however, the children + * are linked as children of the parent but not vice versa. + */ + ParseSpecial specType = SP_NOT; + + DEBUG1(PARSE, "ParseDoDependency(%s)\n", line); + tOp = 0; + + paths = NULL; + + curTargs = Lst_New(); + + /* + * First, grind through the targets. + */ + if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp, &paths, + curTargs)) + goto out; /* * Don't need the list of target names anymore... @@ -1513,58 +1696,21 @@ ParseDoDependency(char *line) Lst_Free(curTargs); curTargs = NULL; - if (targets != NULL && !Lst_IsEmpty(targets)) { - switch(specType) { - default: - Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored"); - break; - case Default: - case Stale: - case Begin: - case End: - case dotError: - case Interrupt: - /* - * These four create nodes on which to hang commands, so - * targets shouldn't be empty... - */ - case Not: - /* - * Nothing special here -- targets can be empty if it wants. - */ - break; - } - } + if (!Lst_IsEmpty(targets)) + ParseDoDependencyCheckSpec(specType); /* - * Have now parsed all the target names. Must parse the operator next. The - * result is left in op . + * Have now parsed all the target names. Must parse the operator next. */ - if (*cp == '!') { - op = OP_FORCE; - } else if (*cp == ':') { - if (cp[1] == ':') { - op = OP_DOUBLEDEP; - cp++; - } else { - op = OP_DEPENDS; - } - } else { - Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive" - : "Missing dependency operator"); + if (!ParseDoDependencyParseOp(&cp, lstart, &op)) goto out; - } - - /* Advance beyond the operator */ - cp++; /* * Apply the operator to the target. This is how we remember which * operator a target was defined with. It fails if the operator * used isn't consistent across all references. */ - if (targets != NULL) - Lst_ForEach(targets, ParseDoOp, &op); + ApplyDependencyOperator(op); /* * Onward to the sources. @@ -1572,9 +1718,7 @@ ParseDoDependency(char *line) * LINE will now point to the first source word, if any, or the * end of the string if not. */ - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } + pp_skip_whitespace(&cp); line = cp; /* @@ -1587,33 +1731,8 @@ ParseDoDependency(char *line) * a .PATH removes all directories from the search path(s). */ if (!*line) { - switch (specType) { - case Suffixes: - Suff_ClearSuffixes(); - break; - case Precious: - allPrecious = TRUE; - break; - case Ignore: - ignoreErrors = TRUE; - break; - case Silent: - beSilent = TRUE; - break; - case ExPath: - if (paths != NULL) - Lst_ForEach(paths, ParseClearPath, NULL); - Dir_SetPATH(); - break; -#ifdef POSIX - case Posix: - Var_Set("%POSIX", "1003.2", VAR_GLOBAL); - break; -#endif - default: - break; - } - } else if (specType == MFlags) { + 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 @@ -1621,153 +1740,38 @@ ParseDoDependency(char *line) */ Main_ParseArgLine(line); *line = '\0'; - } else if (specType == ExShell) { + } else if (specType == SP_SHELL) { if (!Job_ParseShell(line)) { Parse_Error(PARSE_FATAL, "improper shell specification"); goto out; } *line = '\0'; - } else if (specType == NotParallel || specType == SingleShell || - specType == DeleteOnError) { + } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL || + specType == SP_DELETE_ON_ERROR) { *line = '\0'; } /* * NOW GO FOR THE SOURCES */ - if (specType == Suffixes || specType == ExPath || - specType == Includes || specType == Libs || - specType == Null || specType == ExObjdir) + if (specType == SP_SUFFIXES || specType == SP_PATH || + specType == SP_INCLUDES || specType == SP_LIBS || + specType == SP_NULL || specType == SP_OBJDIR) { - while (*line) { - /* - * If the target was one that doesn't take files as its sources - * but takes something like suffixes, we take each - * space-separated word on the line as a something and deal - * with it accordingly. - * - * If the target was .SUFFIXES, we take each source as a - * suffix and add it to the list of suffixes maintained by the - * Suff module. - * - * If the target was a .PATH, we add the source as a directory - * to search on the search path. - * - * If it was .INCLUDES, the source is taken to be the suffix of - * files which will be #included and whose search path should - * be present in the .INCLUDES variable. - * - * If it was .LIBS, the source is taken to be the suffix of - * files which are considered libraries and whose search path - * should be present in the .LIBS variable. - * - * If it was .NULL, the source is the suffix to use when a file - * has no valid suffix. - * - * If it was .OBJDIR, the source is a new definition for .OBJDIR, - * and will cause make to do a new chdir to that path. - */ - while (*cp && !isspace ((unsigned char)*cp)) { - cp++; - } - savec = *cp; - *cp = '\0'; - switch (specType) { - case Suffixes: - Suff_AddSuffix(line, &mainNode); - break; - case ExPath: - if (paths != NULL) - Lst_ForEach(paths, ParseAddDir, line); - break; - case Includes: - Suff_AddInclude(line); - break; - case Libs: - Suff_AddLib(line); - break; - case Null: - Suff_SetNull(line); - break; - case ExObjdir: - Main_SetObjdir("%s", line); - break; - default: - break; - } - *cp = savec; - if (savec != '\0') { - cp++; - } - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } - line = cp; - } + ParseDoDependencySourcesSpecial(line, cp, specType, paths); if (paths) { Lst_Free(paths); paths = NULL; } - if (specType == ExPath) + if (specType == SP_PATH) Dir_SetPATH(); } else { assert(paths == NULL); - while (*line) { - /* - * The targets take real sources, so we must beware of archive - * specifications (i.e. things with left parentheses in them) - * and handle them accordingly. - */ - for (; *cp && !isspace ((unsigned char)*cp); cp++) { - if (*cp == LPAREN && cp > line && cp[-1] != '$') { - /* - * Only stop for a left parenthesis if it isn't at the - * start of a word (that'll be for variable changes - * later) and isn't preceded by a dollar sign (a dynamic - * source). - */ - break; - } - } - - if (*cp == LPAREN) { - sources = Lst_Init(); - if (!Arch_ParseArchive(&line, sources, VAR_CMD)) { - Parse_Error(PARSE_FATAL, - "Error in source archive spec \"%s\"", line); - goto out; - } - - while (!Lst_IsEmpty(sources)) { - gn = Lst_Dequeue(sources); - ParseDoSrc(tOp, gn->name); - } - Lst_Free(sources); - cp = line; - } else { - if (*cp) { - *cp = '\0'; - cp += 1; - } - - ParseDoSrc(tOp, line); - } - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } - line = cp; - } + if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp)) + goto out; } - if (mainNode == NULL && targets != NULL) { - /* - * If we have yet to decide on a main target to make, in the - * absence of any user input, we want the first target on - * the first dependency line that is actually a real target - * (i.e. isn't a .USE or .EXEC rule) to be made. - */ - Lst_ForEach(targets, ParseFindMain, NULL); - } + FindMainTarget(); out: if (paths != NULL) @@ -1776,42 +1780,94 @@ out: Lst_Free(curTargs); } -/*- - *--------------------------------------------------------------------- - * Parse_IsVar -- - * Return TRUE if the passed line is a variable assignment. A variable - * assignment consists of a single word followed by optional whitespace - * followed by either a += or an = operator. - * This function is used both by the Parse_File function and main when - * parsing the command-line arguments. - * - * Input: - * line the line to check +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. * - * Results: - * TRUE if it is. FALSE if it ain't + * 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++ =". * - * Side Effects: - * none - *--------------------------------------------------------------------- - */ + * Used for both lines in a file and command line arguments. */ Boolean -Parse_IsVar(char *line) +Parse_IsVar(const char *p, VarAssign *out_var) { - Boolean wasSpace = FALSE; /* set TRUE if found a space */ + VarAssignParsed pvar; + const char *firstSpace = NULL; char ch; int level = 0; -#define ISEQOPERATOR(c) \ - (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!')) - /* - * Skip to variable name - */ - while (*line == ' ' || *line == '\t') - line++; + /* Skip to variable name */ + while (*p == ' ' || *p == '\t') + p++; + + /* During parsing, the '+' of the '+=' operator is initially parsed + * as part of the variable name. It is later corrected, as is the ':sh' + * modifier. Of these two (nameEnd and op), the earlier one determines the + * actual end of the variable name. */ + pvar.nameStart = p; +#ifdef CLEANUP + pvar.nameEnd = NULL; + pvar.eq = NULL; +#endif /* Scan for one of the assignment operators outside a variable expansion */ - while ((ch = *line++) != 0) { + while ((ch = *p++) != 0) { if (ch == '(' || ch == '{') { level++; continue; @@ -1820,233 +1876,200 @@ Parse_IsVar(char *line) level--; continue; } + if (level != 0) continue; - while (ch == ' ' || ch == '\t') { - ch = *line++; - wasSpace = TRUE; - } + + if (ch == ' ' || ch == '\t') + if (firstSpace == NULL) + firstSpace = p - 1; + while (ch == ' ' || ch == '\t') + ch = *p++; + #ifdef SUNSHCMD - if (ch == ':' && strncmp(line, "sh", 2) == 0) { - line += 2; + if (ch == ':' && strncmp(p, "sh", 2) == 0) { + p += 2; continue; } #endif - if (ch == '=') + if (ch == '=') { + pvar.eq = p - 1; + pvar.nameEnd = firstSpace != NULL ? firstSpace : p - 1; + cpp_skip_whitespace(&p); + AdjustVarassignOp(&pvar, p, out_var); return TRUE; - if (*line == '=' && ISEQOPERATOR(ch)) + } + if (*p == '=' && (ch == '+' || ch == ':' || ch == '?' || ch == '!')) { + pvar.eq = p; + pvar.nameEnd = firstSpace != NULL ? firstSpace : p; + p++; + cpp_skip_whitespace(&p); + AdjustVarassignOp(&pvar, p, out_var); return TRUE; - if (wasSpace) + } + if (firstSpace != NULL) return FALSE; } return FALSE; } -/*- - *--------------------------------------------------------------------- - * Parse_DoVar -- - * Take the variable assignment in the passed line and do it in the - * global context. - * - * Note: There is a lexical ambiguity with assignment modifier characters - * in variable names. This routine interprets the character before the = - * as a modifier. Therefore, an assignment like - * C++=/usr/bin/CC - * is interpreted as "C+ +=" instead of "C++ =". - * - * Input: - * line a line guaranteed to be a variable assignment. - * This reduces error checks - * ctxt Context in which to do the assignment - * - * Results: - * none - * - * Side Effects: - * the variable structure of the given variable name is altered in the - * global context. - *--------------------------------------------------------------------- - */ -void -Parse_DoVar(char *line, GNode *ctxt) -{ - char *cp; /* pointer into line */ - enum { - VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL - } type; /* Type of assignment */ - char *opc; /* ptr to operator character to - * null-terminate the variable name */ - Boolean freeCp = FALSE; /* TRUE if cp needs to be freed, - * i.e. if any variable expansion was - * performed */ - int depth; - - /* - * Skip to variable name - */ - while (*line == ' ' || *line == '\t') - line++; - - /* - * Skip to operator character, nulling out whitespace as we go - * XXX Rather than counting () and {} we should look for $ and - * then expand the variable. - */ - for (depth = 0, cp = line; depth > 0 || *cp != '='; cp++) { - if (*cp == '(' || *cp == '{') { - depth++; - continue; - } - if (*cp == ')' || *cp == '}') { - depth--; - continue; - } - if (depth == 0 && isspace ((unsigned char)*cp)) { - *cp = '\0'; +static void +VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *ctxt) +{ + if (DEBUG(LINT)) { + if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) { + /* Check for syntax errors such as unclosed expressions or + * unknown modifiers. */ + char *expandedValue; + + (void)Var_Subst(uvalue, ctxt, VARE_NONE, &expandedValue); + /* TODO: handle errors */ + free(expandedValue); } } - opc = cp-1; /* operator is the previous character */ - *cp++ = '\0'; /* nuke the = */ +} +static void +VarAssign_EvalSubst(const char *name, const char *uvalue, GNode *ctxt, + const char **out_avalue, void **out_avalue_freeIt) +{ + const char *avalue = uvalue; + char *evalue; /* - * Check operator type + * Allow variables in the old value to be undefined, but leave their + * expressions alone -- this is done by forcing oldVars to be false. + * XXX: This can cause recursive variables, but that's not hard to do, + * and this allows someone to do something like + * + * CFLAGS = $(.INCLUDES) + * CFLAGS := -I.. $(CFLAGS) + * + * And not get an error. */ - switch (*opc) { - case '+': - type = VAR_APPEND; - *opc = '\0'; - break; - - case '?': - /* - * If the variable already has a value, we don't do anything. - */ - *opc = '\0'; - if (Var_Exists(line, ctxt)) { - return; - } else { - type = VAR_NORMAL; - } - break; - - case ':': - type = VAR_SUBST; - *opc = '\0'; - break; - - case '!': - type = VAR_SHELL; - *opc = '\0'; - break; + Boolean oldOldVars = oldVars; - default: -#ifdef SUNSHCMD - while (opc > line && *opc != ':') - opc--; + oldVars = FALSE; - if (strncmp(opc, ":sh", 3) == 0) { - type = VAR_SHELL; - *opc = '\0'; - break; - } -#endif - type = VAR_NORMAL; - break; - } + /* + * make sure that we set the variable the first time to nothing + * so that it gets substituted! + */ + if (!Var_Exists(name, ctxt)) + Var_Set(name, "", ctxt); - while (isspace((unsigned char)*cp)) - cp++; + (void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_ASSIGN, &evalue); + /* TODO: handle errors */ + oldVars = oldOldVars; + avalue = evalue; + Var_Set(name, avalue, ctxt); - if (DEBUG(LINT)) { - if (type != VAR_SUBST && strchr(cp, '$') != NULL) { - /* sanity check now */ - char *cp2; + *out_avalue = avalue; + *out_avalue_freeIt = evalue; +} - cp2 = Var_Subst(cp, ctxt, VARE_ASSIGN); - free(cp2); - } +static void +VarAssign_EvalShell(const char *name, const char *uvalue, GNode *ctxt, + const char **out_avalue, void **out_avalue_freeIt) +{ + const char *cmd, *errfmt; + char *cmdOut; + void *cmd_freeIt = NULL; + + cmd = uvalue; + if (strchr(cmd, '$') != NULL) { + char *ecmd; + (void)Var_Subst(cmd, VAR_CMDLINE, VARE_UNDEFERR | VARE_WANTRES, &ecmd); + /* TODO: handle errors */ + cmd = cmd_freeIt = ecmd; } - if (type == VAR_APPEND) { - Var_Append(line, cp, ctxt); - } else if (type == VAR_SUBST) { - /* - * Allow variables in the old value to be undefined, but leave their - * invocation alone -- this is done by forcing oldVars to be false. - * XXX: This can cause recursive variables, but that's not hard to do, - * and this allows someone to do something like - * - * CFLAGS = $(.INCLUDES) - * CFLAGS := -I.. $(CFLAGS) - * - * And not get an error. - */ - Boolean oldOldVars = oldVars; - - oldVars = FALSE; - - /* - * make sure that we set the variable the first time to nothing - * so that it gets substituted! - */ - if (!Var_Exists(line, ctxt)) - Var_Set(line, "", ctxt); + cmdOut = Cmd_Exec(cmd, &errfmt); + Var_Set(name, cmdOut, ctxt); + *out_avalue = *out_avalue_freeIt = cmdOut; - cp = Var_Subst(cp, ctxt, VARE_WANTRES|VARE_ASSIGN); - oldVars = oldOldVars; - freeCp = TRUE; + if (errfmt) + Parse_Error(PARSE_WARNING, errfmt, cmd); - Var_Set(line, cp, ctxt); - } else if (type == VAR_SHELL) { - char *res; - const char *error; + free(cmd_freeIt); +} - if (strchr(cp, '$') != NULL) { - /* - * There's a dollar sign in the command, so perform variable - * expansion on the whole thing. The resulting string will need - * freeing when we're done. - */ - cp = Var_Subst(cp, VAR_CMD, VARE_UNDEFERR|VARE_WANTRES); - freeCp = TRUE; +/* Perform a variable assignment. + * + * The actual value of the variable is returned in *out_avalue and + * *out_avalue_freeIt. Especially for VAR_SUBST and VAR_SHELL this can differ + * from the literal value. + * + * Return whether the assignment was actually done. The assignment is only + * skipped if the operator is '?=' and the variable already exists. */ +static Boolean +VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, + GNode *ctxt, const char **out_avalue, void **out_avalue_freeIt) +{ + const char *avalue = uvalue; + void *avalue_freeIt = NULL; + + if (op == VAR_APPEND) { + Var_Append(name, uvalue, ctxt); + } else if (op == VAR_SUBST) { + VarAssign_EvalSubst(name, uvalue, ctxt, &avalue, &avalue_freeIt); + } else if (op == VAR_SHELL) { + VarAssign_EvalShell(name, uvalue, ctxt, &avalue, &avalue_freeIt); + } else { + if (op == VAR_DEFAULT && Var_Exists(name, ctxt)) { + *out_avalue_freeIt = NULL; + return FALSE; } - res = Cmd_Exec(cp, &error); - Var_Set(line, res, ctxt); - free(res); - - if (error) - Parse_Error(PARSE_WARNING, error, cp); - } else { - /* - * Normal assignment -- just do it. - */ - Var_Set(line, cp, ctxt); + /* Normal assignment -- just do it. */ + Var_Set(name, uvalue, ctxt); } - if (strcmp(line, MAKEOVERRIDES) == 0) + + *out_avalue = avalue; + *out_avalue_freeIt = avalue_freeIt; + return TRUE; +} + +static void +VarAssignSpecial(const char *name, const char *avalue) +{ + if (strcmp(name, MAKEOVERRIDES) == 0) Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */ - else if (strcmp(line, ".CURDIR") == 0) { + else if (strcmp(name, ".CURDIR") == 0) { /* - * Somone is being (too?) clever... + * Someone is being (too?) clever... * Let's pretend they know what they are doing and - * re-initialize the 'cur' Path. + * re-initialize the 'cur' CachedDir. */ - Dir_InitCur(cp); + Dir_InitCur(avalue); Dir_SetPATH(); - } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) { + } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) { Job_SetPrefix(); - } else if (strcmp(line, MAKE_EXPORTED) == 0) { - Var_Export(cp, FALSE); + } else if (strcmp(name, MAKE_EXPORTED) == 0) { + Var_Export(avalue, FALSE); } - if (freeCp) - free(cp); +} + +/* Perform the variable variable assignment in the given context. */ +void +Parse_DoVar(VarAssign *var, GNode *ctxt) +{ + const char *avalue; /* actual value (maybe expanded) */ + void *avalue_freeIt; + + VarCheckSyntax(var->op, var->value, ctxt); + if (VarAssign_Eval(var->varname, var->op, var->value, ctxt, + &avalue, &avalue_freeIt)) + VarAssignSpecial(var->varname, avalue); + + free(avalue_freeIt); + free(var->varname); } /* * ParseMaybeSubMake -- - * Scan the command string to see if it a possible submake node + * Scan the command string to see if it a possible submake node * Input: * cmd the command to scan * Results: @@ -2067,41 +2090,27 @@ ParseMaybeSubMake(const char *cmd) MKV("$(.MAKE)"), MKV("make"), }; - for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) { + for (i = 0; i < sizeof vals / sizeof vals[0]; i++) { char *ptr; if ((ptr = strstr(cmd, vals[i].name)) == NULL) continue; - if ((ptr == cmd || !isalnum((unsigned char)ptr[-1])) - && !isalnum((unsigned char)ptr[vals[i].len])) + if ((ptr == cmd || !ch_isalnum(ptr[-1])) + && !ch_isalnum(ptr[vals[i].len])) return TRUE; } return FALSE; } -/*- - * ParseAddCmd -- - * Lst_ForEach function to add a command line to all targets - * - * Input: - * gnp the node to which the command is to be added - * cmd the command to add +/* Append the command to the target node. * - * Results: - * Always 0 - * - * Side Effects: - * A new element is added to the commands list of the node, - * and the node can be marked as a submake node if the command is - * determined to be that. - */ -static int -ParseAddCmd(void *gnp, void *cmd) + * The node may be marked as a submake node if the command is determined to + * be that. */ +static void +ParseAddCmd(GNode *gn, char *cmd) { - GNode *gn = (GNode *)gnp; - /* Add to last (ie current) cohort for :: targets */ - if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(gn->cohorts)) - gn = LstNode_Datum(Lst_Last(gn->cohorts)); + 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)) { @@ -2110,7 +2119,7 @@ ParseAddCmd(void *gnp, void *cmd) gn->type |= OP_SUBMAKE; ParseMark(gn); } else { -#ifdef notyet +#if 0 /* XXX: We cannot do this until we fix the tree */ Lst_Append(gn->commands, cmd); Parse_Error(PARSE_WARNING, @@ -2119,95 +2128,39 @@ ParseAddCmd(void *gnp, void *cmd) gn->name, gn->fname, gn->lineno); #else Parse_Error(PARSE_WARNING, - "duplicate script for target \"%s\" ignored", - gn->name); - ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING, - "using previous script for \"%s\" defined here", - gn->name); + "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 } - return 0; } -/*- - *----------------------------------------------------------------------- - * ParseHasCommands -- - * Callback procedure for Parse_File when destroying the list of - * targets on the last dependency line. Marks a target as already - * having commands if it does, to keep from having shell commands - * on multiple dependency lines. - * - * Input: - * gnp Node to examine - * - * Results: - * None - * - * Side Effects: - * OP_HAS_COMMANDS may be set for the target. - * - *----------------------------------------------------------------------- - */ -static void -ParseHasCommands(void *gnp) -{ - GNode *gn = (GNode *)gnp; - if (!Lst_IsEmpty(gn->commands)) { - gn->type |= OP_HAS_COMMANDS; - } -} - -/*- - *----------------------------------------------------------------------- - * Parse_AddIncludeDir -- - * Add a directory to the path searched for included makefiles - * bracketed by double-quotes. Used by functions in main.c - * - * Input: - * dir The name of the directory to add - * - * Results: - * None. - * - * Side Effects: - * The directory is appended to the list. - * - *----------------------------------------------------------------------- - */ +/* Add a directory to the path searched for included makefiles bracketed + * by double-quotes. */ void -Parse_AddIncludeDir(char *dir) +Parse_AddIncludeDir(const char *dir) { (void)Dir_AddDir(parseIncPath, dir); } -/*- - *--------------------------------------------------------------------- - * ParseDoInclude -- - * Push to another file. - * - * The input is the line minus the `.'. A file spec is a string - * enclosed in <> or "". The former is looked for only in sysIncPath. - * The latter in . and the directories specified by -I command line - * options - * - * Results: - * None +/* Push to another file. * - * Side Effects: - * A structure is added to the includes Lst and readProc, lineno, - * fname and curFILE are altered for the new file - *--------------------------------------------------------------------- + * The input is the line minus the '.'. A file spec is a string enclosed in + * <> or "". The <> file is looked for only in sysIncPath. The "" file is + * first searched in the parsedir and then in the directories specified by + * the -I command line options. */ - static void Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) { struct loadedfile *lf; - char *fullname; /* full pathname of file */ - char *newName; - char *prefEnd, *incdir; - int fd; - int i; + char *fullname; /* full pathname of file */ + char *newName; + char *prefEnd, *incdir; + int fd; + int i; /* * Now we know the file's name and its search path, we attempt to @@ -2225,7 +2178,7 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) * we can locate the beast. */ - incdir = bmake_strdup(curFile->fname); + incdir = bmake_strdup(CurFile()->fname); prefEnd = strrchr(incdir, '/'); if (prefEnd != NULL) { *prefEnd = '\0'; @@ -2252,7 +2205,7 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) * If we have a suffix specific path we should use that. */ char *suff; - Lst suffPath = NULL; + SearchPath *suffPath = NULL; if ((suff = strrchr(file, '.'))) { suffPath = Suff_GetPath(suff); @@ -2274,8 +2227,8 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) /* * Look for it on the system path */ - fullname = Dir_FindFile(file, - Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath); + SearchPath *path = Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath; + fullname = Dir_FindFile(file, path); } if (fullname == NULL) { @@ -2296,21 +2249,20 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) /* load it */ lf = loadfile(fullname, fd); - ParseSetIncludedFile(); /* Start reading from this file next */ Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf); - curFile->lf = lf; + CurFile()->lf = lf; if (depinc) - doing_depend = depinc; /* only turn it on */ + doing_depend = depinc; /* only turn it on */ } static void ParseDoInclude(char *line) { - char endc; /* the character which ends the file spec */ - char *cp; /* current position in file spec */ - int silent = *line != 'i'; - char *file = &line[7 + silent]; + char endc; /* the character which ends the file spec */ + char *cp; /* current position in file spec */ + int silent = *line != 'i'; + char *file = line + (silent ? 8 : 7); /* Skip to delimiter character so we know where to look */ while (*file == ' ' || *file == '\t') @@ -2318,7 +2270,7 @@ ParseDoInclude(char *line) if (*file != '"' && *file != '<') { Parse_Error(PARSE_FATAL, - ".include filename must be delimited by '\"' or '<'"); + ".include filename must be delimited by '\"' or '<'"); return; } @@ -2339,8 +2291,8 @@ ParseDoInclude(char *line) if (*cp != endc) { Parse_Error(PARSE_FATAL, - "Unclosed %cinclude filename. '%c' expected", - '.', endc); + "Unclosed %cinclude filename. '%c' expected", + '.', endc); return; } *cp = '\0'; @@ -2349,153 +2301,147 @@ ParseDoInclude(char *line) * Substitute for any variables in the file name before trying to * find the thing. */ - file = Var_Subst(file, VAR_CMD, VARE_WANTRES); + (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &file); + /* TODO: handle errors */ Parse_include_file(file, endc == '>', *line == 'd', silent); free(file); } - -/*- - *--------------------------------------------------------------------- - * ParseSetIncludedFile -- - * Set the .INCLUDEDFROMFILE variable to the contents of .PARSEFILE - * and the .INCLUDEDFROMDIR variable to the contents of .PARSEDIR - * - * Results: - * None - * - * Side Effects: - * The .INCLUDEDFROMFILE variable is overwritten by the contents - * of .PARSEFILE and the .INCLUDEDFROMDIR variable is overwriten - * by the contents of .PARSEDIR - *--------------------------------------------------------------------- - */ +/* Split filename into dirname + basename, then assign these to the + * given variables. */ static void -ParseSetIncludedFile(void) +SetFilenameVars(const char *filename, const char *dirvar, const char *filevar) { - const char *pf, *pd; - char *pf_freeIt, *pd_freeIt; + const char *slash, *dirname, *basename; + void *freeIt; - pf = Var_Value(".PARSEFILE", VAR_GLOBAL, &pf_freeIt); - Var_Set(".INCLUDEDFROMFILE", pf, VAR_GLOBAL); - pd = Var_Value(".PARSEDIR", VAR_GLOBAL, &pd_freeIt); - Var_Set(".INCLUDEDFROMDIR", pd, VAR_GLOBAL); + slash = strrchr(filename, '/'); + if (slash == NULL) { + dirname = curdir; + basename = filename; + freeIt = NULL; + } else { + dirname = freeIt = bmake_strsedup(filename, slash); + basename = slash + 1; + } - if (DEBUG(PARSE)) - fprintf(debug_file, "%s: ${.INCLUDEDFROMDIR} = `%s' " - "${.INCLUDEDFROMFILE} = `%s'\n", __func__, pd, pf); + Var_Set(dirvar, dirname, VAR_GLOBAL); + Var_Set(filevar, basename, VAR_GLOBAL); - bmake_free(pf_freeIt); - bmake_free(pd_freeIt); + DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n", + __func__, dirvar, dirname, filevar, basename); + free(freeIt); } -/*- - *--------------------------------------------------------------------- - * ParseSetParseFile -- - * Set the .PARSEDIR and .PARSEFILE variables to the dirname and - * basename of the given filename - * - * Results: - * None + +/* Return the immediately including file. * - * Side Effects: - * The .PARSEDIR and .PARSEFILE variables are overwritten by the - * dirname and basename of the given filename. - *--------------------------------------------------------------------- - */ + * 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) { - char *slash, *dirname; - const char *pd, *pf; + const char *including; - slash = strrchr(filename, '/'); - if (slash == NULL) { - Var_Set(".PARSEDIR", pd = curdir, VAR_GLOBAL); - Var_Set(".PARSEFILE", pf = filename, VAR_GLOBAL); - dirname = NULL; + SetFilenameVars(filename, ".PARSEDIR", ".PARSEFILE"); + + including = GetActuallyIncludingFile(); + if (including != NULL) { + SetFilenameVars(including, + ".INCLUDEDFROMDIR", ".INCLUDEDFROMFILE"); } else { - dirname = bmake_strsedup(filename, slash); - Var_Set(".PARSEDIR", pd = dirname, VAR_GLOBAL); - Var_Set(".PARSEFILE", pf = slash + 1, VAR_GLOBAL); + Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL); + Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL); } - if (DEBUG(PARSE)) - fprintf(debug_file, "%s: ${.PARSEDIR} = `%s' ${.PARSEFILE} = `%s'\n", - __func__, pd, pf); - free(dirname); } -/* - * Track the makefiles we read - so makefiles can - * set dependencies on them. - * Avoid adding anything more than once. - */ +static Boolean +StrContainsWord(const char *str, const char *word) +{ + size_t strLen = strlen(str); + size_t wordLen = strlen(word); + const char *p, *end; + + if (strLen < wordLen) + return FALSE; /* str is too short to contain word */ + + end = str + strLen - wordLen; + for (p = str; p != NULL; p = strchr(p, ' ')) { + if (*p == ' ') + p++; + if (p > end) + return FALSE; /* cannot contain word */ + + if (memcmp(p, word, wordLen) == 0 && + (p[wordLen] == '\0' || p[wordLen] == ' ')) + return TRUE; + } + return FALSE; +} +/* XXX: Searching through a set of words with this linear search is + * inefficient for variables that contain thousands of words. */ +static Boolean +VarContainsWord(const char *varname, const char *word) +{ + void *val_freeIt; + const char *val = Var_Value(varname, VAR_GLOBAL, &val_freeIt); + Boolean found = val != NULL && StrContainsWord(val, word); + bmake_free(val_freeIt); + return found; +} + +/* Track the makefiles we read - so makefiles can set dependencies on them. + * Avoid adding anything more than once. */ static void ParseTrackInput(const char *name) { - char *fp = NULL; - - const char *old = Var_Value(MAKE_MAKEFILES, VAR_GLOBAL, &fp); - if (old) { - size_t name_len = strlen(name); - const char *ep = old + strlen(old) - name_len; - /* does it contain name? */ - for (; old != NULL; old = strchr(old, ' ')) { - if (*old == ' ') - old++; - if (old >= ep) - break; /* cannot contain name */ - if (memcmp(old, name, name_len) == 0 - && (old[name_len] == 0 || old[name_len] == ' ')) - goto cleanup; - } - } - Var_Append (MAKE_MAKEFILES, name, VAR_GLOBAL); - cleanup: - bmake_free(fp); + if (!VarContainsWord(MAKE_MAKEFILES, name)) + Var_Append(MAKE_MAKEFILES, name, VAR_GLOBAL); } -/*- - *--------------------------------------------------------------------- - * Parse_setInput -- - * Start Parsing from the given source +/* Start Parsing from the given source. * - * Results: - * None - * - * Side Effects: - * A structure is added to the includes Lst and readProc, lineno, - * fname and curFile are altered for the new file - *--------------------------------------------------------------------- - */ + * The given file is added to the includes stack. */ void Parse_SetInput(const char *name, int line, int fd, - char *(*nextbuf)(void *, size_t *), void *arg) + char *(*nextbuf)(void *, size_t *), void *arg) { + IFile *curFile; char *buf; size_t len; + Boolean fromForLoop = name == NULL; - if (name == NULL) - name = curFile->fname; + if (fromForLoop) + name = CurFile()->fname; else ParseTrackInput(name); if (DEBUG(PARSE)) - fprintf(debug_file, "%s: file %s, line %d, fd %d, nextbuf %p, arg %p\n", - __func__, name, line, fd, nextbuf, arg); + debug_printf("%s: file %s, line %d, fd %d, nextbuf %s, arg %p\n", + __func__, name, line, fd, + nextbuf == loadedfile_nextbuf ? "loadedfile" : "other", + arg); if (fd == -1 && nextbuf == NULL) /* sanity */ return; - if (curFile != NULL) - /* Save exiting file info */ - Lst_Prepend(includes, curFile); - - /* Allocate and fill in new structure */ - curFile = bmake_malloc(sizeof *curFile); + curFile = Vector_Push(&includes); /* * Once the previous state has been saved, we can get down to reading @@ -2504,6 +2450,7 @@ Parse_SetInput(const char *name, int line, int fd, * place. */ curFile->fname = bmake_strdup(name); + curFile->fromForLoop = fromForLoop; curFile->lineno = line; curFile->first_lineno = line; curFile->nextbuf = nextbuf; @@ -2522,58 +2469,31 @@ Parse_SetInput(const char *name, int line, int fd, free(curFile); return; } - curFile->P_str = buf; - curFile->P_ptr = buf; - curFile->P_end = buf+len; + curFile->buf_freeIt = buf; + curFile->buf_ptr = buf; + curFile->buf_end = buf + len; curFile->cond_depth = Cond_save_depth(); ParseSetParseFile(name); } -/*- - *----------------------------------------------------------------------- - * IsInclude -- - * Check if the line is an include directive - * - * Results: - * TRUE if it is. - * - * Side Effects: - * None - * - *----------------------------------------------------------------------- - */ +/* Check if the directive is an include directive. */ static Boolean -IsInclude(const char *line, Boolean sysv) +IsInclude(const char *dir, Boolean sysv) { - static const char inc[] = "include"; - static const size_t inclen = sizeof(inc) - 1; - - /* 'd' is not valid for sysv */ - int o = strchr(sysv ? "s-" : "ds-", *line) != NULL; + if (dir[0] == 's' || dir[0] == '-' || (dir[0] == 'd' && !sysv)) + dir++; - if (strncmp(line + o, inc, inclen) != 0) + if (strncmp(dir, "include", 7) != 0) return FALSE; /* Space is not mandatory for BSD .include */ - return !sysv || isspace((unsigned char)line[inclen + o]); + return !sysv || ch_isspace(dir[7]); } #ifdef SYSVINCLUDE -/*- - *----------------------------------------------------------------------- - * IsSysVInclude -- - * Check if the line is a SYSV include directive - * - * Results: - * TRUE if it is. - * - * Side Effects: - * None - * - *----------------------------------------------------------------------- - */ +/* Check if the line is a SYSV include directive. */ static Boolean IsSysVInclude(const char *line) { @@ -2582,13 +2502,13 @@ IsSysVInclude(const char *line) if (!IsInclude(line, TRUE)) return FALSE; - /* Avoid interpeting a dependency line as an include */ + /* Avoid interpreting a dependency line as an include */ for (p = line; (p = strchr(p, ':')) != NULL;) { if (*++p == '\0') { /* end of line -> dependency */ return FALSE; } - if (*p == ':' || isspace((unsigned char)*p)) { + if (*p == ':' || ch_isspace(*p)) { /* :: operator or ': ' -> dependency */ return FALSE; } @@ -2596,56 +2516,35 @@ IsSysVInclude(const char *line) return TRUE; } -/*- - *--------------------------------------------------------------------- - * ParseTraditionalInclude -- - * Push to another file. - * - * The input is the current line. The file name(s) are - * following the "include". - * - * Results: - * None - * - * Side Effects: - * A structure is added to the includes Lst and readProc, lineno, - * fname and curFILE are altered for the new file - *--------------------------------------------------------------------- - */ +/* Push to another file. The line points to the word "include". */ static void ParseTraditionalInclude(char *line) { - char *cp; /* current position in file spec */ - int done = 0; - int silent = line[0] != 'i'; - char *file = &line[silent + 7]; - char *all_files; + char *cp; /* current position in file spec */ + int done = 0; + int silent = line[0] != 'i'; + char *file = line + (silent ? 8 : 7); + char *all_files; - if (DEBUG(PARSE)) { - fprintf(debug_file, "%s: %s\n", __func__, file); - } + DEBUG2(PARSE, "%s: %s\n", __func__, file); - /* - * Skip over whitespace - */ - while (isspace((unsigned char)*file)) - file++; + pp_skip_whitespace(&file); /* * Substitute for any variables in the file name before trying to * find the thing. */ - all_files = Var_Subst(file, VAR_CMD, VARE_WANTRES); + (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &all_files); + /* TODO: handle errors */ if (*file == '\0') { - Parse_Error(PARSE_FATAL, - "Filename missing from \"include\""); + 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 && !isspace((unsigned char) *cp); cp++) + for (cp = file; *cp && !ch_isspace(*cp); cp++) continue; if (*cp) @@ -2661,130 +2560,104 @@ out: #endif #ifdef GMAKEEXPORT -/*- - *--------------------------------------------------------------------- - * ParseGmakeExport -- - * Parse export <variable>=<value> - * - * And set the environment with it. - * - * Results: - * None - * - * Side Effects: - * None - *--------------------------------------------------------------------- - */ +/* Parse "export <variable>=<value>", and actually export it. */ static void ParseGmakeExport(char *line) { - char *variable = &line[6]; - char *value; + char *variable = line + 6; + char *value; - if (DEBUG(PARSE)) { - fprintf(debug_file, "%s: %s\n", __func__, variable); - } + DEBUG2(PARSE, "%s: %s\n", __func__, variable); - /* - * Skip over whitespace - */ - while (isspace((unsigned char)*variable)) - variable++; + pp_skip_whitespace(&variable); for (value = variable; *value && *value != '='; value++) continue; if (*value != '=') { Parse_Error(PARSE_FATAL, - "Variable/Value missing from \"export\""); + "Variable/Value missing from \"export\""); return; } - *value++ = '\0'; /* terminate variable */ + *value++ = '\0'; /* terminate variable */ /* * Expand the value before putting it in the environment. */ - value = Var_Subst(value, VAR_CMD, VARE_WANTRES); + (void)Var_Subst(value, VAR_CMDLINE, VARE_WANTRES, &value); + /* TODO: handle errors */ + setenv(variable, value, 1); free(value); } #endif -/*- - *--------------------------------------------------------------------- - * ParseEOF -- - * Called when EOF is reached in the current file. If we were reading - * an include file, the includes stack is popped and things set up - * to go back to reading the previous file at the previous location. +/* Called when EOF is reached in the current file. If we were reading an + * include file, the includes stack is popped and things set up to go back + * to reading the previous file at the previous location. * * Results: - * CONTINUE if there's more to do. DONE if not. - * - * Side Effects: - * The old curFILE, is closed. The includes list is shortened. - * lineno, curFILE, and fname are changed if CONTINUE is returned. - *--------------------------------------------------------------------- + * 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 int +static Boolean ParseEOF(void) { char *ptr; size_t len; + IFile *curFile = CurFile(); assert(curFile->nextbuf != NULL); doing_depend = curFile->depending; /* restore this */ /* get next input buffer, if any */ ptr = curFile->nextbuf(curFile->nextbuf_arg, &len); - curFile->P_ptr = ptr; - curFile->P_str = ptr; - curFile->P_end = ptr + len; + curFile->buf_ptr = ptr; + curFile->buf_freeIt = ptr; + curFile->buf_end = ptr + len; curFile->lineno = curFile->first_lineno; if (ptr != NULL) { /* Iterate again */ - return CONTINUE; + return TRUE; } /* 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; + loadedfile_destroy(curFile->lf); + curFile->lf = NULL; } /* Dispose of curFile info */ /* Leak curFile->fname because all the gnodes have pointers to it */ - free(curFile->P_str); - free(curFile); + free(curFile->buf_freeIt); + Vector_Pop(&includes); - if (Lst_IsEmpty(includes)) { - curFile = NULL; + if (includes.len == 0) { /* We've run out of input */ Var_Delete(".PARSEDIR", VAR_GLOBAL); Var_Delete(".PARSEFILE", VAR_GLOBAL); Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL); Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL); - return DONE; + return FALSE; } - curFile = Lst_Dequeue(includes); - if (DEBUG(PARSE)) - fprintf(debug_file, "ParseEOF: returning to file %s, line %d\n", - curFile->fname, curFile->lineno); + curFile = CurFile(); + DEBUG2(PARSE, "ParseEOF: returning to file %s, line %d\n", + curFile->fname, curFile->lineno); - /* Restore the PARSEDIR/PARSEFILE variables */ ParseSetParseFile(curFile->fname); - return CONTINUE; + return TRUE; } #define PARSE_RAW 1 #define PARSE_SKIP 2 static char * -ParseGetLine(int flags, int *length) +ParseGetLine(int flags) { - IFile *cf = curFile; + IFile *cf = CurFile(); char *ptr; char ch; char *line; @@ -2796,29 +2669,32 @@ ParseGetLine(int flags, int *length) /* Loop through blank lines and comment lines */ for (;;) { cf->lineno++; - line = cf->P_ptr; + line = cf->buf_ptr; ptr = line; line_end = line; escaped = NULL; comment = NULL; for (;;) { - if (cf->P_end != NULL && ptr == cf->P_end) { + /* XXX: can buf_end ever be null? */ + if (cf->buf_end != NULL && ptr == cf->buf_end) { /* end of buffer */ ch = 0; break; } ch = *ptr; if (ch == 0 || (ch == '\\' && ptr[1] == 0)) { - if (cf->P_end == NULL) + /* XXX: can buf_end ever be null? */ + if (cf->buf_end == NULL) /* End of string (aka for loop) data */ break; /* see if there is more we can parse */ - while (ptr++ < cf->P_end) { + while (ptr++ < cf->buf_end) { if ((ch = *ptr) == '\n') { if (ptr > line && ptr[-1] == '\\') continue; Parse_Error(PARSE_WARNING, - "Zero byte read from file, skipping rest of line."); + "Zero byte read from file, " + "skipping rest of line."); break; } } @@ -2852,13 +2728,13 @@ ParseGetLine(int flags, int *length) ptr++; if (ch == '\n') break; - if (!isspace((unsigned char)ch)) + if (!ch_isspace(ch)) /* We are not interested in trailing whitespace */ line_end = ptr; } /* Save next 'to be processed' location */ - cf->P_ptr = ptr; + cf->buf_ptr = ptr; /* Check we have a non-comment, non-blank line */ if (line_end == line || comment == line) { @@ -2874,7 +2750,6 @@ ParseGetLine(int flags, int *length) if (flags & PARSE_RAW) { /* Leave '\' (etc) in line buffer (eg 'for' lines) */ - *length = line_end - line; return line; } @@ -2894,10 +2769,8 @@ ParseGetLine(int flags, int *length) } /* If we didn't see a '\\' then the in-situ data is fine */ - if (escaped == NULL) { - *length = line_end - line; + if (escaped == NULL) return line; - } /* Remove escapes from '\n' and '#' */ tp = ptr = escaped; @@ -2936,36 +2809,30 @@ ParseGetLine(int flags, int *length) } /* Delete any trailing spaces - eg from empty continuations */ - while (tp > escaped && isspace((unsigned char)tp[-1])) + while (tp > escaped && ch_isspace(tp[-1])) tp--; *tp = 0; - *length = tp - line; return line; } -/*- - *--------------------------------------------------------------------- - * ParseReadLine -- - * Read an entire line from the input file. Called only by Parse_File. +/* Read an entire line from the input file. Called only by Parse_File. * * Results: - * A line w/o its newline + * A line without its newline. * * Side Effects: * Only those associated with reading a character - *--------------------------------------------------------------------- */ static char * ParseReadLine(void) { - char *line; /* Result */ - int lineLength; /* Length of result */ - int lineno; /* Saved line # */ - int rval; + char *line; /* Result */ + int lineno; /* Saved line # */ + int rval; for (;;) { - line = ParseGetLine(0, &lineLength); + line = ParseGetLine(0); if (line == NULL) return NULL; @@ -2976,12 +2843,12 @@ ParseReadLine(void) * The line might be a conditional. Ask the conditional module * about it and act accordingly */ - switch (Cond_Eval(line)) { + switch (Cond_EvalLine(line)) { case COND_SKIP: /* Skip to next conditional that evaluates to COND_PARSE. */ do { - line = ParseGetLine(PARSE_SKIP, &lineLength); - } while (line && Cond_Eval(line) != COND_PARSE); + line = ParseGetLine(PARSE_SKIP); + } while (line && Cond_EvalLine(line) != COND_PARSE); if (line == NULL) break; continue; @@ -2997,13 +2864,13 @@ ParseReadLine(void) /* Syntax error - error printed, ignore line */ continue; /* Start of a .for loop */ - lineno = curFile->lineno; + lineno = CurFile()->lineno; /* Accumulate loop lines until matching .endfor */ do { - line = ParseGetLine(PARSE_RAW, &lineLength); + line = ParseGetLine(PARSE_RAW); if (line == NULL) { Parse_Error(PARSE_FATAL, - "Unexpected end of file in for loop."); + "Unexpected end of file in for loop."); break; } } while (For_Accum(line)); @@ -3016,309 +2883,325 @@ ParseReadLine(void) } } -/*- - *----------------------------------------------------------------------- - * ParseFinishLine -- - * Handle the end of a dependency group. - * - * Results: - * Nothing. - * - * Side Effects: - * inLine set FALSE. 'targets' list destroyed. - * - *----------------------------------------------------------------------- - */ static void -ParseFinishLine(void) +FinishDependencyGroup(void) { - if (inLine) { - if (targets != NULL) { - Lst_ForEach(targets, Suff_EndTransform, NULL); - Lst_Destroy(targets, ParseHasCommands); + if (targets != NULL) { + GNodeListNode *ln; + 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; - inLine = FALSE; } } +/* 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 */ -/*- - *--------------------------------------------------------------------- - * Parse_File -- - * Parse a file into its component parts, incorporating it into the - * current dependency graph. This is the main function and controls - * almost every other function in this module - * - * Input: - * name the name of the file being read - * fd Open file to makefile to parse - * - * Results: - * None - * - * Side Effects: - * closes fd. - * Loads. Nodes are added to the list of all targets, nodes and links - * are added to the dependency graph. etc. etc. etc. - *--------------------------------------------------------------------- - */ -void -Parse_File(const char *name, int fd) + if (targets == NULL) { + Parse_Error(PARSE_FATAL, "Unassociated shell command \"%s\"", p); + return; + } + + { + char *cmd = bmake_strdup(p); + GNodeListNode *ln; + + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + ParseAddCmd(gn, cmd); + } +#ifdef CLEANUP + Lst_Append(targCmds, cmd); +#endif + } +} + +static Boolean +ParseDirective(char *line) { - char *cp; /* pointer into the line */ - char *line; /* the line we're working on */ - struct loadedfile *lf; + char *cp; - lf = loadfile(name, fd); + if (*line == '.') { + /* + * Lines that begin with the special character may be + * include or undef directives. + * On the other hand they can be suffix rules (.c.o: ...) + * or just dependencies for filenames that start '.'. + */ + cp = line + 1; + pp_skip_whitespace(&cp); + if (IsInclude(cp, FALSE)) { + ParseDoInclude(cp); + return TRUE; + } + if (strncmp(cp, "undef", 5) == 0) { + const char *varname; + cp += 5; + pp_skip_whitespace(&cp); + varname = cp; + for (; !ch_isspace(*cp) && *cp != '\0'; cp++) + continue; + *cp = '\0'; + Var_Delete(varname, VAR_GLOBAL); + /* TODO: undefine all variables, not only the first */ + /* TODO: use Str_Words, like everywhere else */ + return TRUE; + } else if (strncmp(cp, "export", 6) == 0) { + cp += 6; + pp_skip_whitespace(&cp); + Var_Export(cp, TRUE); + return TRUE; + } else if (strncmp(cp, "unexport", 8) == 0) { + Var_UnExport(cp); + return TRUE; + } else if (strncmp(cp, "info", 4) == 0 || + strncmp(cp, "error", 5) == 0 || + strncmp(cp, "warning", 7) == 0) { + if (ParseMessage(cp)) + return TRUE; + } + } + return FALSE; +} - inLine = FALSE; - fatals = 0; +static Boolean +ParseVarassign(const char *line) +{ + VarAssign var; + if (Parse_IsVar(line, &var)) { + FinishDependencyGroup(); + Parse_DoVar(&var, VAR_GLOBAL); + return TRUE; + } + return FALSE; +} - if (name == NULL) - name = "(stdin)"; +static char * +FindSemicolon(char *p) +{ + int level = 0; - Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf); - curFile->lf = lf; + for (; *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') { + p++; + continue; + } - do { - for (; (line = ParseReadLine()) != NULL; ) { - if (DEBUG(PARSE)) - fprintf(debug_file, "ParseReadLine (%d): '%s'\n", - curFile->lineno, line); - if (*line == '.') { - /* - * Lines that begin with the special character may be - * include or undef directives. - * On the other hand they can be suffix rules (.c.o: ...) - * or just dependencies for filenames that start '.'. - */ - for (cp = line + 1; isspace((unsigned char)*cp); cp++) { - continue; - } - if (IsInclude(cp, FALSE)) { - ParseDoInclude(cp); - continue; - } - if (strncmp(cp, "undef", 5) == 0) { - char *cp2; - for (cp += 5; isspace((unsigned char) *cp); cp++) - continue; - for (cp2 = cp; !isspace((unsigned char) *cp2) && - *cp2 != '\0'; cp2++) - continue; - *cp2 = '\0'; - Var_Delete(cp, VAR_GLOBAL); - continue; - } else if (strncmp(cp, "export", 6) == 0) { - for (cp += 6; isspace((unsigned char) *cp); cp++) - continue; - Var_Export(cp, TRUE); - continue; - } else if (strncmp(cp, "unexport", 8) == 0) { - Var_UnExport(cp); - continue; - } else if (strncmp(cp, "info", 4) == 0 || - strncmp(cp, "error", 5) == 0 || - strncmp(cp, "warning", 7) == 0) { - if (ParseMessage(cp)) - continue; - } - } + if (*p == '$' && (p[1] == '(' || p[1] == '{')) { + level++; + continue; + } - if (*line == '\t') { - /* - * If a line starts with a tab, it can only hope to be - * a creation command. - */ - cp = line + 1; - shellCommand: - for (; isspace ((unsigned char)*cp); cp++) { - continue; - } - if (*cp) { - if (!inLine) - Parse_Error(PARSE_FATAL, - "Unassociated shell command \"%s\"", - cp); - /* - * So long as it's not a blank line and we're actually - * in a dependency spec, add the command to the list of - * commands of all targets in the dependency spec - */ - if (targets) { - cp = bmake_strdup(cp); - Lst_ForEach(targets, ParseAddCmd, cp); -#ifdef CLEANUP - Lst_Append(targCmds, cp); -#endif - } - } - continue; - } + if (level > 0 && (*p == ')' || *p == '}')) { + level--; + continue; + } + + if (level == 0 && *p == ';') { + break; + } + } + return p; +} + +/* dependency -> target... op [source...] + * op -> ':' | '::' | '!' */ +static void +ParseDependency(char *line) +{ + VarEvalFlags eflags; + char *expanded_line; + const char *shellcmd = NULL; + + /* + * For some reason - probably to make the parser impossible - + * a ';' can be used to separate commands from dependencies. + * Attempt to avoid ';' inside substitution patterns. + */ + { + char *semicolon = FindSemicolon(line); + if (*semicolon != '\0') { + /* Terminate the dependency list at the ';' */ + *semicolon = '\0'; + shellcmd = semicolon + 1; + } + } + + /* + * We now know it's a dependency line so it needs to have all + * variables expanded before being parsed. + * + * XXX: Ideally the dependency line would first be split into + * its left-hand side, dependency operator and right-hand side, + * and then each side would be expanded on its own. This would + * allow for the left-hand side to allow only defined variables + * and to allow variables on the right-hand side to be undefined + * as well. + * + * Parsing the line first would also prevent that targets + * generated from variable expressions are interpreted as the + * dependency operator, such as in "target${:U:} middle: source", + * in which the middle is interpreted as a source, not a target. + */ + + /* In lint mode, allow undefined variables to appear in + * dependency lines. + * + * Ideally, only the right-hand side would allow undefined + * variables since it is common to have no dependencies. + * Having undefined variables on the left-hand side is more + * unusual though. Since both sides are expanded in a single + * pass, there is not much choice what to do here. + * + * In normal mode, it does not matter whether undefined + * variables are allowed or not since as of 2020-09-14, + * Var_Parse does not print any parse errors in such a case. + * It simply returns the special empty string var_Error, + * which cannot be detected in the result of Var_Subst. */ + eflags = DEBUG(LINT) ? VARE_WANTRES : VARE_UNDEFERR | VARE_WANTRES; + (void)Var_Subst(line, VAR_CMDLINE, eflags, &expanded_line); + /* TODO: handle errors */ + + /* Need a fresh list for the target nodes */ + if (targets != NULL) + Lst_Free(targets); + targets = Lst_New(); + + ParseDoDependency(expanded_line); + free(expanded_line); + + if (shellcmd != NULL) + ParseLine_ShellCommand(shellcmd); +} + +static void +ParseLine(char *line) +{ + if (ParseDirective(line)) + return; + + if (*line == '\t') { + ParseLine_ShellCommand(line + 1); + return; + } #ifdef SYSVINCLUDE - if (IsSysVInclude(line)) { - /* - * It's an S3/S5-style "include". - */ - ParseTraditionalInclude(line); - continue; - } + if (IsSysVInclude(line)) { + /* + * It's an S3/S5-style "include". + */ + ParseTraditionalInclude(line); + return; + } #endif + #ifdef GMAKEEXPORT - if (strncmp(line, "export", 6) == 0 && - isspace((unsigned char) line[6]) && - strchr(line, ':') == NULL) { - /* - * It's a Gmake "export". - */ - ParseGmakeExport(line); - continue; - } + if (strncmp(line, "export", 6) == 0 && ch_isspace(line[6]) && + strchr(line, ':') == NULL) { + /* + * It's a Gmake "export". + */ + ParseGmakeExport(line); + return; + } #endif - if (Parse_IsVar(line)) { - ParseFinishLine(); - Parse_DoVar(line, VAR_GLOBAL); - continue; - } -#ifndef POSIX - /* - * To make life easier on novices, if the line is indented we - * first make sure the line has a dependency operator in it. - * If it doesn't have an operator and we're in a dependency - * line's script, we assume it's actually a shell command - * and add it to the current list of targets. - */ - cp = line; - if (isspace((unsigned char) line[0])) { - while (isspace((unsigned char) *cp)) - cp++; - while (*cp && (ParseIsEscaped(line, cp) || - *cp != ':' && *cp != '!')) { - cp++; - } - if (*cp == '\0') { - if (inLine) { - Parse_Error(PARSE_WARNING, - "Shell command needs a leading tab"); - goto shellCommand; - } - } - } -#endif - ParseFinishLine(); + if (ParseVarassign(line)) + return; - /* - * For some reason - probably to make the parser impossible - - * a ';' can be used to separate commands from dependencies. - * Attempt to avoid ';' inside substitution patterns. - */ - { - int level = 0; + FinishDependencyGroup(); - for (cp = line; *cp != 0; cp++) { - if (*cp == '\\' && cp[1] != 0) { - cp++; - continue; - } - if (*cp == '$' && - (cp[1] == '(' || cp[1] == '{')) { - level++; - continue; - } - if (level > 0) { - if (*cp == ')' || *cp == '}') { - level--; - continue; - } - } else if (*cp == ';') { - break; - } - } - } - if (*cp != 0) - /* Terminate the dependency list at the ';' */ - *cp++ = 0; - else - cp = NULL; + ParseDependency(line); +} - /* - * We now know it's a dependency line so it needs to have all - * variables expanded before being parsed. Tell the variable - * module to complain if some variable is undefined... - */ - line = Var_Subst(line, VAR_CMD, VARE_UNDEFERR|VARE_WANTRES); +/* Parse a top-level makefile into its component parts, incorporating them + * 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; - /* - * Need a non-circular list for the target nodes - */ - if (targets != NULL) - Lst_Free(targets); + lf = loadfile(name, fd); + + assert(targets == NULL); + fatals = 0; - targets = Lst_Init(); - inLine = TRUE; + if (name == NULL) + name = "(stdin)"; - ParseDoDependency(line); - free(line); + Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf); + CurFile()->lf = lf; - /* If there were commands after a ';', add them now */ - if (cp != NULL) { - goto shellCommand; - } + 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() == CONTINUE); + } while (ParseEOF()); + + FinishDependencyGroup(); if (fatals) { (void)fflush(stdout); (void)fprintf(stderr, - "%s: Fatal errors encountered -- cannot continue", - progname); + "%s: Fatal errors encountered -- cannot continue", + progname); PrintOnError(NULL, NULL); exit(1); } } -/*- - *--------------------------------------------------------------------- - * Parse_Init -- - * initialize the parsing module - * - * Results: - * none - * - * Side Effects: - * the parseIncPath list is initialized... - *--------------------------------------------------------------------- - */ +/* Initialize the parsing module. */ void Parse_Init(void) { mainNode = NULL; - parseIncPath = Lst_Init(); - sysIncPath = Lst_Init(); - defIncPath = Lst_Init(); - includes = Lst_Init(); + parseIncPath = Lst_New(); + sysIncPath = Lst_New(); + defSysIncPath = Lst_New(); + Vector_Init(&includes, sizeof(IFile)); #ifdef CLEANUP - targCmds = Lst_Init(); + targCmds = Lst_New(); #endif } +/* Clean up the parsing module. */ void Parse_End(void) { #ifdef CLEANUP Lst_Destroy(targCmds, free); - if (targets) - Lst_Free(targets); - Lst_Destroy(defIncPath, Dir_Destroy); + assert(targets == NULL); + Lst_Destroy(defSysIncPath, Dir_Destroy); Lst_Destroy(sysIncPath, Dir_Destroy); Lst_Destroy(parseIncPath, Dir_Destroy); - Lst_Free(includes); /* Should be empty now */ + assert(includes.len == 0); + Vector_Done(&includes); #endif } @@ -3337,12 +3220,12 @@ Parse_End(void) * *----------------------------------------------------------------------- */ -Lst +GNodeList * Parse_MainName(void) { - Lst mainList; /* result list */ + GNodeList *mainList; - mainList = Lst_Init(); + mainList = Lst_New(); if (mainNode == NULL) { Punt("no target to make."); @@ -3350,27 +3233,14 @@ Parse_MainName(void) } else if (mainNode->type & OP_DOUBLEDEP) { Lst_Append(mainList, mainNode); Lst_AppendAll(mainList, mainNode->cohorts); - } - else + } else Lst_Append(mainList, mainNode); Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL); return mainList; } -/*- - *----------------------------------------------------------------------- - * ParseMark -- - * Add the filename and lineno to the GNode so that we remember - * where it was first defined. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -static void -ParseMark(GNode *gn) +int +Parse_GetFatals(void) { - gn->fname = curFile->fname; - gn->lineno = curFile->lineno; + return fatals; } @@ -1,4 +1,4 @@ -/* $NetBSD: str.c,v 1.64 2020/08/30 19:56:02 rillig Exp $ */ +/* $NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $ */ /*- * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,21 +68,11 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: str.c,v 1.64 2020/08/30 19:56:02 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)str.c 5.8 (Berkeley) 6/1/90"; -#else -__RCSID("$NetBSD: str.c,v 1.64 2020/08/30 19:56:02 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - #include "make.h" +/* "@(#)str.c 5.8 (Berkeley) 6/1/90" */ +MAKE_RCSID("$NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $"); + /* Return the concatenation of s1 and s2, freshly allocated. */ char * str_concat2(const char *s1, const char *s2) @@ -157,7 +147,7 @@ Str_Words(const char *str, Boolean expand) str_len = strlen(str); words_buf = bmake_malloc(strlen(str) + 1); - words_cap = MAX((str_len / 5), 50); + words_cap = str_len / 5 > 50 ? str_len / 5 : 50; words = bmake_malloc((words_cap + 1) * sizeof(char *)); /* @@ -179,7 +169,7 @@ Str_Words(const char *str, Boolean expand) else break; } else { - inquote = (char)ch; + inquote = ch; /* Don't miss "" or '' */ if (word_start == NULL && str_p[1] == inquote) { if (!expand) { @@ -279,46 +269,6 @@ done: } /* - * Str_FindSubstring -- See if a string contains a particular substring. - * - * Input: - * string String to search. - * substring Substring to find in string. - * - * Results: If string contains substring, the return value is the location of - * the first matching instance of substring in string. If string doesn't - * contain substring, the return value is NULL. Matching is done on an exact - * character-for-character basis with no wildcards or special characters. - * - * Side effects: None. - */ -char * -Str_FindSubstring(const char *string, const char *substring) -{ - const char *a, *b; - - /* - * First scan quickly through the two strings looking for a single- - * character match. When it's found, then compare the rest of the - * substring. - */ - - for (b = substring; *string != 0; string++) { - if (*string != *b) - continue; - a = string; - for (;;) { - if (*b == 0) - return UNCONST(string); - if (*a++ != *b++) - break; - } - b = substring; - } - return NULL; -} - -/* * Str_Match -- Test if a string matches a pattern like "*.[ch]". * * XXX this function does not detect or report malformed patterns. diff --git a/strlist.c b/strlist.c deleted file mode 100644 index 905e78fd8caa..000000000000 --- a/strlist.c +++ /dev/null @@ -1,91 +0,0 @@ -/* $NetBSD: strlist.c,v 1.6 2020/08/25 17:37:09 rillig Exp $ */ - -/*- - * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc. - * All rights reserved. - * - * This code is derived from software contributed to The NetBSD Foundation - * by David Laight. - * - * 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 NetBSD Foundation 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 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_NATIVE -static char rcsid[] = "$NetBSD: strlist.c,v 1.6 2020/08/25 17:37:09 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -__RCSID("$NetBSD: strlist.c,v 1.6 2020/08/25 17:37:09 rillig Exp $"); -#endif /* not lint */ -#endif - -#include "make.h" -#include "strlist.h" - -void -strlist_init(strlist_t *sl) -{ - sl->sl_num = 0; - sl->sl_max = 0; - sl->sl_items = NULL; -} - -void -strlist_clean(strlist_t *sl) -{ - char *str; - int i; - - STRLIST_FOREACH(str, sl, i) - free(str); - free(sl->sl_items); - - sl->sl_num = 0; - sl->sl_max = 0; - sl->sl_items = NULL; -} - -void -strlist_add_str(strlist_t *sl, char *str, unsigned int info) -{ - unsigned int n; - strlist_item_t *items; - - if (str == NULL) - return; - - n = sl->sl_num + 1; - sl->sl_num = n; - items = sl->sl_items; - if (n >= sl->sl_max) { - items = bmake_realloc(items, (n + 7) * sizeof *sl->sl_items); - sl->sl_items = items; - sl->sl_max = n + 6; - } - items += n - 1; - items->si_str = str; - items->si_info = info; - items[1].si_str = NULL; /* STRLIST_FOREACH() terminator */ -} diff --git a/strlist.h b/strlist.h deleted file mode 100644 index 551317c090f3..000000000000 --- a/strlist.h +++ /dev/null @@ -1,62 +0,0 @@ -/* $NetBSD: strlist.h,v 1.4 2020/08/13 03:54:57 rillig Exp $ */ - -/*- - * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc. - * All rights reserved. - * - * This code is derived from software contributed to The NetBSD Foundation - * by David Laight. - * - * 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 NetBSD Foundation 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 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_STRLIST_H -#define MAKE_STRLIST_H - -typedef struct { - char *si_str; - unsigned int si_info; -} strlist_item_t; - -typedef struct { - unsigned int sl_num; - unsigned int sl_max; - strlist_item_t *sl_items; -} strlist_t; - -void strlist_init(strlist_t *); -void strlist_clean(strlist_t *); -void strlist_add_str(strlist_t *, char *, unsigned int); - -#define strlist_num(sl) ((sl)->sl_num) -#define strlist_str(sl, n) ((sl)->sl_items[n].si_str) -#define strlist_info(sl, n) ((sl)->sl_items[n].si_info) -#define strlist_set_info(sl, n, v) ((void)((sl)->sl_items[n].si_info = (v))) - -#define STRLIST_FOREACH(v, sl, index) \ - if ((sl)->sl_items != NULL) \ - for (index = 0; (v = strlist_str(sl, index)) != NULL; index++) - -#endif /* MAKE_STRLIST_H */ @@ -1,4 +1,4 @@ -/* $NetBSD: suff.c,v 1.142 2020/08/31 16:44:25 rillig Exp $ */ +/* $NetBSD: suff.c,v 1.230 2020/10/31 11:54:33 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,98 +68,92 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: suff.c,v 1.142 2020/08/31 16:44:25 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)suff.c 8.4 (Berkeley) 3/21/94"; -#else -__RCSID("$NetBSD: suff.c,v 1.142 2020/08/31 16:44:25 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - /*- * suff.c -- * Functions to maintain suffix lists and find implicit dependents * using suffix transformation rules * * Interface: - * Suff_Init Initialize all things to do with suffixes. + * Suff_Init Initialize all things to do with suffixes. * - * Suff_End Cleanup the module + * Suff_End Clean up the module * - * Suff_DoPaths This function is used to make life easier - * when searching for a file according to its - * suffix. It takes the global search path, - * as defined using the .PATH: target, and appends - * its directories to the path of each of the - * defined suffixes, as specified using - * .PATH<suffix>: targets. In addition, all - * directories given for suffixes labeled as - * include files or libraries, using the .INCLUDES - * or .LIBS targets, are played with using - * Dir_MakeFlags to create the .INCLUDES and - * .LIBS global variables. + * Suff_DoPaths This function is used to make life easier + * when searching for a file according to its + * suffix. It takes the global search path, + * as defined using the .PATH: target, and appends + * its directories to the path of each of the + * defined suffixes, as specified using + * .PATH<suffix>: targets. In addition, all + * directories given for suffixes labeled as + * include files or libraries, using the .INCLUDES + * or .LIBS targets, are played with using + * Dir_MakeFlags to create the .INCLUDES and + * .LIBS global variables. * - * Suff_ClearSuffixes Clear out all the suffixes and defined - * transformations. + * Suff_ClearSuffixes + * Clear out all the suffixes and defined + * transformations. * - * Suff_IsTransform Return TRUE if the passed string is the lhs - * of a transformation rule. + * Suff_IsTransform + * Return TRUE if the passed string is the lhs + * of a transformation rule. * - * Suff_AddSuffix Add the passed string as another known suffix. + * Suff_AddSuffix Add the passed string as another known suffix. * - * Suff_GetPath Return the search path for the given suffix. + * Suff_GetPath Return the search path for the given suffix. * - * Suff_AddInclude Mark the given suffix as denoting an include - * file. + * Suff_AddInclude + * Mark the given suffix as denoting an include file. * - * Suff_AddLib Mark the given suffix as denoting a library. + * Suff_AddLib Mark the given suffix as denoting a library. * - * Suff_AddTransform Add another transformation to the suffix - * graph. Returns GNode suitable for framing, I - * mean, tacking commands, attributes, etc. on. + * Suff_AddTransform + * Add another transformation to the suffix + * graph. Returns GNode suitable for framing, I + * mean, tacking commands, attributes, etc. on. * - * Suff_SetNull Define the suffix to consider the suffix of - * any file that doesn't have a known one. + * Suff_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_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. + * Suff_FindPath Return the appropriate path to search in order to + * find the node. */ -#include "make.h" -#include "dir.h" +#include "make.h" +#include "dir.h" -#define SUFF_DEBUG0(fmt) \ - if (!DEBUG(SUFF)) (void) 0; else fprintf(debug_file, fmt) +/* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ +MAKE_RCSID("$NetBSD: suff.c,v 1.230 2020/10/31 11:54:33 rillig Exp $"); -#define SUFF_DEBUG1(fmt, arg1) \ - if (!DEBUG(SUFF)) (void) 0; else fprintf(debug_file, fmt, arg1) +#define SUFF_DEBUG0(text) DEBUG0(SUFF, text) +#define SUFF_DEBUG1(fmt, arg1) DEBUG1(SUFF, fmt, arg1) +#define SUFF_DEBUG2(fmt, arg1, arg2) DEBUG2(SUFF, fmt, arg1, arg2) +#define SUFF_DEBUG3(fmt, arg1, arg2, arg3) DEBUG3(SUFF, fmt, arg1, arg2, arg3) +#define SUFF_DEBUG4(fmt, arg1, arg2, arg3, arg4) \ + DEBUG4(SUFF, fmt, arg1, arg2, arg3, arg4) -#define SUFF_DEBUG2(fmt, arg1, arg2) \ - if (!DEBUG(SUFF)) (void) 0; else fprintf(debug_file, fmt, arg1, arg2) +typedef List SuffList; +typedef ListNode SuffListNode; -#define SUFF_DEBUG3(fmt, arg1, arg2, arg3) \ - if (!DEBUG(SUFF)) (void) 0; else fprintf(debug_file, fmt, arg1, arg2, arg3) +typedef List SrcList; +typedef ListNode SrcListNode; -static Lst sufflist; /* Lst of suffixes */ +static SuffList *sufflist; /* List of suffixes */ #ifdef CLEANUP -static Lst suffClean; /* Lst of suffixes to be cleaned */ +static SuffList *suffClean; /* List of suffixes to be cleaned */ #endif -static Lst srclist; /* Lst of sources */ -static Lst transforms; /* Lst of transformation rules */ +static SrcList *srclist; /* List of sources */ +static GNodeList *transforms; /* List of transformation rules */ static int sNum = 0; /* Counter for assigning suffix numbers */ -typedef enum { +typedef enum SuffFlags { SUFF_INCLUDE = 0x01, /* One which is #include'd */ SUFF_LIBRARY = 0x02, /* One which contains a library */ SUFF_NULL = 0x04 /* The empty suffix */ @@ -169,76 +163,48 @@ typedef enum { ENUM_FLAGS_RTTI_3(SuffFlags, SUFF_INCLUDE, SUFF_LIBRARY, SUFF_NULL); +typedef List SuffListList; + /* * Structure describing an individual suffix. */ typedef struct Suff { - char *name; /* The suffix itself, such as ".c" */ - int nameLen; /* Length of the name, to avoid strlen calls */ - SuffFlags flags; /* Type of suffix */ - Lst searchPath; /* The path along which files of this suffix + char *name; /* The suffix itself, such as ".c" */ + size_t nameLen; /* Length of the name, to avoid strlen calls */ + SuffFlags flags; /* Type of suffix */ + SearchPath *searchPath; /* The path along which files of this suffix * may be found */ - int sNum; /* The suffix number */ - int refCount; /* Reference count of list membership */ - Lst parents; /* Suffixes we have a transformation to */ - Lst children; /* Suffixes we have a transformation from */ - Lst ref; /* List of lists this suffix is referenced */ + int sNum; /* The suffix number */ + int refCount; /* Reference count of list membership + * and several other places */ + SuffList *parents; /* Suffixes we have a transformation to */ + SuffList *children; /* Suffixes we have a transformation from */ + SuffListList *ref; /* Lists in which this suffix is referenced */ } Suff; /* * Structure used in the search for implied sources. */ -typedef struct _Src { - char *file; /* The file to look for */ - char *pref; /* Prefix from which file was formed */ - Suff *suff; /* The suffix on the file */ - struct _Src *parent; /* The Src for which this is a source */ - GNode *node; /* The node describing the file */ - int children; /* Count of existing children (so we don't free +typedef struct Src { + char *file; /* The file to look for */ + char *pref; /* Prefix from which file was formed */ + Suff *suff; /* The suffix on the file */ + struct Src *parent; /* The Src for which this is a source */ + GNode *node; /* The node describing the file */ + int children; /* Count of existing children (so we don't free * this thing too early or never nuke it) */ #ifdef DEBUG_SRC - Lst cp; /* Debug; children list */ + SrcList *childrenList; #endif } Src; -/* - * A structure for passing more than one argument to the Lst-library-invoked - * function... - */ -typedef struct { - Lst l; - Src *s; -} LstSrc; - -typedef struct { - GNode **gn; - Suff *s; - Boolean r; -} GNodeSuff; - -static Suff *suffNull; /* The NULL suffix for this run */ -static Suff *emptySuff; /* The empty suffix required for POSIX +static Suff *suffNull; /* The NULL suffix for this run */ +static Suff *emptySuff; /* The empty suffix required for POSIX * single-suffix transformation rules */ -static void SuffUnRef(void *, void *); -static void SuffFree(void *); -static void SuffInsert(Lst, Suff *); -static void SuffRemove(Lst, Suff *); -static Boolean SuffParseTransform(char *, Suff **, Suff **); -static int SuffRebuildGraph(void *, void *); -static int SuffScanTargets(void *, void *); -static int SuffAddSrc(void *, void *); -static void SuffAddLevel(Lst, Src *); -static void SuffExpandChildren(LstNode, GNode *); -static void SuffExpandWildcards(LstNode, GNode *); -static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *); -static void SuffFindDeps(GNode *, Lst); -static void SuffFindArchiveDeps(GNode *, Lst); -static void SuffFindNormalDeps(GNode *, Lst); -static int SuffPrintName(void *, void *); -static int SuffPrintSuff(void *, void *); -static int SuffPrintTrans(void *, void *); +static void SuffFindDeps(GNode *, SrcList *); +static void SuffExpandWildcards(GNodeListNode *, GNode *); /*************** Lst Predicates ****************/ /*- @@ -268,83 +234,83 @@ SuffStrIsPrefix(const char *pref, const char *str) return *pref ? NULL : str; } -typedef struct { - char *ename; /* The end of the name */ - int len; /* Length of the name */ -} SuffSuffGetSuffixArgs; - -/* See if suff is a suffix of str. str->ename should point to THE END - * of the string to check. (THE END == the null byte) +/* See if suff is a suffix of str. * * Input: * s possible suffix - * str string to examine + * nameLen length of the string to examine + * nameEnd end of the string to examine * * Results: - * NULL if it ain't, pointer to character in str before suffix if - * it is. + * NULL if it ain't, pointer to the start of suffix in str if it is. */ -static char * -SuffSuffGetSuffix(const Suff *s, const SuffSuffGetSuffixArgs *str) +static const char * +SuffSuffGetSuffix(const Suff *s, size_t nameLen, const char *nameEnd) { - char *p1; /* Pointer into suffix name */ - char *p2; /* Pointer into string being examined */ + const char *p1; /* Pointer into suffix name */ + const char *p2; /* Pointer into string being examined */ - if (str->len < s->nameLen) + if (nameLen < s->nameLen) return NULL; /* this string is shorter than the suffix */ p1 = s->name + s->nameLen; - p2 = str->ename; + p2 = nameEnd; while (p1 >= s->name && *p1 == *p2) { p1--; p2--; } - return p1 == s->name - 1 ? p2 : NULL; + /* XXX: s->name - 1 invokes undefined behavior */ + return p1 == s->name - 1 ? p2 + 1 : NULL; } -/* Predicate form of SuffSuffGetSuffix, for Lst_Find. */ static Boolean -SuffSuffIsSuffix(const void *s, const void *sd) +SuffSuffIsSuffix(const Suff *suff, size_t nameLen, const char *nameEnd) { - return SuffSuffGetSuffix(s, sd) != NULL; + return SuffSuffGetSuffix(suff, nameLen, nameEnd) != NULL; } -/* See if the suffix has the desired name. */ -static Boolean -SuffSuffHasName(const void *s, const void *desiredName) +static Suff * +FindSuffByNameLen(const char *name, size_t nameLen) { - return strcmp(((const Suff *)s)->name, desiredName) == 0; + SuffListNode *ln; + + for (ln = sufflist->first; ln != NULL; ln = ln->next) { + Suff *suff = ln->datum; + if (suff->nameLen == nameLen && memcmp(suff->name, name, nameLen) == 0) + return suff; + } + return NULL; } -/* See if the suffix name is a prefix of the string. Care must be taken when - * using this to search for transformations and what-not, since there could - * well be two suffixes, one of which is a prefix of the other... */ -static Boolean -SuffSuffIsPrefix(const void *s, const void *str) +static Suff * +FindSuffByName(const char *name) { - return SuffStrIsPrefix(((const Suff *)s)->name, str) != NULL; + return FindSuffByNameLen(name, strlen(name)); } -/* See if the graph node has the desired name. */ -static Boolean -SuffGNHasName(const void *gn, const void *desiredName) +static GNode * +FindTransformByName(const char *name) { - return strcmp(((const GNode *)gn)->name, desiredName) == 0; + GNodeListNode *ln; + for (ln = transforms->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + if (strcmp(gn->name, name) == 0) + return gn; + } + return NULL; } /*********** Maintenance Functions ************/ static void -SuffUnRef(void *lp, void *sp) +SuffUnRef(SuffList *list, Suff *suff) { - Lst l = (Lst) lp; - - LstNode ln = Lst_FindDatum(l, sp); + SuffListNode *ln = Lst_FindDatum(list, suff); if (ln != NULL) { - Lst_Remove(l, ln); - ((Suff *)sp)->refCount--; + Lst_Remove(list, ln); + suff->refCount--; } } @@ -352,7 +318,7 @@ SuffUnRef(void *lp, void *sp) static void SuffFree(void *sp) { - Suff *s = (Suff *)sp; + Suff *s = sp; if (s == suffNull) suffNull = NULL; @@ -360,7 +326,7 @@ SuffFree(void *sp) if (s == emptySuff) emptySuff = NULL; -#ifdef notdef +#if 0 /* We don't delete suffixes in order, so we cannot use this */ if (s->refCount) Punt("Internal error deleting suffix `%s' with refcount = %d", s->name, @@ -378,50 +344,43 @@ SuffFree(void *sp) /* Remove the suffix from the list, and free if it is otherwise unused. */ static void -SuffRemove(Lst l, Suff *s) +SuffRemove(SuffList *list, Suff *suff) { - SuffUnRef(l, s); - if (s->refCount == 0) { - SuffUnRef(sufflist, s); - SuffFree(s); + SuffUnRef(list, suff); + if (suff->refCount == 0) { + SuffUnRef(sufflist, suff); + SuffFree(suff); } } -/* Insert the suffix into the list keeping the list ordered by suffix numbers. - * - * Input: - * l the list where in s should be inserted - * s the suffix to insert - */ +/* Insert the suffix into the list, keeping the list ordered by suffix + * numbers. */ static void -SuffInsert(Lst l, Suff *s) +SuffInsert(SuffList *list, Suff *suff) { - LstNode ln; /* current element in l we're examining */ - Suff *s2 = NULL; /* the suffix descriptor in this element */ + SuffListNode *ln; + Suff *listSuff = NULL; - Lst_Open(l); - while ((ln = Lst_Next(l)) != NULL) { - s2 = LstNode_Datum(ln); - if (s2->sNum >= s->sNum) { + for (ln = list->first; ln != NULL; ln = ln->next) { + listSuff = ln->datum; + if (listSuff->sNum >= suff->sNum) break; - } } - Lst_Close(l); - - SUFF_DEBUG2("inserting %s(%d)...", s->name, s->sNum); if (ln == NULL) { - SUFF_DEBUG0("at end of list\n"); - Lst_Append(l, s); - s->refCount++; - Lst_Append(s->ref, l); - } else if (s2->sNum != s->sNum) { - SUFF_DEBUG2("before %s(%d)\n", s2->name, s2->sNum); - Lst_InsertBefore(l, ln, s); - s->refCount++; - Lst_Append(s->ref, l); + SUFF_DEBUG2("inserting \"%s\" (%d) at end of list\n", + suff->name, suff->sNum); + Lst_Append(list, suff); + suff->refCount++; + Lst_Append(suff->ref, list); + } else if (listSuff->sNum != suff->sNum) { + SUFF_DEBUG4("inserting \"%s\" (%d) before \"%s\" (%d)\n", + suff->name, suff->sNum, listSuff->name, listSuff->sNum); + Lst_InsertBefore(list, ln, suff); + suff->refCount++; + Lst_Append(suff->ref, list); } else { - SUFF_DEBUG0("already there\n"); + SUFF_DEBUG2("\"%s\" (%d) is already there\n", suff->name, suff->sNum); } } @@ -430,15 +389,15 @@ SuffNew(const char *name) { Suff *s = bmake_malloc(sizeof(Suff)); - s->name = bmake_strdup(name); - s->nameLen = strlen(s->name); - s->searchPath = Lst_Init(); - s->children = Lst_Init(); - s->parents = Lst_Init(); - s->ref = Lst_Init(); - s->sNum = sNum++; - s->flags = 0; - s->refCount = 1; + s->name = bmake_strdup(name); + s->nameLen = strlen(s->name); + s->searchPath = Lst_New(); + s->children = Lst_New(); + s->parents = Lst_New(); + s->ref = Lst_New(); + s->sNum = sNum++; + s->flags = 0; + s->refCount = 1; return s; } @@ -454,40 +413,27 @@ Suff_ClearSuffixes(void) #ifdef CLEANUP Lst_MoveAll(suffClean, sufflist); #endif - sufflist = Lst_Init(); + sufflist = Lst_New(); sNum = 0; if (suffNull) SuffFree(suffNull); emptySuff = suffNull = SuffNew(""); Dir_Concat(suffNull->searchPath, dirSearchPath); - suffNull->flags = SUFF_NULL; + suffNull->flags = SUFF_NULL; } -/* Parse a transformation string to find its two component suffixes. +/* 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. * - * Input: - * str String being parsed - * out_src Place to store source of trans. - * out_targ Place to store target of trans. - * - * Results: - * TRUE if the string is a valid transformation, FALSE otherwise. + * Return TRUE if the string is a valid transformation. */ static Boolean -SuffParseTransform(char *str, Suff **out_src, Suff **out_targ) +SuffParseTransform(const char *str, Suff **out_src, Suff **out_targ) { - LstNode srcLn; /* element in suffix list of trans source*/ - Suff *src; /* Source of transformation */ - LstNode targLn; /* element in suffix list of trans target*/ - char *str2; /* Extra pointer (maybe target suffix) */ - LstNode singleLn; /* element in suffix list of any suffix - * that exactly matches str */ - Suff *single = NULL;/* Source of possible transformation to - * null suffix */ - - srcLn = NULL; - singleLn = NULL; + SuffListNode *ln; + Suff *singleSrc = NULL; /* * Loop looking first for a suffix that matches the start of the @@ -495,87 +441,77 @@ SuffParseTransform(char *str, Suff **out_src, Suff **out_targ) * we can find two that meet these criteria, we've successfully * parsed the string. */ - for (;;) { - if (srcLn == NULL) { - srcLn = Lst_Find(sufflist, SuffSuffIsPrefix, str); - } else { - srcLn = Lst_FindFrom(sufflist, LstNode_Next(srcLn), - SuffSuffIsPrefix, str); - } - if (srcLn == NULL) { - /* - * Ran out of source suffixes -- no such rule - */ - if (singleLn != NULL) { - /* - * Not so fast Mr. Smith! There was a suffix that encompassed - * the entire string, so we assume it was a transformation - * to the null suffix (thank you POSIX). We still prefer to - * find a double rule over a singleton, hence we leave this - * check until the end. - * - * XXX: Use emptySuff over suffNull? - */ - *out_src = single; - *out_targ = suffNull; - return TRUE; - } - return FALSE; - } - src = LstNode_Datum(srcLn); - str2 = str + src->nameLen; - if (*str2 == '\0') { - single = src; - singleLn = srcLn; + for (ln = sufflist->first; ln != NULL; ln = ln->next) { + Suff *src = ln->datum; + + if (SuffStrIsPrefix(src->name, str) == NULL) + continue; + + if (str[src->nameLen] == '\0') { + singleSrc = src; } else { - targLn = Lst_Find(sufflist, SuffSuffHasName, str2); - if (targLn != NULL) { + Suff *targ = FindSuffByName(str + src->nameLen); + if (targ != NULL) { *out_src = src; - *out_targ = LstNode_Datum(targLn); + *out_targ = targ; return TRUE; } } } + + if (singleSrc != NULL) { + /* + * Not so fast Mr. Smith! There was a suffix that encompassed + * the entire string, so we assume it was a transformation + * to the null suffix (thank you POSIX). We still prefer to + * find a double rule over a singleton, hence we leave this + * check until the end. + * + * XXX: Use emptySuff over suffNull? + */ + *out_src = singleSrc; + *out_targ = suffNull; + return TRUE; + } + return FALSE; } /* Return TRUE if the given string is a transformation rule, that is, a * concatenation of two known suffixes. */ Boolean -Suff_IsTransform(char *str) +Suff_IsTransform(const char *str) { - Suff *src, *targ; + Suff *src, *targ; return SuffParseTransform(str, &src, &targ); } -/* Add the transformation rule described by the line to the list of rules - * and place the transformation itself in the graph. +/* 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. * - * The node is placed on the end of the transforms Lst and links are made - * between the two suffixes mentioned in the target name. - * Input: - * line name of transformation to add + * name must have the form ".from.to" or just ".from" * * Results: - * The node created for the transformation in the transforms list + * The created or existing transformation node in the transforms list */ GNode * -Suff_AddTransform(char *line) +Suff_AddTransform(const char *name) { GNode *gn; /* GNode of transformation rule */ Suff *s, /* source suffix */ *t; /* target suffix */ - LstNode ln; /* Node for existing transformation */ Boolean ok; - ln = Lst_Find(transforms, SuffGNHasName, line); - if (ln == NULL) { + gn = FindTransformByName(name); + if (gn == NULL) { /* * Make a new graph node for the transformation. It will be filled in * by the Parse module. */ - gn = Targ_NewGN(line); + gn = Targ_NewGN(name); Lst_Append(transforms, gn); } else { /* @@ -584,16 +520,15 @@ Suff_AddTransform(char *line) * free the commands themselves, because a given command can be * attached to several different transformations. */ - gn = LstNode_Datum(ln); Lst_Free(gn->commands); Lst_Free(gn->children); - gn->commands = Lst_Init(); - gn->children = Lst_Init(); + gn->commands = Lst_New(); + gn->children = Lst_New(); } gn->type = OP_TRANSFORM; - ok = SuffParseTransform(line, &s, &t); + ok = SuffParseTransform(name, &s, &t); assert(ok); (void)ok; @@ -610,24 +545,18 @@ Suff_AddTransform(char *line) /* Handle the finish of a transformation definition, removing the * transformation from the graph if it has neither commands nor sources. - * This is a callback procedure for the Parse module via Lst_ForEach. * * If the node has no commands or children, the children and parents lists * of the affected suffixes are altered. * * Input: - * gnp Node for transformation - * - * Results: - * 0, so that Lst_ForEach continues + * gn Node for transformation */ -int -Suff_EndTransform(void *gnp, void *dummy MAKE_ATTR_UNUSED) +void +Suff_EndTransform(GNode *gn) { - GNode *gn = (GNode *)gnp; - if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(gn->cohorts)) - gn = LstNode_Datum(Lst_Last(gn->cohorts)); + gn = gn->cohorts->last->datum; if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && Lst_IsEmpty(gn->children)) { @@ -638,7 +567,7 @@ Suff_EndTransform(void *gnp, void *dummy MAKE_ATTR_UNUSED) * actual transformation rules. (e.g. .DEFAULT) */ if (SuffParseTransform(gn->name, &s, &t)) { - Lst p; + SuffList *p; SUFF_DEBUG2("deleting transformation from `%s' to `%s'\n", s->name, t->name); @@ -664,13 +593,11 @@ Suff_EndTransform(void *gnp, void *dummy MAKE_ATTR_UNUSED) SuffRemove(p, t); } } else if (gn->type & OP_TRANSFORM) { - SUFF_DEBUG1("transformation %s complete\n", gn->name); + SUFF_DEBUG1("transformation %s complete\n", gn->name); } - - return 0; } -/* Called from Suff_AddSuffix via Lst_ForEach to search through the list of +/* 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 @@ -680,108 +607,82 @@ Suff_EndTransform(void *gnp, void *dummy MAKE_ATTR_UNUSED) * transformation rules exist for it. * * Input: - * transformp Transformation to test - * sp Suffix to rebuild - * - * Results: - * 0, so that Lst_ForEach continues + * transform Transformation to test + * suff Suffix to rebuild */ -static int -SuffRebuildGraph(void *transformp, void *sp) +static void +SuffRebuildGraph(GNode *transform, Suff *suff) { - GNode *transform = (GNode *)transformp; - Suff *s = (Suff *)sp; - char *cp; - LstNode ln; - Suff *s2; - SuffSuffGetSuffixArgs sd; + const char *name = transform->name; + size_t nameLen = strlen(name); + const char *toName; /* * First see if it is a transformation from this suffix. */ - cp = UNCONST(SuffStrIsPrefix(s->name, transform->name)); - if (cp != NULL) { - ln = Lst_Find(sufflist, SuffSuffHasName, cp); - if (ln != NULL) { - /* - * Found target. Link in and return, since it can't be anything - * else. - */ - s2 = LstNode_Datum(ln); - SuffInsert(s2->children, s); - SuffInsert(s->parents, s2); - return 0; + toName = SuffStrIsPrefix(suff->name, name); + if (toName != NULL) { + Suff *to = FindSuffByName(toName); + if (to != NULL) { + /* Link in and return, since it can't be anything else. */ + SuffInsert(to->children, suff); + SuffInsert(suff->parents, to); + return; } } /* * Not from, maybe to? */ - sd.len = strlen(transform->name); - sd.ename = transform->name + sd.len; - cp = SuffSuffGetSuffix(s, &sd); - if (cp != NULL) { - /* - * Null-terminate the source suffix in order to find it. - */ - cp[1] = '\0'; - ln = Lst_Find(sufflist, SuffSuffHasName, transform->name); - /* - * Replace the start of the target suffix - */ - cp[1] = s->name[0]; - if (ln != NULL) { - /* - * Found it -- establish the proper relationship - */ - s2 = LstNode_Datum(ln); - SuffInsert(s->children, s2); - SuffInsert(s2->parents, s); + toName = SuffSuffGetSuffix(suff, nameLen, name + nameLen); + if (toName != NULL) { + Suff *from = FindSuffByNameLen(name, (size_t)(toName - name)); + + if (from != NULL) { + /* establish the proper relationship */ + SuffInsert(suff->children, from); + SuffInsert(from->parents, suff); } } - return 0; } -/* Called from Suff_AddSuffix via Lst_ForEach to search through the list of - * existing targets and find if any of the existing targets can be turned - * into a transformation rule. +/* 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: - * 1 if a new main target has been selected, 0 otherwise. + * TRUE iff a new main target has been selected. */ -static int -SuffScanTargets(void *targetp, void *gsp) +static Boolean +SuffScanTargets(GNode *target, GNode **inout_main, Suff *gs_s, Boolean *gs_r) { - GNode *target = (GNode *)targetp; - GNodeSuff *gs = (GNodeSuff *)gsp; - Suff *s, *t; - char *ptr; + Suff *s, *t; + char *ptr; - if (*gs->gn == NULL && gs->r && (target->type & OP_NOTARGET) == 0) { - *gs->gn = target; + if (*inout_main == NULL && *gs_r && !(target->type & OP_NOTARGET)) { + *inout_main = target; Targ_SetMain(target); - return 1; + return TRUE; } if (target->type == OP_TRANSFORM) - return 0; + return FALSE; - if ((ptr = strstr(target->name, gs->s->name)) == NULL || + if ((ptr = strstr(target->name, gs_s->name)) == NULL || ptr == target->name) - return 0; + return FALSE; if (SuffParseTransform(target->name, &s, &t)) { - if (*gs->gn == target) { - gs->r = TRUE; - *gs->gn = NULL; + if (*inout_main == target) { + *gs_r = TRUE; + *inout_main = NULL; Targ_SetMain(NULL); } Lst_Free(target->children); - target->children = Lst_Init(); + target->children = Lst_New(); target->type = OP_TRANSFORM; /* * link the two together in the proper relationship and order @@ -791,7 +692,24 @@ SuffScanTargets(void *targetp, void *gsp) SuffInsert(t->children, s); SuffInsert(s->parents, t); } - return 0; + return FALSE; +} + +/* Look at all existing targets to see if adding this suffix will make one + * of the current targets mutate into a suffix rule. + * + * This is ugly, but other makes treat all targets that start with a '.' as + * suffix rules. */ +static void +UpdateTargets(GNode **inout_main, Suff *s) +{ + Boolean r = FALSE; + GNodeListNode *ln; + for (ln = Targ_List()->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + if (SuffScanTargets(gn, inout_main, s, &r)) + break; + } } /* Add the suffix to the end of the list of known suffixes. @@ -806,49 +724,33 @@ SuffScanTargets(void *targetp, void *gsp) * name the name of the suffix to add */ void -Suff_AddSuffix(const char *name, GNode **gn) +Suff_AddSuffix(const char *name, GNode **inout_main) { - Suff *s; /* new suffix descriptor */ - LstNode ln; - GNodeSuff gs; + GNodeListNode *ln; - ln = Lst_Find(sufflist, SuffSuffHasName, name); - if (ln == NULL) { - s = SuffNew(name); + Suff *s = FindSuffByName(name); + if (s != NULL) + return; - Lst_Append(sufflist, s); - /* - * We also look at our existing targets list to see if adding - * this suffix will make one of our current targets mutate into - * a suffix rule. This is ugly, but other makes treat all targets - * that start with a . as suffix rules. - */ - gs.gn = gn; - gs.s = s; - gs.r = FALSE; - Lst_ForEach(Targ_List(), SuffScanTargets, &gs); - /* - * Look for any existing transformations from or to this suffix. - * XXX: Only do this after a Suff_ClearSuffixes? - */ - Lst_ForEach(transforms, SuffRebuildGraph, s); - } + s = SuffNew(name); + Lst_Append(sufflist, s); + + UpdateTargets(inout_main, s); + + /* + * Look for any existing transformations from or to this suffix. + * XXX: Only do this after a Suff_ClearSuffixes? + */ + for (ln = transforms->first; ln != NULL; ln = ln->next) + SuffRebuildGraph(ln->datum, s); } /* Return the search path for the given suffix, or NULL. */ -Lst -Suff_GetPath(char *sname) +SearchPath * +Suff_GetPath(const char *sname) { - LstNode ln; - Suff *s; - - ln = Lst_Find(sufflist, SuffSuffHasName, sname); - if (ln == NULL) { - return NULL; - } else { - s = LstNode_Datum(ln); - return s->searchPath; - } + Suff *s = FindSuffByName(sname); + return s != NULL ? s->searchPath : NULL; } /* Extend the search paths for all suffixes to include the default search @@ -863,37 +765,33 @@ Suff_GetPath(char *sname) void Suff_DoPaths(void) { - Suff *s; - LstNode ln; - char *ptr; - Lst inIncludes; /* Cumulative .INCLUDES path */ - Lst inLibs; /* Cumulative .LIBS path */ + SuffListNode *ln; + char *ptr; + SearchPath *inIncludes; /* Cumulative .INCLUDES path */ + SearchPath *inLibs; /* Cumulative .LIBS path */ + inIncludes = Lst_New(); + inLibs = Lst_New(); - inIncludes = Lst_Init(); - inLibs = Lst_Init(); - - Lst_Open(sufflist); - while ((ln = Lst_Next(sufflist)) != NULL) { - s = LstNode_Datum(ln); + for (ln = sufflist->first; ln != NULL; ln = ln->next) { + Suff *s = ln->datum; if (!Lst_IsEmpty(s->searchPath)) { #ifdef INCLUDES if (s->flags & SUFF_INCLUDE) { Dir_Concat(inIncludes, s->searchPath); } -#endif /* INCLUDES */ +#endif #ifdef LIBRARIES if (s->flags & SUFF_LIBRARY) { Dir_Concat(inLibs, s->searchPath); } -#endif /* LIBRARIES */ +#endif Dir_Concat(s->searchPath, dirSearchPath); } else { Lst_Destroy(s->searchPath, Dir_Destroy); - s->searchPath = Lst_Copy(dirSearchPath, Dir_CopyDir); + s->searchPath = Dir_CopyDirSearchPath(); } } - Lst_Close(sufflist); Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL); free(ptr); @@ -913,16 +811,11 @@ Suff_DoPaths(void) * sname Name of the suffix to mark */ void -Suff_AddInclude(char *sname) +Suff_AddInclude(const char *sname) { - LstNode ln; - Suff *s; - - ln = Lst_Find(sufflist, SuffSuffHasName, sname); - if (ln != NULL) { - s = LstNode_Datum(ln); - s->flags |= SUFF_INCLUDE; - } + Suff *suff = FindSuffByName(sname); + if (suff != NULL) + suff->flags |= SUFF_INCLUDE; } /* Add the given suffix as a type of file which is a library. @@ -936,82 +829,79 @@ Suff_AddInclude(char *sname) void Suff_AddLib(const char *sname) { - LstNode ln; - Suff *s; - - ln = Lst_Find(sufflist, SuffSuffHasName, sname); - if (ln != NULL) { - s = LstNode_Datum(ln); - s->flags |= SUFF_LIBRARY; - } + Suff *suff = FindSuffByName(sname); + if (suff != NULL) + suff->flags |= SUFF_LIBRARY; } /********** Implicit Source Search Functions *********/ +#ifdef DEBUG_SRC +static void +SrcList_PrintAddrs(SrcList *srcList) +{ + SrcListNode *ln; + for (ln = srcList->first; ln != NULL; ln = ln->next) + debug_printf(" %p", ln->datum); + debug_printf("\n"); +} +#endif + +static Src * +SrcNew(char *name, char *pref, Suff *suff, Src *parent, GNode *gn) +{ + Src *src = bmake_malloc(sizeof *src); + + src->file = name; + src->pref = pref; + src->suff = suff; + src->parent = parent; + src->node = gn; + src->children = 0; +#ifdef DEBUG_SRC + src->childrenList = Lst_New(); +#endif + + return src; +} + +static void +SuffAddSrc(Suff *suff, SrcList *srcList, Src *targ, char *srcName, + const char *debug_tag) +{ + Src *s2 = SrcNew(srcName, targ->pref, suff, targ, NULL); + suff->refCount++; + targ->children++; + Lst_Append(srcList, s2); +#ifdef DEBUG_SRC + Lst_Append(targ->childrenList, s2); + debug_printf("%s add suff %p src %p to list %p:", + debug_tag, targ, s2, srcList); + SrcList_PrintAddrs(srcList); +#endif +} + /* Add a suffix as a Src structure to the given list with its parent * being the given Src structure. If the suffix is the null suffix, * the prefix is used unaltered as the file name in the Src structure. * * Input: - * sp suffix for which to create a Src structure - * lsp list and parent for the new Src - * - * Results: - * 0, so that Lst_ForEach continues + * suff suffix for which to create a Src structure + * srcList list for the new Src + * targ parent for the new Src */ -static int -SuffAddSrc(void *sp, void *lsp) +static void +SuffAddSources(Suff *suff, SrcList *srcList, Src *targ) { - Suff *s = (Suff *)sp; - LstSrc *ls = (LstSrc *)lsp; - Src *s2; /* new Src structure */ - Src *targ; /* Target structure */ - - targ = ls->s; - - if ((s->flags & SUFF_NULL) && (*s->name != '\0')) { + if ((suff->flags & SUFF_NULL) && suff->name[0] != '\0') { /* * If the suffix has been marked as the NULL suffix, also create a Src * structure for a file with no suffix attached. Two birds, and all * that... */ - s2 = bmake_malloc(sizeof(Src)); - s2->file = bmake_strdup(targ->pref); - s2->pref = targ->pref; - s2->parent = targ; - s2->node = NULL; - s2->suff = s; - s->refCount++; - s2->children = 0; - targ->children += 1; - Lst_Append(ls->l, s2); -#ifdef DEBUG_SRC - s2->cp = Lst_Init(); - Lst_Append(targ->cp, s2); - fprintf(debug_file, "1 add %p %p to %p:", targ, s2, ls->l); - Lst_ForEach(ls->l, PrintAddr, NULL); - fprintf(debug_file, "\n"); -#endif + SuffAddSrc(suff, srcList, targ, bmake_strdup(targ->pref), "1"); } - s2 = bmake_malloc(sizeof(Src)); - s2->file = str_concat2(targ->pref, s->name); - s2->pref = targ->pref; - s2->parent = targ; - s2->node = NULL; - s2->suff = s; - s->refCount++; - s2->children = 0; - targ->children += 1; - Lst_Append(ls->l, s2); -#ifdef DEBUG_SRC - s2->cp = Lst_Init(); - Lst_Append(targ->cp, s2); - fprintf(debug_file, "2 add %p %p to %p:", targ, s2, ls->l); - Lst_ForEach(ls->l, PrintAddr, NULL); - fprintf(debug_file, "\n"); -#endif - - return 0; + SuffAddSrc(suff, srcList, targ, str_concat2(targ->pref, suff->name), "2"); } /* Add all the children of targ as Src structures to the given list. @@ -1021,66 +911,60 @@ SuffAddSrc(void *sp, void *lsp) * targ Src structure to use as the parent */ static void -SuffAddLevel(Lst l, Src *targ) +SuffAddLevel(SrcList *l, Src *targ) { - LstSrc ls; - - ls.s = targ; - ls.l = l; - - Lst_ForEach(targ->suff->children, SuffAddSrc, &ls); + SrcListNode *ln; + for (ln = targ->suff->children->first; ln != NULL; ln = ln->next) { + Suff *childSuff = ln->datum; + SuffAddSources(childSuff, l, targ); + } } -/* Free the first Src in the list that doesn't have a reference count. +/* Free the first Src in the list that is not referenced anymore. * Return whether a Src was removed. */ static Boolean -SuffRemoveSrc(Lst l) +SuffRemoveSrc(SrcList *l) { - LstNode ln; - Src *s; - - Lst_Open(l); + SrcListNode *ln; #ifdef DEBUG_SRC - fprintf(debug_file, "cleaning %lx: ", (unsigned long) l); - Lst_ForEach(l, PrintAddr, NULL); - fprintf(debug_file, "\n"); + debug_printf("cleaning list %p:", l); + SrcList_PrintAddrs(l); #endif - while ((ln = Lst_Next(l)) != NULL) { - s = LstNode_Datum(ln); + for (ln = l->first; ln != NULL; ln = ln->next) { + Src *s = ln->datum; + if (s->children == 0) { free(s->file); - if (!s->parent) + if (s->parent == NULL) free(s->pref); else { #ifdef DEBUG_SRC - LstNode ln2 = Lst_FindDatum(s->parent->cp, s); + SrcListNode *ln2 = Lst_FindDatum(s->parent->childrenList, s); if (ln2 != NULL) - Lst_Remove(s->parent->cp, ln2); + Lst_Remove(s->parent->childrenList, ln2); #endif - --s->parent->children; + s->parent->children--; } #ifdef DEBUG_SRC - fprintf(debug_file, "free: [l=%p] p=%p %d\n", l, s, s->children); - Lst_Free(s->cp); + debug_printf("free: list %p src %p children %d\n", + l, s, s->children); + Lst_Free(s->childrenList); #endif Lst_Remove(l, ln); free(s); - Lst_Close(l); return TRUE; } #ifdef DEBUG_SRC else { - fprintf(debug_file, "keep: [l=%p] p=%p %d: ", l, s, s->children); - Lst_ForEach(s->cp, PrintAddr, NULL); - fprintf(debug_file, "\n"); + debug_printf("keep: list %p src %p children %d:", + l, s, s->children); + SrcList_PrintAddrs(s->childrenList); } #endif } - Lst_Close(l); - return FALSE; } @@ -1093,50 +977,49 @@ SuffRemoveSrc(Lst l) * The lowest structure in the chain of transformations, or NULL. */ static Src * -SuffFindThem(Lst srcs, Lst slst) +SuffFindThem(SrcList *srcs, SrcList *slst) { - Src *s; /* current Src */ - Src *rs; /* returned Src */ - char *ptr; - - rs = NULL; + Src *retsrc = NULL; while (!Lst_IsEmpty(srcs)) { - s = Lst_Dequeue(srcs); + Src *src = Lst_Dequeue(srcs); - SUFF_DEBUG1("\ttrying %s...", s->file); + SUFF_DEBUG1("\ttrying %s...", src->file); /* * A file is considered to exist if either a node exists in the * graph for it or the file actually exists. */ - if (Targ_FindNode(s->file, TARG_NOCREATE) != NULL) { + if (Targ_FindNode(src->file) != NULL) { #ifdef DEBUG_SRC - fprintf(debug_file, "remove %p from %p\n", s, srcs); + debug_printf("remove from list %p src %p\n", srcs, src); #endif - rs = s; + retsrc = src; break; } - if ((ptr = Dir_FindFile(s->file, s->suff->searchPath)) != NULL) { - rs = s; + { + char *file = Dir_FindFile(src->file, src->suff->searchPath); + if (file != NULL) { + retsrc = src; #ifdef DEBUG_SRC - fprintf(debug_file, "remove %p from %p\n", s, srcs); + debug_printf("remove from list %p src %p\n", srcs, src); #endif - free(ptr); - break; + free(file); + break; + } } SUFF_DEBUG0("not there\n"); - SuffAddLevel(srcs, s); - Lst_Append(slst, s); + SuffAddLevel(srcs, src); + Lst_Append(slst, src); } - if (rs) { + if (retsrc) { SUFF_DEBUG0("got it\n"); } - return rs; + return retsrc; } /* See if any of the children of the target in the Src structure is one from @@ -1150,29 +1033,23 @@ SuffFindThem(Lst srcs, Lst slst) * The Src of the "winning" child, or NULL. */ static Src * -SuffFindCmds(Src *targ, Lst slst) +SuffFindCmds(Src *targ, SrcList *slst) { - LstNode ln; /* General-purpose list node */ - GNode *t, /* Target GNode */ - *s; /* Source GNode */ - int prefLen;/* The length of the defined prefix */ - Suff *suff; /* Suffix on matching beastie */ - Src *ret; /* Return value */ - char *cp; - - t = targ->node; - Lst_Open(t->children); + GNodeListNode *gln; + GNode *tgn; /* Target GNode */ + GNode *sgn; /* Source GNode */ + size_t prefLen; /* The length of the defined prefix */ + Suff *suff; /* Suffix on matching beastie */ + Src *ret; /* Return value */ + char *cp; + + tgn = targ->node; prefLen = strlen(targ->pref); - for (;;) { - ln = Lst_Next(t->children); - if (ln == NULL) { - Lst_Close(t->children); - return NULL; - } - s = LstNode_Datum(ln); + for (gln = tgn->children->first; gln != NULL; gln = gln->next) { + sgn = gln->datum; - if (s->type & OP_OPTIONAL && Lst_IsEmpty(t->commands)) { + 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. @@ -1183,9 +1060,9 @@ SuffFindCmds(Src *targ, Lst slst) continue; } - cp = strrchr(s->name, '/'); + cp = strrchr(sgn->name, '/'); if (cp == NULL) { - cp = s->name; + cp = sgn->name; } else { cp++; } @@ -1195,16 +1072,16 @@ SuffFindCmds(Src *targ, Lst slst) * The node matches the prefix ok, see if it has a known * suffix. */ - ln = Lst_Find(sufflist, SuffSuffHasName, &cp[prefLen]); - if (ln == NULL) + suff = FindSuffByName(cp + prefLen); + if (suff == NULL) continue; + /* * It even has a known suffix, see if there's a transformation * defined between the node's suffix and the target's suffix. * * XXX: Handle multi-stage transformations here, too. */ - suff = LstNode_Datum(ln); /* XXX: Can targ->suff be NULL here? */ if (targ->suff != NULL && @@ -1212,34 +1089,29 @@ SuffFindCmds(Src *targ, Lst slst) break; } + if (gln == NULL) + return NULL; + /* * Hot Damn! Create a new Src structure to describe * this transformation (making sure to duplicate the * source node's name so Suff_FindDeps can free it * again (ick)), and return the new structure. */ - ret = bmake_malloc(sizeof(Src)); - ret->file = bmake_strdup(s->name); - ret->pref = targ->pref; - ret->suff = suff; + ret = SrcNew(bmake_strdup(sgn->name), targ->pref, suff, targ, sgn); suff->refCount++; - ret->parent = targ; - ret->node = s; - ret->children = 0; - targ->children += 1; + targ->children++; #ifdef DEBUG_SRC - ret->cp = Lst_Init(); - fprintf(debug_file, "3 add %p %p\n", targ, ret); - Lst_Append(targ->cp, ret); + debug_printf("3 add targ %p ret %p\n", targ, ret); + Lst_Append(targ->childrenList, ret); #endif Lst_Append(slst, ret); - SUFF_DEBUG1("\tusing existing source %s\n", s->name); - Lst_Close(t->children); + SUFF_DEBUG1("\tusing existing source %s\n", sgn->name); return ret; } /* Expand the names of any children of a given node that contain variable - * invocations or file wildcards into actual targets. + * 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. @@ -1249,11 +1121,11 @@ SuffFindCmds(Src *targ, Lst slst) * pgn Parent node being processed */ static void -SuffExpandChildren(LstNode cln, GNode *pgn) +SuffExpandChildren(GNodeListNode *cln, GNode *pgn) { - GNode *cgn = LstNode_Datum(cln); - GNode *gn; /* New source 8) */ - char *cp; /* Expanded value */ + GNode *cgn = cln->datum; + GNode *gn; /* New source 8) */ + char *cp; /* Expanded value */ if (!Lst_IsEmpty(cgn->order_pred) || !Lst_IsEmpty(cgn->order_succ)) /* It is all too hard to process the result of .ORDER */ @@ -1275,10 +1147,11 @@ SuffExpandChildren(LstNode cln, GNode *pgn) } SUFF_DEBUG1("Expanding \"%s\"...", cgn->name); - cp = Var_Subst(cgn->name, pgn, VARE_UNDEFERR|VARE_WANTRES); + (void)Var_Subst(cgn->name, pgn, VARE_UNDEFERR|VARE_WANTRES, &cp); + /* TODO: handle errors */ { - Lst members = Lst_Init(); + GNodeList *members = Lst_New(); if (cgn->type & OP_ARCHV) { /* @@ -1302,36 +1175,41 @@ SuffExpandChildren(LstNode cln, GNode *pgn) for (start = cp; *start == ' ' || *start == '\t'; start++) continue; - for (cp = start; *cp != '\0'; cp++) { + cp = start; + while (*cp != '\0') { if (*cp == ' ' || *cp == '\t') { /* * White-space -- terminate element, find the node, * add it, skip any further spaces. */ *cp++ = '\0'; - gn = Targ_FindNode(start, TARG_CREATE); + gn = Targ_GetNode(start); Lst_Append(members, gn); while (*cp == ' ' || *cp == '\t') { cp++; } - /* - * Adjust cp for increment at start of loop, but - * set start to first non-space. - */ - start = cp--; + start = cp; /* Continue at the next non-space. */ } else if (*cp == '$') { /* * Start of a variable spec -- contact variable module * to find the end so we can skip over it. */ + const char *nested_p = cp; const char *junk; - int len; void *freeIt; - junk = Var_Parse(cp, pgn, VARE_UNDEFERR|VARE_WANTRES, - &len, &freeIt); - if (junk != var_Error) { - cp += len - 1; + /* XXX: Why VARE_WANTRES when the result is not used? */ + (void)Var_Parse(&nested_p, pgn, + VARE_UNDEFERR|VARE_WANTRES, + &junk, &freeIt); + /* TODO: handle errors */ + if (junk == var_Error) { + Parse_Error(PARSE_FATAL, + "Malformed variable expression at \"%s\"", + cp); + cp++; + } else { + cp += nested_p - cp; } free(freeIt); @@ -1339,6 +1217,11 @@ SuffExpandChildren(LstNode cln, GNode *pgn) /* * 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++; } } @@ -1347,7 +1230,7 @@ SuffExpandChildren(LstNode cln, GNode *pgn) /* * Stuff left over -- add it to the list too */ - gn = Targ_FindNode(start, TARG_CREATE); + gn = Targ_GetNode(start); Lst_Append(members, gn); } /* @@ -1369,7 +1252,7 @@ SuffExpandChildren(LstNode cln, GNode *pgn) Lst_Append(gn->parents, pgn); pgn->unmade++; /* Expand wildcards on new node */ - SuffExpandWildcards(LstNode_Prev(cln), pgn); + SuffExpandWildcards(cln->prev, pgn); } Lst_Free(members); @@ -1391,12 +1274,10 @@ SuffExpandChildren(LstNode cln, GNode *pgn) } static void -SuffExpandWildcards(LstNode cln, GNode *pgn) +SuffExpandWildcards(GNodeListNode *cln, GNode *pgn) { - GNode *cgn = LstNode_Datum(cln); - GNode *gn; /* New source 8) */ - char *cp; /* Expanded value */ - Lst explist; /* List of expansions */ + GNode *cgn = cln->datum; + StringList *explist; if (!Dir_HasWildcards(cgn->name)) return; @@ -1404,17 +1285,18 @@ SuffExpandWildcards(LstNode cln, GNode *pgn) /* * Expand the word along the chosen path */ - explist = Lst_Init(); + explist = Lst_New(); Dir_Expand(cgn->name, Suff_FindPath(cgn), explist); while (!Lst_IsEmpty(explist)) { + GNode *gn; /* * Fetch next expansion off the list and find its GNode */ - cp = Lst_Dequeue(explist); + char *cp = Lst_Dequeue(explist); SUFF_DEBUG1("%s...", cp); - gn = Targ_FindNode(cp, TARG_CREATE); + gn = Targ_GetNode(cp); /* Add gn to the parents child list before the original child */ Lst_InsertBefore(pgn->children, cln, gn); @@ -1446,32 +1328,31 @@ SuffExpandWildcards(LstNode cln, GNode *pgn) * Results: * The appropriate path to search for the GNode. */ -Lst +SearchPath * Suff_FindPath(GNode* gn) { Suff *suff = gn->suffix; if (suff == NULL) { - SuffSuffGetSuffixArgs sd; /* Search string data */ - LstNode ln; - sd.len = strlen(gn->name); - sd.ename = gn->name + sd.len; - ln = Lst_Find(sufflist, SuffSuffIsSuffix, &sd); + char *name = gn->name; + size_t nameLen = strlen(gn->name); + SuffListNode *ln; + for (ln = sufflist->first; ln != NULL; ln = ln->next) + if (SuffSuffIsSuffix(ln->datum, nameLen, name + nameLen)) + break; SUFF_DEBUG1("Wildcard expanding \"%s\"...", gn->name); if (ln != NULL) - suff = LstNode_Datum(ln); + suff = ln->datum; /* XXX: Here we can save the suffix so we don't have to do this again */ } if (suff != NULL) { - SUFF_DEBUG1("suffix is \"%s\"...", suff->name); + SUFF_DEBUG1("suffix is \"%s\"...\n", suff->name); return suff->searchPath; } else { - /* - * Use default search path - */ - return dirSearchPath; + SUFF_DEBUG0("\n"); + return dirSearchPath; /* Use default search path */ } } @@ -1497,25 +1378,25 @@ Suff_FindPath(GNode* gn) static Boolean SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s) { - LstNode ln, nln; /* General node */ - char *tname; /* Name of transformation rule */ - GNode *gn; /* Node for same */ + GNodeListNode *ln, *nln; /* General node */ + char *tname; /* Name of transformation rule */ + GNode *gn; /* Node for same */ /* * Form the proper links between the target and source. */ Lst_Append(tGn->children, sGn); Lst_Append(sGn->parents, tGn); - tGn->unmade += 1; + tGn->unmade++; /* * Locate the transformation rule itself */ tname = str_concat2(s->name, t->name); - ln = Lst_Find(transforms, SuffGNHasName, tname); + gn = FindTransformByName(tname); free(tname); - if (ln == NULL) { + if (gn == NULL) { /* * Not really such a transformation rule (can happen when we're * called to link an OP_MEMBER and OP_ARCHV node), so return @@ -1524,14 +1405,12 @@ SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s) return FALSE; } - gn = LstNode_Datum(ln); - SUFF_DEBUG3("\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name); /* * Record last child for expansion purposes */ - ln = Lst_Last(tGn->children); + ln = tGn->children->last; /* * Pass the buck to Make_HandleUse to apply the rule @@ -1541,8 +1420,8 @@ SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s) /* * Deal with wildcards and variables in any acquired sources */ - for (ln = ln != NULL ? LstNode_Next(ln) : NULL; ln != NULL; ln = nln) { - nln = LstNode_Next(ln); + for (ln = ln != NULL ? ln->next : NULL; ln != NULL; ln = nln) { + nln = ln->next; SuffExpandChildren(ln, tGn); } @@ -1565,14 +1444,14 @@ SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s) * Same as Suff_FindDeps */ static void -SuffFindArchiveDeps(GNode *gn, Lst slst) +SuffFindArchiveDeps(GNode *gn, SrcList *slst) { - char *eoarch; /* End of archive portion */ - char *eoname; /* End of member portion */ - GNode *mem; /* Node for member */ - LstNode ln, nln; /* Next suffix node to check */ - Suff *ms; /* Suffix descriptor for member */ - char *name; /* Start of member's name */ + char *eoarch; /* End of archive portion */ + char *eoname; /* End of member portion */ + GNode *mem; /* Node for member */ + SuffListNode *ln, *nln; /* Next suffix node to check */ + Suff *ms; /* Suffix descriptor for member */ + char *name; /* Start of member's name */ /* * The node is an archive(member) pair. so we must find a @@ -1600,7 +1479,7 @@ SuffFindArchiveDeps(GNode *gn, Lst slst) * use for the archive without having to do a quadratic search over the * suffix list, backtracking for each one... */ - mem = Targ_FindNode(name, TARG_CREATE); + mem = Targ_GetNode(name); SuffFindDeps(mem, slst); /* @@ -1608,18 +1487,13 @@ SuffFindArchiveDeps(GNode *gn, Lst slst) */ Lst_Append(gn->children, mem); Lst_Append(mem->parents, gn); - gn->unmade += 1; + gn->unmade++; /* * Copy in the variables from the member node to this one. */ - { - char *freeIt; - Var_Set(PREFIX, Var_Value(PREFIX, mem, &freeIt), gn); - bmake_free(freeIt); - Var_Set(TARGET, Var_Value(TARGET, mem, &freeIt), gn); - bmake_free(freeIt); - } + Var_Set(PREFIX, GNode_VarPrefix(mem), gn); + Var_Set(TARGET, GNode_VarTarget(mem), gn); ms = mem->suffix; if (ms == NULL) { @@ -1646,8 +1520,8 @@ SuffFindArchiveDeps(GNode *gn, Lst slst) * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ - for (ln = Lst_First(gn->children); ln != NULL; ln = nln) { - nln = LstNode_Next(ln); + for (ln = gn->children->first; ln != NULL; ln = nln) { + nln = ln->next; SuffExpandChildren(ln, gn); } @@ -1658,20 +1532,18 @@ SuffFindArchiveDeps(GNode *gn, Lst slst) * through the entire list, we just look at suffixes to which the * member's suffix may be transformed... */ - SuffSuffGetSuffixArgs sd; /* Search string data */ + size_t nameLen = (size_t)(eoarch - gn->name); - /* - * Use first matching suffix... - */ - sd.len = eoarch - gn->name; - sd.ename = eoarch; - ln = Lst_Find(ms->parents, SuffSuffIsSuffix, &sd); + /* Use first matching suffix... */ + for (ln = ms->parents->first; ln != NULL; ln = ln->next) + if (SuffSuffIsSuffix(ln->datum, nameLen, eoarch)) + break; if (ln != NULL) { /* * Got one -- apply it */ - Suff *suff = LstNode_Datum(ln); + Suff *suff = ln->datum; if (!SuffApplyTransform(gn, mem, suff, ms)) { SUFF_DEBUG2("\tNo transformation from %s -> %s\n", ms->name, suff->name); @@ -1690,7 +1562,7 @@ SuffFindArchiveDeps(GNode *gn, Lst slst) * the user needn't provide a transformation from the member to the * archive. */ - if (OP_NOP(gn->type)) { + if (!GNode_IsTarget(gn)) { gn->type |= OP_DEPENDS; } @@ -1702,6 +1574,130 @@ SuffFindArchiveDeps(GNode *gn, Lst slst) mem->type |= OP_MEMBER | OP_JOIN | OP_MADE; } +static void +SuffFindNormalDepsKnown(const char *name, size_t nameLen, GNode *gn, + SrcList *srcs, SrcList *targs) +{ + SuffListNode *ln; + Src *targ; + char *pref; + + for (ln = sufflist->first; ln != NULL; ln = ln->next) { + Suff *suff = ln->datum; + if (!SuffSuffIsSuffix(suff, nameLen, name + nameLen)) + continue; + + pref = bmake_strldup(name, (size_t)(nameLen - suff->nameLen)); + targ = SrcNew(bmake_strdup(gn->name), pref, suff, NULL, gn); + suff->refCount++; + + /* + * Add nodes from which the target can be made + */ + SuffAddLevel(srcs, targ); + + /* + * Record the target so we can nuke it + */ + Lst_Append(targs, targ); + } +} + +static void +SuffFindNormalDepsUnknown(GNode *gn, const char *sopref, + SrcList *srcs, SrcList *targs) +{ + Src *targ; + + if (!Lst_IsEmpty(targs) || suffNull == NULL) + return; + + SUFF_DEBUG1("\tNo known suffix on %s. Using .NULL suffix\n", gn->name); + + targ = SrcNew(bmake_strdup(gn->name), bmake_strdup(sopref), + suffNull, NULL, gn); + targ->suff->refCount++; + + /* + * Only use the default suffix rules if we don't have commands + * defined for this gnode; traditional make programs used to + * not define suffix rules if the gnode had children but we + * don't do this anymore. + */ + if (Lst_IsEmpty(gn->commands)) + SuffAddLevel(srcs, targ); + else { + SUFF_DEBUG0("not "); + } + + SUFF_DEBUG0("adding suffix rules\n"); + + Lst_Append(targs, targ); +} + +/* + * Deal with finding the thing on the default search path. We + * always do that, not only if the node is only a source (not + * on the lhs of a dependency operator or [XXX] it has neither + * children or commands) as the old pmake did. + */ +static void +SuffFindNormalDepsPath(GNode *gn, Src *targ) +{ + if (gn->type & (OP_PHONY | OP_NOPATH)) + return; + + free(gn->path); + gn->path = Dir_FindFile(gn->name, + (targ == NULL ? dirSearchPath : + targ->suff->searchPath)); + if (gn->path == NULL) + return; + + Var_Set(TARGET, gn->path, gn); + + if (targ != NULL) { + /* + * Suffix known for the thing -- trim the suffix off + * the path to form the proper .PREFIX variable. + */ + size_t savep = strlen(gn->path) - targ->suff->nameLen; + char savec; + char *ptr; + + if (gn->suffix) + gn->suffix->refCount--; + gn->suffix = targ->suff; + gn->suffix->refCount++; + + savec = gn->path[savep]; + gn->path[savep] = '\0'; + + if ((ptr = strrchr(gn->path, '/')) != NULL) + ptr++; + else + ptr = gn->path; + + Var_Set(PREFIX, ptr, gn); + + gn->path[savep] = savec; + } else { + char *ptr; + + /* The .PREFIX gets the full path if the target has no known suffix. */ + if (gn->suffix) + gn->suffix->refCount--; + gn->suffix = NULL; + + if ((ptr = strrchr(gn->path, '/')) != NULL) + ptr++; + else + ptr = gn->path; + + Var_Set(PREFIX, ptr, gn); + } +} + /* Locate implicit dependencies for regular targets. * * Input: @@ -1711,33 +1707,25 @@ SuffFindArchiveDeps(GNode *gn, Lst slst) * Same as Suff_FindDeps */ static void -SuffFindNormalDeps(GNode *gn, Lst slst) +SuffFindNormalDeps(GNode *gn, SrcList *slst) { - char *eoname; /* End of name */ - char *sopref; /* Start of prefix */ - LstNode ln, nln; /* Next suffix node to check */ - Lst srcs; /* List of sources at which to look */ - Lst targs; /* List of targets to which things can be - * transformed. They all have the same file, - * but different suff and pref fields */ - Src *bottom; /* Start of found transformation path */ - Src *src; /* General Src pointer */ - char *pref; /* Prefix to use */ - Src *targ; /* General Src target pointer */ - SuffSuffGetSuffixArgs sd; /* Search string data */ - - - sd.len = strlen(gn->name); - sd.ename = eoname = gn->name + sd.len; - - sopref = gn->name; + SrcList *srcs; /* List of sources at which to look */ + SrcList *targs; /* List of targets to which things can be + * transformed. They all have the same file, + * but different suff and pref fields */ + Src *bottom; /* Start of found transformation path */ + Src *src; /* General Src pointer */ + char *pref; /* Prefix to use */ + Src *targ; /* General Src target pointer */ + + const char *name = gn->name; + size_t nameLen = strlen(name); /* * Begin at the beginning... */ - ln = Lst_First(sufflist); - srcs = Lst_Init(); - targs = Lst_Init(); + srcs = Lst_New(); + targs = Lst_New(); /* * We're caught in a catch-22 here. On the one hand, we want to use any @@ -1762,84 +1750,10 @@ SuffFindNormalDeps(GNode *gn, Lst slst) if (!(gn->type & OP_PHONY)) { - while (ln != NULL) { - /* - * Look for next possible suffix... - */ - ln = Lst_FindFrom(sufflist, ln, SuffSuffIsSuffix, &sd); + SuffFindNormalDepsKnown(name, nameLen, gn, srcs, targs); - if (ln != NULL) { - const char *eopref; - - /* - * Allocate a Src structure to which things can be transformed - */ - targ = bmake_malloc(sizeof(Src)); - targ->file = bmake_strdup(gn->name); - targ->suff = LstNode_Datum(ln); - targ->suff->refCount++; - targ->node = gn; - targ->parent = NULL; - targ->children = 0; -#ifdef DEBUG_SRC - targ->cp = Lst_Init(); -#endif - - eopref = eoname - targ->suff->nameLen; - targ->pref = bmake_strsedup(sopref, eopref); - - /* - * Add nodes from which the target can be made - */ - SuffAddLevel(srcs, targ); - - /* - * Record the target so we can nuke it - */ - Lst_Append(targs, targ); - - /* - * Search from this suffix's successor... - */ - ln = LstNode_Next(ln); - } - } - - /* - * Handle target of unknown suffix... - */ - if (Lst_IsEmpty(targs) && suffNull != NULL) { - SUFF_DEBUG1("\tNo known suffix on %s. Using .NULL suffix\n", - gn->name); - - targ = bmake_malloc(sizeof(Src)); - targ->file = bmake_strdup(gn->name); - targ->suff = suffNull; - targ->suff->refCount++; - targ->node = gn; - targ->parent = NULL; - targ->children = 0; - targ->pref = bmake_strdup(sopref); -#ifdef DEBUG_SRC - targ->cp = Lst_Init(); -#endif - - /* - * Only use the default suffix rules if we don't have commands - * defined for this gnode; traditional make programs used to - * not define suffix rules if the gnode had children but we - * don't do this anymore. - */ - if (Lst_IsEmpty(gn->commands)) - SuffAddLevel(srcs, targ); - else { - SUFF_DEBUG0("not "); - } - - SUFF_DEBUG0("adding suffix rules\n"); - - Lst_Append(targs, targ); - } + /* Handle target of unknown suffix... */ + SuffFindNormalDepsUnknown(gn, name, srcs, targs); /* * Using the list of possible sources built up from the target @@ -1853,7 +1767,7 @@ SuffFindNormalDeps(GNode *gn, Lst slst) * for setting the local variables. */ if (!Lst_IsEmpty(targs)) { - targ = LstNode_Datum(Lst_First(targs)); + targ = targs->first->datum; } else { targ = NULL; } @@ -1867,7 +1781,7 @@ SuffFindNormalDeps(GNode *gn, Lst slst) } } - Var_Set(TARGET, gn->path ? gn->path : gn->name, gn); + Var_Set(TARGET, GNode_Path(gn), gn); pref = (targ != NULL) ? targ->pref : gn->name; Var_Set(PREFIX, pref, gn); @@ -1876,73 +1790,19 @@ SuffFindNormalDeps(GNode *gn, Lst slst) * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ - for (ln = Lst_First(gn->children); ln != NULL; ln = nln) { - nln = LstNode_Next(ln); - SuffExpandChildren(ln, gn); + { + SuffListNode *ln, *nln; + for (ln = gn->children->first; ln != NULL; ln = nln) { + nln = ln->next; + SuffExpandChildren(ln, gn); + } } if (targ == NULL) { SUFF_DEBUG1("\tNo valid suffix on %s\n", gn->name); sfnd_abort: - /* - * Deal with finding the thing on the default search path. We - * always do that, not only if the node is only a source (not - * on the lhs of a dependency operator or [XXX] it has neither - * children or commands) as the old pmake did. - */ - if ((gn->type & (OP_PHONY|OP_NOPATH)) == 0) { - free(gn->path); - gn->path = Dir_FindFile(gn->name, - (targ == NULL ? dirSearchPath : - targ->suff->searchPath)); - if (gn->path != NULL) { - char *ptr; - Var_Set(TARGET, gn->path, gn); - - if (targ != NULL) { - /* - * Suffix known for the thing -- trim the suffix off - * the path to form the proper .PREFIX variable. - */ - int savep = strlen(gn->path) - targ->suff->nameLen; - char savec; - - if (gn->suffix) - gn->suffix->refCount--; - gn->suffix = targ->suff; - gn->suffix->refCount++; - - savec = gn->path[savep]; - gn->path[savep] = '\0'; - - if ((ptr = strrchr(gn->path, '/')) != NULL) - ptr++; - else - ptr = gn->path; - - Var_Set(PREFIX, ptr, gn); - - gn->path[savep] = savec; - } else { - /* - * The .PREFIX gets the full path if the target has - * no known suffix. - */ - if (gn->suffix) - gn->suffix->refCount--; - gn->suffix = NULL; - - if ((ptr = strrchr(gn->path, '/')) != NULL) - ptr++; - else - ptr = gn->path; - - Var_Set(PREFIX, ptr, gn); - } - } - } - + SuffFindNormalDepsPath(gn, targ); goto sfnd_return; } @@ -1995,7 +1855,7 @@ sfnd_abort: * Etc. */ if (bottom->node == NULL) { - bottom->node = Targ_FindNode(bottom->file, TARG_CREATE); + bottom->node = Targ_GetNode(bottom->file); } for (src = bottom; src->parent != NULL; src = src->parent) { @@ -2007,7 +1867,7 @@ sfnd_abort: src->node->suffix->refCount++; if (targ->node == NULL) { - targ->node = Targ_FindNode(targ->file, TARG_CREATE); + targ->node = Targ_GetNode(targ->file); } SuffApplyTransform(targ->node, src->node, @@ -2077,7 +1937,7 @@ Suff_FindDeps(GNode *gn) } static void -SuffFindDeps(GNode *gn, Lst slst) +SuffFindDeps(GNode *gn, SrcList *slst) { if (gn->type & OP_DEPS_FOUND) return; @@ -2086,7 +1946,7 @@ SuffFindDeps(GNode *gn, Lst slst) /* * Make sure we have these set, may get revised below. */ - Var_Set(TARGET, gn->path ? gn->path : gn->name, gn); + Var_Set(TARGET, GNode_Path(gn), gn); Var_Set(PREFIX, gn->name, gn); SUFF_DEBUG1("SuffFindDeps (%s)\n", gn->name); @@ -2102,14 +1962,11 @@ SuffFindDeps(GNode *gn, Lst slst) * set the TARGET variable to the node's name in order to give it a * value). */ - LstNode ln; - Suff *s; - - ln = Lst_Find(sufflist, SuffSuffHasName, LIBSUFF); + Suff *s = FindSuffByName(LIBSUFF); if (gn->suffix) gn->suffix->refCount--; - if (ln != NULL) { - gn->suffix = s = LstNode_Datum(ln); + if (s != NULL) { + gn->suffix = s; gn->suffix->refCount++; Arch_FindLib(gn, s->searchPath); } else { @@ -2136,26 +1993,23 @@ SuffFindDeps(GNode *gn, Lst slst) * name Name of null suffix */ void -Suff_SetNull(char *name) +Suff_SetNull(const char *name) { - Suff *s; - LstNode ln; - - ln = Lst_Find(sufflist, SuffSuffHasName, name); - if (ln != NULL) { - s = LstNode_Datum(ln); - if (suffNull != NULL) { - suffNull->flags &= ~SUFF_NULL; - } - s->flags |= SUFF_NULL; - /* - * XXX: Here's where the transformation mangling would take place - */ - suffNull = s; - } else { + Suff *s = FindSuffByName(name); + if (s == NULL) { Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.", - name); + name); + return; + } + + if (suffNull != NULL) { + suffNull->flags &= ~(unsigned)SUFF_NULL; } + s->flags |= SUFF_NULL; + /* + * XXX: Here's where the transformation mangling would take place + */ + suffNull = s; } /* Initialize the suffixes module. */ @@ -2163,11 +2017,11 @@ void Suff_Init(void) { #ifdef CLEANUP - suffClean = Lst_Init(); - sufflist = Lst_Init(); + suffClean = Lst_New(); + sufflist = Lst_New(); #endif - srclist = Lst_Init(); - transforms = Lst_Init(); + srclist = Lst_New(); + transforms = Lst_New(); /* * Create null suffix for single-suffix rules (POSIX). The thing doesn't @@ -2195,59 +2049,64 @@ Suff_End(void) /********************* DEBUGGING FUNCTIONS **********************/ -static int SuffPrintName(void *s, void *dummy MAKE_ATTR_UNUSED) +static void +PrintSuffNames(const char *prefix, SuffList *suffs) { + SuffListNode *ln; - fprintf(debug_file, "%s ", ((Suff *)s)->name); - return 0; + debug_printf("#\t%s: ", prefix); + for (ln = suffs->first; ln != NULL; ln = ln->next) { + Suff *suff = ln->datum; + debug_printf("%s ", suff->name); + } + debug_printf("\n"); } -static int -SuffPrintSuff(void *sp, void *dummy MAKE_ATTR_UNUSED) +static void +PrintSuff(Suff *s) { - Suff *s = (Suff *)sp; - - fprintf(debug_file, "# `%s' [%d] ", s->name, s->refCount); - + debug_printf("# \"%s\" (num %d, ref %d)", s->name, s->sNum, s->refCount); if (s->flags != 0) { char flags_buf[SuffFlags_ToStringSize]; - fprintf(debug_file, " (%s)", - Enum_FlagsToString(flags_buf, sizeof flags_buf, - s->flags, SuffFlags_ToStringSpecs)); + debug_printf(" (%s)", + Enum_FlagsToString(flags_buf, sizeof flags_buf, + s->flags, SuffFlags_ToStringSpecs)); } - fputc('\n', debug_file); - fprintf(debug_file, "#\tTo: "); - Lst_ForEach(s->parents, SuffPrintName, NULL); - fputc('\n', debug_file); - fprintf(debug_file, "#\tFrom: "); - Lst_ForEach(s->children, SuffPrintName, NULL); - fputc('\n', debug_file); - fprintf(debug_file, "#\tSearch Path: "); + debug_printf("\n"); + + PrintSuffNames("To", s->parents); + PrintSuffNames("From", s->children); + + debug_printf("#\tSearch Path: "); Dir_PrintPath(s->searchPath); - fputc('\n', debug_file); - return 0; + debug_printf("\n"); } -static int -SuffPrintTrans(void *tp, void *dummy MAKE_ATTR_UNUSED) +static void +PrintTransformation(GNode *t) { - GNode *t = (GNode *)tp; - - fprintf(debug_file, "%-16s: ", t->name); + debug_printf("%-16s:", t->name); Targ_PrintType(t->type); - fputc('\n', debug_file); - Lst_ForEach(t->commands, Targ_PrintCmd, NULL); - fputc('\n', debug_file); - return 0; + debug_printf("\n"); + Targ_PrintCmds(t); + debug_printf("\n"); } void Suff_PrintAll(void) { - fprintf(debug_file, "#*** Suffixes:\n"); - Lst_ForEach(sufflist, SuffPrintSuff, NULL); + debug_printf("#*** Suffixes:\n"); + { + SuffListNode *ln; + for (ln = sufflist->first; ln != NULL; ln = ln->next) + PrintSuff(ln->datum); + } - fprintf(debug_file, "#*** Transformations:\n"); - Lst_ForEach(transforms, SuffPrintTrans, NULL); + debug_printf("#*** Transformations:\n"); + { + GNodeListNode *ln; + for (ln = transforms->first; ln != NULL; ln = ln->next) + PrintTransformation(ln->datum); + } } @@ -1,4 +1,4 @@ -/* $NetBSD: targ.c,v 1.81 2020/09/01 20:54:00 rillig Exp $ */ +/* $NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,80 +68,67 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: targ.c,v 1.81 2020/09/01 20:54:00 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)targ.c 8.2 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: targ.c,v 1.81 2020/09/01 20:54:00 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - /*- * targ.c -- * Functions for maintaining the Lst allTargets. Target nodes are * kept in two structures: a Lst and a hash table. * * Interface: - * Targ_Init Initialization procedure. + * Targ_Init Initialization procedure. + * + * Targ_End Clean up the module * - * Targ_End Cleanup the module + * Targ_List Return the list of all targets so far. * - * Targ_List Return the list of all targets so far. + * Targ_NewGN Create a new GNode for the passed target + * (string). The node is *not* placed in the + * hash table, though all its fields are + * initialized. * - * Targ_NewGN Create a new GNode for the passed target - * (string). The node is *not* placed in the - * hash table, though all its fields are - * initialized. + * Targ_FindNode Find the node, or return NULL. * - * Targ_FindNode Find the node for a given target, creating - * and storing it if it doesn't exist and the - * flags are right (TARG_CREATE) + * Targ_GetNode Find the node, or create it. * - * Targ_FindList Given a list of names, find nodes for all - * of them. If a name doesn't exist and the - * TARG_NOCREATE flag was given, an error message - * is printed. Else, if a name doesn't exist, - * its node is created. + * Targ_NewInternalNode + * Create an internal node. * - * Targ_Ignore Return TRUE if errors should be ignored when - * creating the given target. + * Targ_FindList Given a list of names, find nodes for all + * of them, creating them as necessary. * - * Targ_Silent Return TRUE if we should be silent when - * creating the given target. + * Targ_Ignore Return TRUE if errors should be ignored when + * creating the given target. * - * Targ_Precious Return TRUE if the target is precious and - * should not be removed if we are interrupted. + * Targ_Silent Return TRUE if we should be silent when + * creating the given target. * - * Targ_Propagate Propagate information between related - * nodes. Should be called after the - * makefiles are parsed but before any - * action is taken. + * Targ_Precious Return TRUE if the target is precious and + * should not be removed if we are interrupted. + * + * Targ_Propagate Propagate information between related nodes. + * Should be called after the makefiles are parsed + * but before any action is taken. * * Debugging: - * Targ_PrintGraph Print out the entire graphm all variables - * and statistics for the directory cache. Should - * print something for suffixes, too, but... + * Targ_PrintGraph + * Print out the entire graphm all variables and + * statistics for the directory cache. Should print + * something for suffixes, too, but... */ -#include <stdio.h> -#include <time.h> +#include <time.h> + +#include "make.h" +#include "dir.h" -#include "make.h" -#include "dir.h" +/* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $"); -static Lst allTargets; /* the list of all targets found so far */ +static GNodeList *allTargets; /* the list of all targets found so far */ #ifdef CLEANUP -static Lst allGNs; /* List of all the GNodes */ +static GNodeList *allGNs; /* List of all the GNodes */ #endif -static Hash_Table targets; /* a hash table of same */ +static HashTable targets; /* a hash table of same */ -static int TargPrintOnlySrc(void *, void *); -static int TargPrintName(void *, void *); #ifdef CLEANUP static void TargFreeGN(void *); #endif @@ -149,8 +136,8 @@ static void TargFreeGN(void *); void Targ_Init(void) { - allTargets = Lst_Init(); - Hash_InitTable(&targets, 191); + allTargets = Lst_New(); + HashTable_Init(&targets); } void @@ -161,18 +148,18 @@ Targ_End(void) Lst_Free(allTargets); if (allGNs != NULL) Lst_Destroy(allGNs, TargFreeGN); - Hash_DeleteTable(&targets); + HashTable_Done(&targets); #endif } void Targ_Stats(void) { - Hash_DebugStats(&targets, "targets"); + HashTable_DebugStats(&targets, "targets"); } /* Return the list of all targets. */ -Lst +GNodeList * Targ_List(void) { return allTargets; @@ -182,7 +169,7 @@ Targ_List(void) * all gnodes. * * Input: - * name the name of the node, such as "clean", "src.c" + * name the name of the node, such as "clean", "src.c", ".END" */ GNode * Targ_NewGN(const char *name) @@ -194,30 +181,30 @@ Targ_NewGN(const char *name) gn->uname = NULL; gn->path = NULL; gn->type = name[0] == '-' && name[1] == 'l' ? OP_LIB : 0; - gn->unmade = 0; + gn->unmade = 0; gn->unmade_cohorts = 0; - gn->cohort_num[0] = 0; - gn->centurion = NULL; - gn->made = UNMADE; - gn->flags = 0; - gn->checked = 0; - gn->mtime = 0; - gn->cmgn = NULL; - gn->implicitParents = Lst_Init(); - gn->cohorts = Lst_Init(); - gn->parents = Lst_Init(); - gn->children = Lst_Init(); - gn->order_pred = Lst_Init(); - gn->order_succ = Lst_Init(); - Hash_InitTable(&gn->context, 0); - gn->commands = Lst_Init(); - gn->suffix = NULL; - gn->fname = NULL; - gn->lineno = 0; + gn->cohort_num[0] = '\0'; + gn->centurion = NULL; + gn->made = UNMADE; + gn->flags = 0; + gn->checked_seqno = 0; + gn->mtime = 0; + gn->youngestChild = NULL; + gn->implicitParents = Lst_New(); + gn->cohorts = Lst_New(); + gn->parents = Lst_New(); + gn->children = Lst_New(); + gn->order_pred = Lst_New(); + gn->order_succ = Lst_New(); + HashTable_Init(&gn->context); + gn->commands = Lst_New(); + gn->suffix = NULL; + gn->fname = NULL; + gn->lineno = 0; #ifdef CLEANUP if (allGNs == NULL) - allGNs = Lst_Init(); + allGNs = Lst_New(); Lst_Append(allGNs, gn); #endif @@ -228,7 +215,7 @@ Targ_NewGN(const char *name) static void TargFreeGN(void *gnp) { - GNode *gn = (GNode *)gnp; + GNode *gn = gnp; free(gn->name); free(gn->uname); @@ -240,53 +227,46 @@ TargFreeGN(void *gnp) Lst_Free(gn->children); Lst_Free(gn->order_succ); Lst_Free(gn->order_pred); - Hash_DeleteTable(&gn->context); + HashTable_Done(&gn->context); Lst_Free(gn->commands); /* XXX: does gn->suffix need to be freed? It is reference-counted. */ - /* gn->fname points to name allocated when file was opened, don't free */ free(gn); } #endif - -/* Find a node in the list using the given name for matching. - * If the node is created, it is added to the .ALLTARGETS list. - * - * Input: - * name the name to find - * flags flags governing events when target not found - * - * Results: - * The node in the list if it was. If it wasn't, return NULL if - * flags was TARG_NOCREATE or the newly created and initialized node - * if it was TARG_CREATE - */ +/* Get the existing global node, or return NULL. */ GNode * -Targ_FindNode(const char *name, int flags) +Targ_FindNode(const char *name) { - GNode *gn; /* node in that element */ - Hash_Entry *he = NULL; /* New or used hash entry for node */ - Boolean isNew; /* Set TRUE if Hash_CreateEntry had to create */ - /* an entry for the node */ - - if (!(flags & (TARG_CREATE | TARG_NOHASH))) { - he = Hash_FindEntry(&targets, name); - if (he == NULL) - return NULL; - return (GNode *)Hash_GetValue(he); - } + return HashTable_FindValue(&targets, name); +} - if (!(flags & TARG_NOHASH)) { - he = Hash_CreateEntry(&targets, name, &isNew); - if (!isNew) - return (GNode *)Hash_GetValue(he); +/* Get the existing global node, or create it. */ +GNode * +Targ_GetNode(const char *name) +{ + Boolean isNew; + HashEntry *he = HashTable_CreateEntry(&targets, name, &isNew); + if (!isNew) + return HashEntry_Get(he); + + { + GNode *gn = Targ_NewInternalNode(name); + HashEntry_Set(he, gn); + return gn; } +} - gn = Targ_NewGN(name); - if (!(flags & TARG_NOHASH)) - Hash_SetValue(he, 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 = Targ_NewGN(name); Var_Append(".ALLTARGETS", name, VAR_GLOBAL); Lst_Append(allTargets, gn); if (doing_depend) @@ -294,45 +274,30 @@ Targ_FindNode(const char *name, int flags) return gn; } -/* Make a complete list of GNodes from the given list of names. - * If flags is TARG_CREATE, nodes will be created for all names in - * names which do not yet have graph nodes. If flags is TARG_NOCREATE, - * an error message will be printed for each name which can't be found. - * - * Input: - * name list of names to find - * flags flags used if no node is found for a given name - * - * Results: - * A complete list of graph nodes corresponding to all instances of all - * the names in names. - */ -Lst -Targ_FindList(Lst names, int flags) +/* Return the .END node, which contains the commands to be executed when + * everything else is done. */ +GNode *Targ_GetEndNode(void) { - Lst nodes; /* result list */ - LstNode ln; /* name list element */ - GNode *gn; /* node in tLn */ - char *name; - - nodes = Lst_Init(); - - Lst_Open(names); - while ((ln = Lst_Next(names)) != NULL) { - name = LstNode_Datum(ln); - gn = Targ_FindNode(name, flags); - if (gn != NULL) { - /* - * Note: Lst_Append must come before the Lst_Concat so the nodes - * are added to the list in the order in which they were - * encountered in the makefile. - */ - Lst_Append(nodes, gn); - } else if (flags == TARG_NOCREATE) { - Error("\"%s\" -- target unknown.", name); - } + /* Save the node locally to avoid having to search for it all the time. */ + static GNode *endNode = NULL; + if (endNode == NULL) { + endNode = Targ_GetNode(".END"); + endNode->type = OP_SPECIAL; + } + return endNode; +} + +/* Return the named nodes, creating them as necessary. */ +GNodeList * +Targ_FindList(StringList *names) +{ + StringListNode *ln; + GNodeList *nodes = Lst_New(); + for (ln = names->first; ln != NULL; ln = ln->next) { + const char *name = ln->datum; + GNode *gn = Targ_GetNode(name); + Lst_Append(nodes, gn); } - Lst_Close(names); return nodes; } @@ -340,14 +305,14 @@ Targ_FindList(Lst names, int flags) Boolean Targ_Ignore(GNode *gn) { - return ignoreErrors || gn->type & OP_IGNORE; + return opts.ignoreErrors || gn->type & OP_IGNORE; } /* Return true if be silent when creating gn. */ Boolean Targ_Silent(GNode *gn) { - return beSilent || gn->type & OP_SILENT; + return opts.beSilent || gn->type & OP_SILENT; } /* See if the given target is precious. */ @@ -369,22 +334,35 @@ Targ_SetMain(GNode *gn) mainTarg = gn; } -static int -TargPrintName(void *gnp, void *pflags MAKE_ATTR_UNUSED) +static void +PrintNodeNames(GNodeList *gnodes) { - GNode *gn = (GNode *)gnp; - - fprintf(debug_file, "%s%s ", gn->name, gn->cohort_num); + GNodeListNode *node; - return 0; + for (node = gnodes->first; node != NULL; node = node->next) { + GNode *gn = node->datum; + debug_printf(" %s%s", gn->name, gn->cohort_num); + } } +static void +PrintNodeNamesLine(const char *label, GNodeList *gnodes) +{ + if (Lst_IsEmpty(gnodes)) + return; + debug_printf("# %s:", label); + PrintNodeNames(gnodes); + debug_printf("\n"); +} -int -Targ_PrintCmd(void *cmd, void *dummy MAKE_ATTR_UNUSED) +void +Targ_PrintCmds(GNode *gn) { - fprintf(debug_file, "\t%s\n", (char *)cmd); - return 0; + 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. @@ -392,8 +370,8 @@ Targ_PrintCmd(void *cmd, void *dummy MAKE_ATTR_UNUSED) char * Targ_FmtTime(time_t tm) { - struct tm *parts; - static char buf[128]; + struct tm *parts; + static char buf[128]; parts = localtime(&tm); (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts); @@ -406,8 +384,8 @@ Targ_PrintType(int type) { int tbit; -#define PRINTBIT(attr) case CONCAT(OP_,attr): fprintf(debug_file, "." #attr " "); break -#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))fprintf(debug_file, "." #attr " "); break +#define PRINTBIT(attr) case CONCAT(OP_,attr): debug_printf(" ." #attr); break +#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))debug_printf(" ." #attr); break type &= ~OP_OPMASK; @@ -416,22 +394,22 @@ Targ_PrintType(int type) type &= ~tbit; switch(tbit) { - PRINTBIT(OPTIONAL); - PRINTBIT(USE); - PRINTBIT(EXEC); - PRINTBIT(IGNORE); - PRINTBIT(PRECIOUS); - PRINTBIT(SILENT); - PRINTBIT(MAKE); - PRINTBIT(JOIN); - PRINTBIT(INVISIBLE); - PRINTBIT(NOTMAIN); - PRINTDBIT(LIB); + PRINTBIT(OPTIONAL); + PRINTBIT(USE); + PRINTBIT(EXEC); + PRINTBIT(IGNORE); + PRINTBIT(PRECIOUS); + PRINTBIT(SILENT); + PRINTBIT(MAKE); + PRINTBIT(JOIN); + PRINTBIT(INVISIBLE); + PRINTBIT(NOTMAIN); + PRINTDBIT(LIB); /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */ - case OP_MEMBER: if (DEBUG(TARG))fprintf(debug_file, ".MEMBER "); break; - PRINTDBIT(ARCHV); - PRINTDBIT(MADE); - PRINTDBIT(PHONY); + case OP_MEMBER: if (DEBUG(TARG))debug_printf(" .MEMBER"); break; + PRINTDBIT(ARCHV); + PRINTDBIT(MADE); + PRINTDBIT(PHONY); } } } @@ -452,102 +430,96 @@ made_name(GNodeMade made) } } -/* Print the contents of a node. */ -int -Targ_PrintNode(void *gnp, void *passp) +static const char * +GNode_OpName(const GNode *gn) { - GNode *gn = (GNode *)gnp; - int pass = passp ? *(int *)passp : 0; + switch (gn->type & OP_OPMASK) { + case OP_DEPENDS: + return ":"; + case OP_FORCE: + return "!"; + case OP_DOUBLEDEP: + return "::"; + } + return ""; +} - fprintf(debug_file, "# %s%s", gn->name, gn->cohort_num); - GNode_FprintDetails(debug_file, ", ", gn, "\n"); +/* 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 0; + return; - if (!OP_NOP(gn->type)) { - fprintf(debug_file, "#\n"); + if (GNode_IsTarget(gn)) { + debug_printf("#\n"); if (gn == mainTarg) { - fprintf(debug_file, "# *** MAIN TARGET ***\n"); + debug_printf("# *** MAIN TARGET ***\n"); } if (pass >= 2) { if (gn->unmade) { - fprintf(debug_file, "# %d unmade children\n", gn->unmade); + debug_printf("# %d unmade children\n", gn->unmade); } else { - fprintf(debug_file, "# No unmade children\n"); + debug_printf("# No unmade children\n"); } if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { if (gn->mtime != 0) { - fprintf(debug_file, "# last modified %s: %s\n", - Targ_FmtTime(gn->mtime), - made_name(gn->made)); + debug_printf("# last modified %s: %s\n", + Targ_FmtTime(gn->mtime), + made_name(gn->made)); } else if (gn->made != UNMADE) { - fprintf(debug_file, "# non-existent (maybe): %s\n", - made_name(gn->made)); + debug_printf("# non-existent (maybe): %s\n", + made_name(gn->made)); } else { - fprintf(debug_file, "# unmade\n"); + debug_printf("# unmade\n"); } } - if (!Lst_IsEmpty(gn->implicitParents)) { - fprintf(debug_file, "# implicit parents: "); - Lst_ForEach(gn->implicitParents, TargPrintName, NULL); - fprintf(debug_file, "\n"); - } + PrintNodeNamesLine("implicit parents", gn->implicitParents); } else { if (gn->unmade) - fprintf(debug_file, "# %d unmade children\n", gn->unmade); - } - if (!Lst_IsEmpty(gn->parents)) { - fprintf(debug_file, "# parents: "); - Lst_ForEach(gn->parents, TargPrintName, NULL); - fprintf(debug_file, "\n"); - } - if (!Lst_IsEmpty(gn->order_pred)) { - fprintf(debug_file, "# order_pred: "); - Lst_ForEach(gn->order_pred, TargPrintName, NULL); - fprintf(debug_file, "\n"); - } - if (!Lst_IsEmpty(gn->order_succ)) { - fprintf(debug_file, "# order_succ: "); - Lst_ForEach(gn->order_succ, TargPrintName, NULL); - fprintf(debug_file, "\n"); + debug_printf("# %d unmade children\n", gn->unmade); } + PrintNodeNamesLine("parents", gn->parents); + PrintNodeNamesLine("order_pred", gn->order_pred); + PrintNodeNamesLine("order_succ", gn->order_succ); - fprintf(debug_file, "%-16s", gn->name); - switch (gn->type & OP_OPMASK) { - case OP_DEPENDS: - fprintf(debug_file, ": "); break; - case OP_FORCE: - fprintf(debug_file, "! "); break; - case OP_DOUBLEDEP: - fprintf(debug_file, ":: "); break; - } + debug_printf("%-16s%s", gn->name, GNode_OpName(gn)); Targ_PrintType(gn->type); - Lst_ForEach(gn->children, TargPrintName, NULL); - fprintf(debug_file, "\n"); - Lst_ForEach(gn->commands, Targ_PrintCmd, NULL); - fprintf(debug_file, "\n\n"); + PrintNodeNames(gn->children); + debug_printf("\n"); + Targ_PrintCmds(gn); + debug_printf("\n\n"); if (gn->type & OP_DOUBLEDEP) { - Lst_ForEach(gn->cohorts, Targ_PrintNode, &pass); + Targ_PrintNodes(gn->cohorts, pass); } } - return 0; } -/* Print only those targets that are just a source. - * The name of each file is printed, preceded by #\t. */ -static int -TargPrintOnlySrc(void *gnp, void *dummy MAKE_ATTR_UNUSED) +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) { - GNode *gn = (GNode *)gnp; - if (!OP_NOP(gn->type)) - return 0; + GNodeListNode *ln; - fprintf(debug_file, "#\t%s [%s] ", - gn->name, gn->path ? gn->path : gn->name); - Targ_PrintType(gn->type); - fprintf(debug_file, "\n"); + for (ln = allTargets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + if (GNode_IsTarget(gn)) + continue; - return 0; + debug_printf("#\t%s [%s]", gn->name, GNode_Path(gn)); + Targ_PrintType(gn->type); + debug_printf("\n"); + } } /* Input: @@ -558,18 +530,18 @@ TargPrintOnlySrc(void *gnp, void *dummy MAKE_ATTR_UNUSED) void Targ_PrintGraph(int pass) { - fprintf(debug_file, "#*** Input graph:\n"); - Lst_ForEach(allTargets, Targ_PrintNode, &pass); - fprintf(debug_file, "\n\n"); - fprintf(debug_file, "#\n# Files that are only sources:\n"); - Lst_ForEach(allTargets, TargPrintOnlySrc, NULL); - fprintf(debug_file, "#*** Global Variables:\n"); + debug_printf("#*** Input graph:\n"); + Targ_PrintNodes(allTargets, pass); + debug_printf("\n\n"); + debug_printf("#\n# Files that are only sources:\n"); + PrintOnlySources(); + debug_printf("#*** Global Variables:\n"); Var_Dump(VAR_GLOBAL); - fprintf(debug_file, "#*** Command-line Variables:\n"); - Var_Dump(VAR_CMD); - fprintf(debug_file, "\n"); + debug_printf("#*** Command-line Variables:\n"); + Var_Dump(VAR_CMDLINE); + debug_printf("\n"); Dir_PrintDirectories(); - fprintf(debug_file, "\n"); + debug_printf("\n"); Suff_PrintAll(); } @@ -581,18 +553,19 @@ Targ_PrintGraph(int pass) void Targ_Propagate(void) { - LstNode pn, cn; + GNodeListNode *ln, *cln; - for (pn = Lst_First(allTargets); pn != NULL; pn = LstNode_Next(pn)) { - GNode *pgn = LstNode_Datum(pn); + for (ln = allTargets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + GNodeType type = gn->type; - if (!(pgn->type & OP_DOUBLEDEP)) + if (!(type & OP_DOUBLEDEP)) continue; - for (cn = Lst_First(pgn->cohorts); cn != NULL; cn = LstNode_Next(cn)) { - GNode *cgn = LstNode_Datum(cn); + for (cln = gn->cohorts->first; cln != NULL; cln = cln->next) { + GNode *cohort = cln->datum; - cgn->type |= pgn->type & ~OP_OPMASK; + cohort->type |= type & ~OP_OPMASK; } } } @@ -1,4 +1,4 @@ -/* $NetBSD: trace.c,v 1.15 2020/08/03 20:26:09 rillig Exp $ */ +/* $NetBSD: trace.c,v 1.21 2020/10/31 22:05:56 rillig Exp $ */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. @@ -29,16 +29,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: trace.c,v 1.15 2020/08/03 20:26:09 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -__RCSID("$NetBSD: trace.c,v 1.15 2020/08/03 20:26:09 rillig Exp $"); -#endif /* not lint */ -#endif - /*- * trace.c -- * handle logging of trace events generated by various parts of make. @@ -54,13 +44,12 @@ __RCSID("$NetBSD: trace.c,v 1.15 2020/08/03 20:26:09 rillig Exp $"); #include <sys/time.h> -#include <stdio.h> -#include <unistd.h> - #include "make.h" #include "job.h" #include "trace.h" +MAKE_RCSID("$NetBSD: trace.c,v 1.21 2020/10/31 22:05:56 rillig Exp $"); + static FILE *trfile; static pid_t trpid; const char *trwd; @@ -78,8 +67,10 @@ void Trace_Init(const char *pathname) { if (pathname != NULL) { - char *dontFreeIt; + void *dontFreeIt; trpid = getpid(); + /* XXX: This variable may get overwritten later, which + * would make trwd point to undefined behavior. */ trwd = Var_Value(".CURDIR", VAR_GLOBAL, &dontFreeIt); trfile = fopen(pathname, "a"); @@ -1,4 +1,4 @@ -/* $NetBSD: trace.h,v 1.3 2008/04/28 20:24:14 martin Exp $ */ +/* $NetBSD: trace.h,v 1.4 2020/10/18 17:19:54 rillig Exp $ */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. @@ -34,7 +34,7 @@ * Definitions pertaining to the tracing of jobs in parallel mode. */ -typedef enum { +typedef enum TrEvent { MAKESTART, MAKEEND, MAKEERROR, diff --git a/unit-tests/Makefile b/unit-tests/Makefile index 1566b177087a..0940d55671ee 100644 --- a/unit-tests/Makefile +++ b/unit-tests/Makefile @@ -1,6 +1,6 @@ -# $Id: Makefile,v 1.92 2020/09/02 18:39:29 sjg Exp $ +# $Id: Makefile,v 1.107 2020/11/02 00:40:25 sjg Exp $ # -# $NetBSD: Makefile,v 1.130 2020/09/02 05:33:57 rillig Exp $ +# $NetBSD: Makefile,v 1.181 2020/11/01 19:02:22 rillig Exp $ # # Unit tests for make(1) # @@ -34,7 +34,7 @@ # 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 # broken on FreeBSD, enabled in t_make.sh +#TESTS+= archive TESTS+= archive-suffix TESTS+= cmd-interrupt TESTS+= cmdline @@ -47,6 +47,7 @@ TESTS+= cond-cmp-numeric-le TESTS+= cond-cmp-numeric-lt TESTS+= cond-cmp-numeric-ne TESTS+= cond-cmp-string +TESTS+= cond-cmp-unary TESTS+= cond-func TESTS+= cond-func-commands TESTS+= cond-func-defined @@ -65,17 +66,22 @@ TESTS+= cond-token-number TESTS+= cond-token-plain TESTS+= cond-token-string TESTS+= cond-token-var +TESTS+= cond-undef-lint TESTS+= cond1 -TESTS+= cond2 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 @@ -99,6 +105,7 @@ TESTS+= deptgt-begin TESTS+= deptgt-default TESTS+= deptgt-delete_on_error TESTS+= deptgt-end +TESTS+= deptgt-end-jobs TESTS+= deptgt-error TESTS+= deptgt-ignore TESTS+= deptgt-interrupt @@ -120,6 +127,7 @@ TESTS+= deptgt-suffixes TESTS+= dir TESTS+= dir-expand-path TESTS+= directive +TESTS+= directive-dinclude TESTS+= directive-elif TESTS+= directive-elifdef TESTS+= directive-elifmake @@ -130,15 +138,20 @@ TESTS+= directive-endif TESTS+= directive-error TESTS+= directive-export TESTS+= directive-export-env +TESTS+= directive-export-gmake TESTS+= directive-export-literal TESTS+= directive-for TESTS+= directive-for-generating-endif +TESTS+= directive-hyphen-include TESTS+= directive-if TESTS+= directive-ifdef TESTS+= directive-ifmake TESTS+= directive-ifndef TESTS+= directive-ifnmake +TESTS+= directive-include +TESTS+= directive-include-fatal TESTS+= directive-info +TESTS+= directive-sinclude TESTS+= directive-undef TESTS+= directive-unexport TESTS+= directive-unexport-env @@ -156,12 +169,12 @@ TESTS+= export-env TESTS+= export-variants TESTS+= forloop TESTS+= forsubst -TESTS+= hash +TESTS+= hanoi-include TESTS+= impsrc TESTS+= include-main +#TESTS+= job-output-long-lines TESTS+= lint TESTS+= make-exported -TESTS+= misc TESTS+= moderrs TESTS+= modmatch TESTS+= modmisc @@ -171,7 +184,30 @@ TESTS+= opt TESTS+= opt-backwards TESTS+= opt-chdir TESTS+= opt-debug -TESTS+= opt-debug-g1 +TESTS+= opt-debug-all +TESTS+= opt-debug-archive +TESTS+= opt-debug-curdir +TESTS+= opt-debug-cond +TESTS+= opt-debug-dir +TESTS+= opt-debug-errors +TESTS+= opt-debug-file +TESTS+= opt-debug-for +TESTS+= opt-debug-graph1 +TESTS+= opt-debug-graph2 +TESTS+= opt-debug-graph3 +TESTS+= opt-debug-hash +#TESTS+= opt-debug-jobs +TESTS+= opt-debug-lint +TESTS+= opt-debug-loud +TESTS+= opt-debug-meta +TESTS+= opt-debug-making +TESTS+= opt-debug-no-rm +TESTS+= opt-debug-parse +TESTS+= opt-debug-suff +TESTS+= opt-debug-targets +TESTS+= opt-debug-varraw +TESTS+= opt-debug-var +TESTS+= opt-debug-x-trace TESTS+= opt-define TESTS+= opt-env TESTS+= opt-file @@ -194,6 +230,7 @@ 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 @@ -209,9 +246,20 @@ TESTS+= sh-leading-plus TESTS+= sh-meta-chars TESTS+= sh-multi-line TESTS+= sh-single-line -TESTS+= # suffixes # runs into an endless loop (try -dA) +TESTS+= shell-csh +TESTS+= shell-custom +TESTS+= shell-ksh +TESTS+= shell-sh +TESTS+= suff-add-later +TESTS+= suff-clear-regular +TESTS+= suff-clear-single +TESTS+= suff-lookup +TESTS+= suff-main +TESTS+= suff-rebuild +TESTS+= suff-transform-endless +TESTS+= suff-transform-expand +TESTS+= suff-transform-select TESTS+= sunshcmd -TESTS+= sysv TESTS+= ternary TESTS+= unexport TESTS+= unexport-env @@ -228,6 +276,8 @@ 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 @@ -310,10 +360,14 @@ 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-mod +TESTS+= varparse-undef-partial TESTS+= varquote TESTS+= varshell @@ -323,18 +377,16 @@ 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 # 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.archive= -dA -FLAGS.counter= -dv +FLAGS.cond-func-make= via-cmdline FLAGS.directive-ifmake= first second FLAGS.doterror= # none FLAGS.envfirst= -e FLAGS.export= # none -FLAGS.lint= -dL -k -FLAGS.opt-debug-g1= -dg1 FLAGS.opt-ignore= -i FLAGS.opt-keep-going= -k FLAGS.opt-no-action= -n @@ -345,32 +397,52 @@ FLAGS.opt-warnings-as-errors= -W FLAGS.order= -j1 FLAGS.recursive= -dL FLAGS.sh-leading-plus= -n -FLAGS.vardebug= -k -dv FROM_CMDLINE= -FLAGS.varmod-match-escape= -dv -FLAGS.varname-dot-shell= -dpv FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmline-plain' -# Some tests need extra post-processing. -SED_CMDS.opt-debug-g1= -e 's,${.CURDIR},CURDIR,' -SED_CMDS.opt-debug-g1+= -e '/Global Variables:/,/Suffixes:/d' +# Some tests need extra postprocessing. +SED_CMDS.export= \ + -e '/^[^=_A-Za-z0-9]*=/d' +# these all share the same requirement +.for t in export-all export-env +SED_CMDS.$t= ${SED_CMDS.export} +.endfor +SED_CMDS.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-debug-graph1= \ + -e 's,${.CURDIR},CURDIR,' +SED_CMDS.opt-debug-graph1+= \ + -e '/Global Variables:/,/Suffixes:/d' SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,<normalized: ...: not found>,' +SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(<pid>),' +SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid <pid>,' +SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process <pid>,' +SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: <pid>,' +# The "-q" may be there or not, see jobs.c, variable shells. +SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: sh\) -q,\1,' SED_CMDS.varmod-subst-regex+= \ -e 's,\(Regex compilation error:\).*,\1 (details omitted),' SED_CMDS.varmod-edge+= -e 's, line [0-9]*:, line omitted:,' SED_CMDS.varshell+= -e 's,^${.SHELL:T}: ,,' SED_CMDS.varshell+= -e '/command/s,No such.*,not found,' -SED_CMDS.varname-dot-shell= -e 's, = /.*, = (details omitted),' -SED_CMDS.varname-dot-shell+= -e 's,"/[^"]*","(details omitted)",' -SED_CMDS.varname-dot-shell+= -e 's,\[/[^]]*\],[(details omitted)],' +SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,' +SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,' +SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g' +SED_CMDS.varname-dot-shell+= -e 's,"/[^" ]*","(details omitted)",g' +SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g' # Some tests need an additional round of postprocessing. -POSTPROC.counter= ${TOOL_SED} -n -e '/:RELEVANT = yes/,/:RELEVANT = no/p' POSTPROC.deptgt-suffixes= \ ${TOOL_SED} -n -e '/^\#\*\*\* Suffixes/,/^\#\*/p' -POSTPROC.vardebug= ${TOOL_SED} -n -e '/:RELEVANT = yes/,/:RELEVANT = no/p' -POSTPROC.varmod-match-escape= ${TOOL_SED} -n -e '/^Pattern/p' -POSTPROC.varname-dot-shell= \ - awk '/\.SHELL/ || /^ParseReadLine/' +POSTPROC.varname= ${TOOL_SED} -n -e '/^MAGIC/p' -e '/^ORDER_/p' POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p' # Some tests reuse other tests, which makes them unnecessarily fragile. @@ -388,7 +460,11 @@ unexport-env.rawout: export.mk 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} @@ -419,22 +495,26 @@ LANG= C _MKMSG_TEST= : .endif -# the tests are actually done with sub-makes. +MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc + +# Each test is run in a sub-make, to keep the tests for interfering with +# each other, and because they use different environment variables and +# command line options. .SUFFIXES: .mk .rawout .out .mk.rawout: @${_MKMSG_TEST:Uecho '# test '} ${.PREFIX} @set -eu; \ cd ${.OBJDIR}; \ - env -i PATH="$$PATH" ${ENV.${.TARGET:R}} \ + env -i PATH="$$PATH" ${MAKE_TEST_ENV} ${ENV.${.PREFIX:T}} \ ${TEST_MAKE} \ -r -C ${.CURDIR} -f ${.IMPSRC} \ - ${FLAGS.${.TARGET:R}:U-k} \ + ${FLAGS.${.PREFIX:T}:U-k} \ > ${.TARGET}.tmp 2>&1 \ && status=$$? || status=$$?; \ echo $$status > ${.TARGET:R}.status @mv ${.TARGET}.tmp ${.TARGET} -# Post-process the test output so that the results can be compared. +# 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:,' @@ -446,9 +526,9 @@ _SED_CMDS+= -e 's,${.CURDIR:S,.,\\.,g}/,,g' _SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g' .rawout.out: - @${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.TARGET:R}} \ + @${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.PREFIX:T}} \ < ${.IMPSRC} > ${.TARGET}.tmp1 - @${POSTPROC.${.TARGET:R}:Ucat} < ${.TARGET}.tmp1 > ${.TARGET}.tmp2 + @${POSTPROC.${.PREFIX:T}:Ucat} < ${.TARGET}.tmp1 > ${.TARGET}.tmp2 @rm ${.TARGET}.tmp1 @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp2 @mv ${.TARGET}.tmp2 ${.TARGET} @@ -474,7 +554,12 @@ accept: done .if exists(${TEST_MAKE}) -${TESTS:=.rawout}: ${TEST_MAKE} ${.PARSEDIR}/Makefile +${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 <obj.mk> diff --git a/unit-tests/archive.exp b/unit-tests/archive.exp index a42b4f39e173..645add4f5899 100644 --- a/unit-tests/archive.exp +++ b/unit-tests/archive.exp @@ -1,13 +1,28 @@ +Making remove-archive rm -f libprog.a + +Making libprog.a out-of-date archive.mk modmisc.mk varmisc.mk ar cru libprog.a archive.mk modmisc.mk varmisc.mk ranlib libprog.a + +Making create-archive out-of-date libprog.a + +Making list-archive out-of-date libprog.a ar t libprog.a archive.mk modmisc.mk varmisc.mk + +Making list-archive-wildcard out-of-date archive-suffix.mk archive.mk ternary.mk +list-archive-wildcard: archive-suffix.mk list-archive-wildcard: archive.mk list-archive-wildcard: ternary.mk + +Making depend-on-existing-member out-of-date archive.mk depend-on-existing-member + `depend-on-nonexistent-member' is up to date. +Making remove-archive rm -f libprog.a + exit status 0 diff --git a/unit-tests/archive.mk b/unit-tests/archive.mk index c3b7e919eab2..2f91005f988c 100644 --- a/unit-tests/archive.mk +++ b/unit-tests/archive.mk @@ -1,18 +1,25 @@ -# $NetBSD: archive.mk,v 1.5 2020/08/23 17:51:24 rillig Exp $ +# $NetBSD: archive.mk,v 1.10 2020/10/09 06:44:42 rillig Exp $ # # Very basic demonstration of handling archives, based on the description # in PSD.doc/tutorial.ms. +# +# This test aims at covering the code, not at being an introduction to +# archive handling. That's why it is more complicated and detailed than +# strictly necessary. -ARCHIVE= libprog.${EXT.a} -FILES= archive.${EXT.mk} modmisc.${EXT.mk} varmisc.mk - -EXT.a= a -EXT.mk= mk +ARCHIVE= libprog.a +FILES= archive.mk modmisc.mk varmisc.mk MAKE_CMD= ${.MAKE} -f ${MAKEFILE} RUN?= @set -eu; all: +.if ${.PARSEDIR:tA} != ${.CURDIR:tA} + @cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR} +.endif +# The following targets create and remove files. The filesystem cache in +# dir.c would probably not handle this correctly, therefore each of the +# targets is run in its separate sub-make. ${RUN} ${MAKE_CMD} remove-archive ${RUN} ${MAKE_CMD} create-archive ${RUN} ${MAKE_CMD} list-archive @@ -21,25 +28,35 @@ all: ${RUN} ${MAKE_CMD} depend-on-nonexistent-member ${RUN} ${MAKE_CMD} remove-archive -create-archive: ${ARCHIVE} -${ARCHIVE}: ${ARCHIVE}(${FILES}) - ar cru ${.TARGET} ${.OODATE} +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} +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) +list-archive-wildcard: ${ARCHIVE}([at]*.mk) pre post ${RUN} printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} -depend-on-existing-member: ${ARCHIVE}(archive.mk) +depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post ${RUN} echo $@ -depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) +depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post ${RUN} echo $@ -remove-archive: +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/unit-tests/comment.exp b/unit-tests/comment.exp index 9a97df0b7320..f4d31d01b6ec 100644 --- a/unit-tests/comment.exp +++ b/unit-tests/comment.exp @@ -1,5 +1,6 @@ -comment testing start -this is foo -This is how a comment looks: # comment -comment testing done +echo This is a shell comment: # comment +This is a shell comment: +echo This is not a shell comment: '# comment' +This is not a shell comment: # comment +A shell comment can#not start in the middle of a word. exit status 0 diff --git a/unit-tests/comment.mk b/unit-tests/comment.mk index 7dd7dbbe28b5..1cdcfcdd86ba 100644 --- a/unit-tests/comment.mk +++ b/unit-tests/comment.mk @@ -1,31 +1,74 @@ -# This is a comment -.if ${MACHINE_ARCH} == something -FOO=bar -.endif - -#\ - Multiline comment +# $NetBSD: comment.mk,v 1.2 2020/09/07 19:17:36 rillig Exp $ +# +# Demonstrate how comments are written in makefiles. -BAR=# defined -FOOBAR= # defined +# This is a comment. -# This is an escaped comment \ -that keeps going until the end of this line +#\ +This is a multiline comment. -# Another escaped comment \ +# Another multiline comment \ that \ goes \ -on +on and on. + + # Comments can be indented, but that is rather unusual. + + # Comments can be indented with a tab. + # These are not shell commands, they are just makefile comments. + +.if 1 # There can be comments after conditions. +.endif # And after the closing directive. + +VAR= # This comment makes the variable value empty. +.if ${VAR} != "" +. error +.endif + +# The comment does not need to start at the beginning of a word (as in the +# shell), it can start anywhere. +VAR=# defined but empty + +# The space before the comment is always trimmed. +VAR= value +.if ${VAR} != "value" +. error +.endif # This is NOT an escaped comment due to the double backslashes \\ -all: hi foo bar - @echo comment testing done +VAR= not part of the comment +.if ${VAR} != "not part of the comment" +. error +.endif -hi: - @echo comment testing start +# To escape a comment sign, precede it with a backslash. +VAR= \# # Both in the assignment. +.if ${VAR} != "\#" # And in the comparison. +. error +.endif + +# Since 2012-03-24 the variable modifier :[#] does not need to be escaped. +# To keep the parsing code simple, any "[#" does not start a comment, even +# outside of a variable expression. +WORDS= ${VAR:[#]} [# +.if ${WORDS} != "1 [#" +. error +.endif -foo: - @echo this is $@ +# An odd number of comment signs makes a line continuation, \\\ +no matter if it is 3 or 5 \\\\\ +or 9 backslashes. \\\\\\\\\ +This is the last line of the comment. +VAR= no comment anymore +.if ${VAR} != "no comment anymore" +. error +.endif -bar: - @echo This is how a comment looks: '# comment' +all: +# In the commands associated with a target, the '#' does not start a makefile +# comment. The '#' is just passed to the shell, like any ordinary character. + echo This is a shell comment: # comment +# If the '#' were to start a makefile comment, the following shell command +# would have unbalanced quotes. + echo This is not a shell comment: '# comment' + @echo A shell comment can#not start in the middle of a word. diff --git a/unit-tests/cond-cmp-numeric-eq.exp b/unit-tests/cond-cmp-numeric-eq.exp index 39a9383953dd..1f12e858a66c 100644 --- a/unit-tests/cond-cmp-numeric-eq.exp +++ b/unit-tests/cond-cmp-numeric-eq.exp @@ -1 +1,6 @@ -exit status 0 +make: "cond-cmp-numeric-eq.mk" line 54: warning: Unknown operator +make: "cond-cmp-numeric-eq.mk" line 54: Malformed conditional (!(12345 = 12345)) +make: "cond-cmp-numeric-eq.mk" line 61: Malformed conditional (!(12345 === 12345)) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-cmp-numeric-eq.mk b/unit-tests/cond-cmp-numeric-eq.mk index 02b95ae593cb..0e77d364ac94 100755 --- a/unit-tests/cond-cmp-numeric-eq.mk +++ b/unit-tests/cond-cmp-numeric-eq.mk @@ -1,43 +1,43 @@ -# $NetBSD: cond-cmp-numeric-eq.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $ +# $NetBSD: cond-cmp-numeric-eq.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for numeric comparisons with the == operator in .if conditions. # This comparison yields the same result, whether numeric or character-based. .if 1 == 1 .else -.error +. error .endif # This comparison yields the same result, whether numeric or character-based. .if 1 == 2 -.error +. error .endif .if 2 == 1 -.error +. error .endif # Scientific notation is supported, as per strtod. .if 2e7 == 2000e4 .else -.error +. error .endif .if 2000e4 == 2e7 .else -.error +. error .endif # Trailing zeroes after the decimal point are irrelevant for the numeric # value. .if 3.30000 == 3.3 .else -.error +. error .endif .if 3.3 == 3.30000 .else -.error +. error .endif # As of 2020-08-23, numeric comparison is implemented as parsing both sides @@ -46,7 +46,22 @@ # be equal. .if 1.000000000000000001 == 1.000000000000000002 .else -.error +. error +.endif + + +# There is no = operator for numbers. +.if !(12345 = 12345) +. error +.else +. error +.endif + +# There is no === operator for numbers either. +.if !(12345 === 12345) +. error +.else +. error .endif all: diff --git a/unit-tests/cond-cmp-numeric-ge.mk b/unit-tests/cond-cmp-numeric-ge.mk index 510b80e14942..e64be7f0c1a8 100755 --- a/unit-tests/cond-cmp-numeric-ge.mk +++ b/unit-tests/cond-cmp-numeric-ge.mk @@ -1,65 +1,65 @@ -# $NetBSD: cond-cmp-numeric-ge.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $ +# $NetBSD: cond-cmp-numeric-ge.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ # # Tests for numeric comparisons with the >= operator in .if conditions. # When both sides are equal, the >= operator always yields true. .if 1 >= 1 .else -.error +. error .endif # This comparison yields the same result, whether numeric or character-based. .if 1 >= 2 -.error +. error .endif .if 2 >= 1 .else -.error +. error .endif # If this comparison were character-based instead of numerical, the # 5 would be >= 14 since its first digit is greater. .if 5 >= 14 -.error +. error .endif .if 14 >= 5 .else -.error +. error .endif # Scientific notation is supported, as per strtod. .if 2e7 >= 1e8 -.error +. error .endif .if 1e8 >= 2e7 .else -.error +. error .endif # Floating pointer numbers can be compared as well. # This might be tempting to use for version numbers, but there are a few pitfalls. .if 3.141 >= 111.222 -.error +. error .endif .if 111.222 >= 3.141 .else -.error +. error .endif # When parsed as a version number, 3.30 is greater than 3.7. # Since make parses numbers as plain numbers, that leads to wrong results. # Numeric comparisons are not suited for comparing version number. .if 3.30 >= 3.7 -.error +. error .endif .if 3.7 >= 3.30 .else -.error +. error .endif # As of 2020-08-23, numeric comparison is implemented as parsing both sides @@ -68,7 +68,7 @@ # be equal. .if 1.000000000000000001 >= 1.000000000000000002 .else -.error +. error .endif all: diff --git a/unit-tests/cond-cmp-numeric-gt.mk b/unit-tests/cond-cmp-numeric-gt.mk index 24ac1eb8531b..1cdcc9891d6f 100755 --- a/unit-tests/cond-cmp-numeric-gt.mk +++ b/unit-tests/cond-cmp-numeric-gt.mk @@ -1,64 +1,64 @@ -# $NetBSD: cond-cmp-numeric-gt.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $ +# $NetBSD: cond-cmp-numeric-gt.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ # # Tests for numeric comparisons with the > operator in .if conditions. # When both sides are equal, the > operator always yields false. .if 1 > 1 -.error +. error .endif # This comparison yields the same result, whether numeric or character-based. .if 1 > 2 -.error +. error .endif .if 2 > 1 .else -.error +. error .endif # If this comparison were character-based instead of numerical, the # 5 would be > 14 since its first digit is greater. .if 5 > 14 -.error +. error .endif .if 14 > 5 .else -.error +. error .endif # Scientific notation is supported, as per strtod. .if 2e7 > 1e8 -.error +. error .endif .if 1e8 > 2e7 .else -.error +. error .endif # Floating pointer numbers can be compared as well. # This might be tempting to use for version numbers, but there are a few pitfalls. .if 3.141 > 111.222 -.error +. error .endif .if 111.222 > 3.141 .else -.error +. error .endif # When parsed as a version number, 3.30 is greater than 3.7. # Since make parses numbers as plain numbers, that leads to wrong results. # Numeric comparisons are not suited for comparing version number. .if 3.30 > 3.7 -.error +. error .endif .if 3.7 > 3.30 .else -.error +. error .endif # As of 2020-08-23, numeric comparison is implemented as parsing both sides @@ -66,7 +66,7 @@ # typically 16 or 17 significant digits, therefore these two numbers seem to # be equal. .if 1.000000000000000001 > 1.000000000000000002 -.error +. error .endif all: diff --git a/unit-tests/cond-cmp-numeric-le.mk b/unit-tests/cond-cmp-numeric-le.mk index 2e4f5e9e694b..05f5e8dba312 100755 --- a/unit-tests/cond-cmp-numeric-le.mk +++ b/unit-tests/cond-cmp-numeric-le.mk @@ -1,53 +1,53 @@ -# $NetBSD: cond-cmp-numeric-le.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $ +# $NetBSD: cond-cmp-numeric-le.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ # # Tests for numeric comparisons with the <= operator in .if conditions. # When both sides are equal, the <= operator always yields true. .if 1 <= 1 .else -.error +. error .endif # This comparison yields the same result, whether numeric or character-based. .if 1 <= 2 .else -.error +. error .endif .if 2 <= 1 -.error +. error .endif # If this comparison were character-based instead of numerical, the # 5 would be >= 14 since its first digit is greater. .if 5 <= 14 .else -.error +. error .endif .if 14 <= 5 -.error +. error .endif # Scientific notation is supported, as per strtod. .if 2e7 <= 1e8 .else -.error +. error .endif .if 1e8 <= 2e7 -.error +. error .endif # Floating pointer numbers can be compared as well. # This might be tempting to use for version numbers, but there are a few pitfalls. .if 3.141 <= 111.222 .else -.error +. error .endif .if 111.222 <= 3.141 -.error +. error .endif # When parsed as a version number, 3.30 is greater than 3.7. @@ -55,11 +55,11 @@ # Numeric comparisons are not suited for comparing version number. .if 3.30 <= 3.7 .else -.error +. error .endif .if 3.7 <= 3.30 -.error +. error .endif # As of 2020-08-23, numeric comparison is implemented as parsing both sides @@ -68,7 +68,7 @@ # be equal. .if 1.000000000000000001 <= 1.000000000000000002 .else -.error +. error .endif all: diff --git a/unit-tests/cond-cmp-numeric-lt.mk b/unit-tests/cond-cmp-numeric-lt.mk index a5fcceddff4b..b0dddd591543 100755 --- a/unit-tests/cond-cmp-numeric-lt.mk +++ b/unit-tests/cond-cmp-numeric-lt.mk @@ -1,52 +1,52 @@ -# $NetBSD: cond-cmp-numeric-lt.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $ +# $NetBSD: cond-cmp-numeric-lt.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ # # Tests for numeric comparisons with the < operator in .if conditions. # When both sides are equal, the < operator always yields false. .if 1 < 1 -.error +. error .endif # This comparison yields the same result, whether numeric or character-based. .if 1 < 2 .else -.error +. error .endif .if 2 < 1 -.error +. error .endif # If this comparison were character-based instead of numerical, the # 5 would be > 14 since its first digit is greater. .if 5 < 14 .else -.error +. error .endif .if 14 < 5 -.error +. error .endif # Scientific notation is supported, as per strtod. .if 2e7 < 1e8 .else -.error +. error .endif .if 1e8 < 2e7 -.error +. error .endif # Floating pointer numbers can be compared as well. # This might be tempting to use for version numbers, but there are a few pitfalls. .if 3.141 < 111.222 .else -.error +. error .endif .if 111.222 < 3.141 -.error +. error .endif # When parsed as a version number, 3.30 is greater than 3.7. @@ -54,11 +54,11 @@ # Numeric comparisons are not suited for comparing version number. .if 3.30 < 3.7 .else -.error +. error .endif .if 3.7 < 3.30 -.error +. error .endif # As of 2020-08-23, numeric comparison is implemented as parsing both sides @@ -66,7 +66,7 @@ # typically 16 or 17 significant digits, therefore these two numbers seem to # be equal. .if 1.000000000000000001 < 1.000000000000000002 -.error +. error .endif all: diff --git a/unit-tests/cond-cmp-numeric-ne.mk b/unit-tests/cond-cmp-numeric-ne.mk index 6f858584d139..0a366a905a21 100755 --- a/unit-tests/cond-cmp-numeric-ne.mk +++ b/unit-tests/cond-cmp-numeric-ne.mk @@ -1,40 +1,40 @@ -# $NetBSD: cond-cmp-numeric-ne.mk,v 1.1 2020/08/23 13:50:17 rillig Exp $ +# $NetBSD: cond-cmp-numeric-ne.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ # # Tests for numeric comparisons with the != operator in .if conditions. # When both sides are equal, the != operator always yields false. .if 1 != 1 -.error +. error .endif # This comparison yields the same result, whether numeric or character-based. .if 1 != 2 .else -.error +. error .endif .if 2 != 1 .else -.error +. error .endif # Scientific notation is supported, as per strtod. .if 2e7 != 2000e4 -.error +. error .endif .if 2000e4 != 2e7 -.error +. error .endif # Trailing zeroes after the decimal point are irrelevant for the numeric # value. .if 3.30000 != 3.3 -.error +. error .endif .if 3.3 != 3.30000 -.error +. error .endif # As of 2020-08-23, numeric comparison is implemented as parsing both sides @@ -42,7 +42,7 @@ # typically 16 or 17 significant digits, therefore these two numbers seem to # be equal. .if 1.000000000000000001 != 1.000000000000000002 -.error +. error .endif all: diff --git a/unit-tests/cond-cmp-numeric.exp b/unit-tests/cond-cmp-numeric.exp index 39a9383953dd..67d882e0c628 100644 --- a/unit-tests/cond-cmp-numeric.exp +++ b/unit-tests/cond-cmp-numeric.exp @@ -1 +1,11 @@ -exit status 0 +CondParser_Eval: !(${:UINF} > 1e100) +make: "cond-cmp-numeric.mk" line 11: warning: String comparison operator must be either == or != +make: "cond-cmp-numeric.mk" line 11: Malformed conditional (!(${:UINF} > 1e100)) +CondParser_Eval: ${:UNaN} > NaN +make: "cond-cmp-numeric.mk" line 16: warning: String comparison operator must be either == or != +make: "cond-cmp-numeric.mk" line 16: Malformed conditional (${:UNaN} > NaN) +CondParser_Eval: !(${:UNaN} == NaN) +lhs = "NaN", rhs = "NaN", op = == +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-cmp-numeric.mk b/unit-tests/cond-cmp-numeric.mk index 409636c3c3ca..67358ddaf86b 100644 --- a/unit-tests/cond-cmp-numeric.mk +++ b/unit-tests/cond-cmp-numeric.mk @@ -1,8 +1,29 @@ -# $NetBSD: cond-cmp-numeric.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: cond-cmp-numeric.mk,v 1.3 2020/09/12 18:01:51 rillig Exp $ # # Tests for numeric comparisons in .if conditions. -# TODO: Implementation +.MAKEFLAGS: -dc + +# The ${:U...} on the left-hand side is necessary for the parser. + +# Even if strtod(3) parses "INF" as +Infinity, make does not accept this +# since it is not really a number; see TryParseNumber. +.if !(${:UINF} > 1e100) +. error +.endif + +# Neither is NaN a number; see TryParseNumber. +.if ${:UNaN} > NaN +. error +.endif + +# Since NaN is not parsed as a number, both operands are interpreted +# as strings and are therefore equal. If they were parsed as numbers, +# they would compare unequal, since NaN is unequal to any and everything, +# including itself. +.if !(${:UNaN} == NaN) +. error +.endif all: @:; diff --git a/unit-tests/cond-cmp-string.exp b/unit-tests/cond-cmp-string.exp index 03dcb0416898..735b7cda4430 100644 --- a/unit-tests/cond-cmp-string.exp +++ b/unit-tests/cond-cmp-string.exp @@ -1,5 +1,8 @@ make: "cond-cmp-string.mk" line 18: Malformed conditional (str != str) make: "cond-cmp-string.mk" line 37: Malformed conditional ("string" != "str""ing") +make: "cond-cmp-string.mk" line 42: warning: String comparison operator must be either == or != +make: "cond-cmp-string.mk" line 42: Malformed conditional (!("value" = "value")) +make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" === "value")) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-cmp-string.mk b/unit-tests/cond-cmp-string.mk index 67d86b61e88d..6af457925e97 100644 --- a/unit-tests/cond-cmp-string.mk +++ b/unit-tests/cond-cmp-string.mk @@ -1,39 +1,90 @@ -# $NetBSD: cond-cmp-string.mk,v 1.3 2020/08/20 18:43:19 rillig Exp $ +# $NetBSD: cond-cmp-string.mk,v 1.11 2020/10/30 14:53:31 rillig Exp $ # # Tests for string comparisons in .if conditions. # This is a simple comparison of string literals. # Nothing surprising here. .if "str" != "str" -.error +. error .endif # The right-hand side of the comparison may be written without quotes. .if "str" != str -.error +. error .endif # The left-hand side of the comparison must be enclosed in quotes. # This one is not enclosed in quotes and thus generates an error message. .if str != str -.error +. error .endif # The left-hand side of the comparison requires a defined variable. # The variable named "" is not defined, but applying the :U modifier to it # makes it "kind of defined" (see VAR_KEEP). Therefore it is ok here. .if ${:Ustr} != "str" -.error +. error .endif # Any character in a string literal may be escaped using a backslash. # This means that "\n" does not mean a newline but a simple "n". .if "string" != "\s\t\r\i\n\g" -.error +. error .endif # It is not possible to concatenate two string literals to form a single # string. .if "string" != "str""ing" -.error +. error +.endif + +# There is no = operator for strings. +.if !("value" = "value") +. error +.else +. error +.endif + +# There is no === operator for strings either. +.if !("value" === "value") +. error +.else +. error +.endif + +# A variable expression can be enclosed in double quotes. +.if ${:Uword} != "${:Uword}" +. error +.endif + +# Between 2003-01-01 (maybe even earlier) and 2020-10-30, adding one of the +# characters " \t!=><" directly after a variable expression resulted in a +# "Malformed conditional", even though the string was well-formed. +.if ${:Uword } != "${:Uword} " +. error +.endif +# Some other characters worked though, and some didn't. +# Those that are mentioned in is_separator didn't work. +.if ${:Uword0} != "${:Uword}0" +. error +.endif +.if ${:Uword&} != "${:Uword}&" +. error +.endif +.if ${:Uword!} != "${:Uword}!" +. error +.endif +.if ${:Uword<} != "${:Uword}<" +. error +.endif + +# Adding another variable expression to the string literal works though. +.if ${:Uword} != "${:Uwo}${:Urd}" +. error +.endif + +# Adding a space at the beginning of the quoted variable expression works +# though. +.if ${:U word } != " ${:Uword} " +. error .endif diff --git a/unit-tests/misc.exp b/unit-tests/cond-cmp-unary.exp index 39a9383953dd..39a9383953dd 100644..100755 --- a/unit-tests/misc.exp +++ b/unit-tests/cond-cmp-unary.exp diff --git a/unit-tests/cond-cmp-unary.mk b/unit-tests/cond-cmp-unary.mk new file mode 100755 index 000000000000..88ce79bf1a99 --- /dev/null +++ b/unit-tests/cond-cmp-unary.mk @@ -0,0 +1,43 @@ +# $NetBSD: cond-cmp-unary.mk,v 1.1 2020/09/14 06:22:59 rillig Exp $ +# +# Tests for unary comparisons in .if conditions, that is, comparisons with +# a single operand. If the operand is a number, it is compared to zero, +# if it is a string, it is tested for emptiness. + +# The number 0 evaluates to false. +.if 0 +. error +.endif + +# Any other number evaluates to true. +.if !12345 +. error +.endif + +# The empty string evaluates to false. +.if "" +. error +.endif + +# Any other string evaluates to true. +.if !"0" +. error +.endif + +# The empty string may come from a variable expression. +.if ${:U} +. error +.endif + +# A variable expression that is not surrounded by quotes is interpreted +# as a number if possible, otherwise as a string. +.if ${:U0} +. error +.endif + +# A non-zero number from a variable expression evaluates to true. +.if !${:U12345} +. error +.endif + +all: # nothing diff --git a/unit-tests/cond-func-commands.mk b/unit-tests/cond-func-commands.mk index 4a098169048c..c6e1724c72f3 100644 --- a/unit-tests/cond-func-commands.mk +++ b/unit-tests/cond-func-commands.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-commands.mk,v 1.3 2020/08/23 14:07:20 rillig Exp $ +# $NetBSD: cond-func-commands.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for the commands() function in .if conditions. @@ -6,14 +6,14 @@ # The target "target" does not exist yet, therefore it cannot have commands. .if commands(target) -.error +. error .endif target: # Now the target exists, but it still has no commands. .if commands(target) -.error +. error .endif target: @@ -21,7 +21,7 @@ target: # Even after the comment, the target still has no commands. .if commands(target) -.error +. error .endif target: @@ -29,7 +29,7 @@ target: # Finally the target has commands. .if !commands(target) -.error +. error .endif all: diff --git a/unit-tests/cond-func-defined.mk b/unit-tests/cond-func-defined.mk index dce1399183aa..ec4feae05839 100644 --- a/unit-tests/cond-func-defined.mk +++ b/unit-tests/cond-func-defined.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-defined.mk,v 1.3 2020/08/20 17:23:43 rillig Exp $ +# $NetBSD: cond-func-defined.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ # # Tests for the defined() function in .if conditions. @@ -6,27 +6,27 @@ DEF= defined ${:UA B}= variable name with spaces .if !defined(DEF) -.error +. error .endif -# Horizontal whitespace after the opening parenthesis is ignored. +# Horizontal whitespace (space tab) after the opening parenthesis is ignored. .if !defined( DEF) -.error +. error .endif -# Horizontal whitespace before the closing parenthesis is ignored. +# Horizontal whitespace (space tab) before the closing parenthesis is ignored. .if !defined(DEF ) -.error +. error .endif # The argument of a function must not directly contain whitespace. .if !defined(A B) -.error +. error .endif # If necessary, the whitespace can be generated by a variable expression. .if !defined(${:UA B}) -.error +. error .endif all: diff --git a/unit-tests/cond-func-empty.mk b/unit-tests/cond-func-empty.mk index 737403f94525..f93b45895e6e 100644 --- a/unit-tests/cond-func-empty.mk +++ b/unit-tests/cond-func-empty.mk @@ -1,8 +1,150 @@ -# $NetBSD: cond-func-empty.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: cond-func-empty.mk,v 1.8 2020/09/23 08:11:28 rillig Exp $ # -# Tests for the empty() function in .if conditions. +# Tests for the empty() function in .if conditions, which tests a variable +# expression for emptiness. +# +# Note that the argument in the parentheses is indeed a variable name, +# optionally followed by variable modifiers. This is like the defined() +# function. +# + +.undef UNDEF +EMPTY= # empty +SPACE= ${:U } +WORD= word + +# An undefined variable is empty. +.if !empty(UNDEF) +. error +.endif + +# An undefined variable has the empty string as the value, and the :M +# variable modifier does not change that. +# +.if !empty(UNDEF:M*) +. error +.endif + +# The :S modifier replaces the empty value with an actual word, and +# after that the expression is no longer empty. Because the variable +# was undefined in the first place, the expression has the flag VAR_JUNK +# but not VAR_KEEP, therefore it is still considered undefined. +# Only very few variable modifiers turn an undefined variable expression +# into a defined variable expression. The :U and :D modifiers belong to +# that group, but :S doesn't (see VAR_KEEP). +# +# XXX: This is hard to explain to someone who doesn't know these +# implementation details. +# +.if !empty(UNDEF:S,^$,value,W) +. error +.endif + +# The :U modifier modifies expressions based on undefined variables +# (VAR_JUNK) by adding the VAR_KEEP flag, which marks the expression +# as "being interesting enough to be further processed". +# +.if empty(UNDEF:S,^$,value,W:Ufallback) +. error +.endif + +# And now to the surprising part. Applying the following :S modifier to the +# undefined variable makes it non-empty, but the marker VAR_JUNK is preserved +# nevertheless. The :U modifier that follows only looks at VAR_JUNK to decide +# whether the variable is defined or not. This kind of makes sense since the +# :U modifier tests the _variable_, not the _expression_. +# +# But since the variable was undefined to begin with, the fallback value is +# used in this expression. +# +.if ${UNDEF:S,^$,value,W:Ufallback} != "fallback" +. error +.endif + +# The variable EMPTY is completely empty (0 characters). +.if !empty(EMPTY) +. error +.endif + +# The variable SPACE has a single space, which counts as being empty. +.if !empty(SPACE) +. error +.endif + +# The variable .newline has a single newline, which counts as being empty. +.if !empty(.newline) +. error +.endif + +# The empty variable named "" gets a fallback value of " ", which counts as +# empty. +# +# Contrary to the other functions in conditionals, the trailing space is not +# stripped off, as can be seen in the -dv debug log. If the space had been +# stripped, it wouldn't make a difference in this case. +# +.if !empty(:U ) +. error +.endif + +# Now the variable named " " gets a non-empty value, which demonstrates that +# neither leading nor trailing spaces are trimmed in the argument of the +# function. If the spaces were trimmed, the variable name would be "" and +# that variable is indeed undefined. Since ParseEmptyArg calls Var_Parse +# without VARE_UNDEFERR, the value of the undefined variable is returned as +# an empty string. +${:U }= space +.if empty( ) +. error +.endif + +# The value of the following expression is " word", which is not empty. +.if empty(:U word) +. error +.endif + +# The :L modifier creates a variable expression that has the same value as +# its name, which both are "VAR" in this case. The value is therefore not +# empty. +.if empty(VAR:L) +. error +.endif + +# The variable WORD has the value "word", which does not count as empty. +.if empty(WORD) +. error +.endif + +# The expression ${} for a variable with the empty name always evaluates +# to an empty string (see Var_Parse, varUndefined). +.if !empty() +. error +.endif + +# Ensure that variable expressions that appear as part of the argument are +# properly parsed. Typical use cases for this are .for loops, which are +# expanded to exactly these ${:U} expressions. +# +# If everything goes well, the argument expands to "WORD", and that variable +# is defined at the beginning of this file. The surrounding 'W' and 'D' +# ensure that the parser in ParseEmptyArg has the correct position, both +# before and after the call to Var_ParsePP. +.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 -# TODO: Implementation +# Now there is a variable named " WORD ", and it is not empty. +.if empty ( WORD ) +. error +.endif all: @:; diff --git a/unit-tests/cond-func-exists.mk b/unit-tests/cond-func-exists.mk index 6386f21fdc7b..4a80fb1c393d 100644 --- a/unit-tests/cond-func-exists.mk +++ b/unit-tests/cond-func-exists.mk @@ -1,41 +1,41 @@ -# $NetBSD: cond-func-exists.mk,v 1.4 2020/08/28 12:59:36 rillig Exp $ +# $NetBSD: cond-func-exists.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ # # Tests for the exists() function in .if conditions. .if !exists(.) -.error +. error .endif # The argument to the function must not be enclosed in quotes. # Neither double quotes nor single quotes are allowed. .if exists(".") -.error +. error .endif .if exists('.') -.error +. error .endif # The only way to escape characters that would otherwise influence the parser # is to enclose them in a variable expression. For function arguments, # neither the backslash nor the dollar sign act as escape character. .if exists(\.) -.error +. error .endif .if !exists(${:U.}) -.error +. error .endif # The argument to the function can have several variable expressions. # See cond-func.mk for the characters that cannot be used directly. .if !exists(${.PARSEDIR}/${.PARSEFILE}) -.error +. error .endif # Whitespace is trimmed on both sides of the function argument. .if !exists( . ) -.error +. error .endif all: diff --git a/unit-tests/cond-func-make.exp b/unit-tests/cond-func-make.exp index 39a9383953dd..922203b72cbf 100644 --- a/unit-tests/cond-func-make.exp +++ b/unit-tests/cond-func-make.exp @@ -1 +1,3 @@ +: via-cmdline +: via-dot-makeflags exit status 0 diff --git a/unit-tests/cond-func-make.mk b/unit-tests/cond-func-make.mk index baa0d37da726..d75b69bcf98f 100644 --- a/unit-tests/cond-func-make.mk +++ b/unit-tests/cond-func-make.mk @@ -1,8 +1,24 @@ -# $NetBSD: cond-func-make.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: cond-func-make.mk,v 1.3 2020/09/25 20:11:06 rillig Exp $ # -# Tests for the make() function in .if conditions. +# Tests for the make() function in .if conditions, which tests whether +# the argument has been passed as a target via the command line or later +# via the .MAKEFLAGS special dependency target. -# TODO: Implementation +.if !make(via-cmdline) +. error +.endif +.if make(via-dot-makeflags) +. error +.endif -all: - @:; +.MAKEFLAGS: via-dot-makeflags + +.if !make(via-cmdline) +. error +.endif +.if !make(via-dot-makeflags) +. error +.endif + +via-cmdline via-dot-makeflags: + : $@ diff --git a/unit-tests/cond-func-target.mk b/unit-tests/cond-func-target.mk index c36bcfe0a5f8..62266839df9e 100644 --- a/unit-tests/cond-func-target.mk +++ b/unit-tests/cond-func-target.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-target.mk,v 1.3 2020/08/23 14:07:20 rillig Exp $ +# $NetBSD: cond-func-target.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for the target() function in .if conditions. @@ -6,14 +6,14 @@ # The target "target" does not exist yet. .if target(target) -.error +. error .endif target: # The target exists, even though it does not have any commands. .if !target(target) -.error +. error .endif target: @@ -22,7 +22,7 @@ target: # Adding a comment to an existing target does not change whether the target # is defined or not. .if !target(target) -.error +. error .endif target: @@ -31,7 +31,7 @@ target: # Adding a command to an existing target does not change whether the target # is defined or not. .if !target(target) -.error +. error .endif all: diff --git a/unit-tests/cond-func.mk b/unit-tests/cond-func.mk index 304735241e7f..8e7362182429 100644 --- a/unit-tests/cond-func.mk +++ b/unit-tests/cond-func.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func.mk,v 1.1 2020/08/20 17:45:47 rillig Exp $ +# $NetBSD: cond-func.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for those parts of the functions in .if conditions that are common # among several functions. @@ -12,27 +12,27 @@ ${:UVAR(value)}= variable name with parentheses ${:UVAR{value}}= variable name with braces .if !defined(DEF) -.error +. error .endif -# Horizontal whitespace after the opening parenthesis is ignored. +# Horizontal whitespace (space tab) after the opening parenthesis is ignored. .if !defined( DEF) -.error +. error .endif -# Horizontal whitespace before the closing parenthesis is ignored. +# Horizontal whitespace (space tab) before the closing parenthesis is ignored. .if !defined(DEF ) -.error +. error .endif # The argument of a function must not directly contain whitespace. .if !defined(A B) -.error +. error .endif # If necessary, the whitespace can be generated by a variable expression. .if !defined(${:UA B}) -.error +. error .endif # Characters that could be mistaken for operators must not appear directly @@ -42,21 +42,29 @@ ${:UVAR{value}}= variable name with braces # It's not entirely clear why these characters are forbidden. # The most plausible reason seems to be typo detection. .if !defined(A&B) -.error +. error .endif .if !defined(A|B) -.error +. error .endif # Even parentheses may appear in variable names. # They must be balanced though. .if !defined(VAR(value)) -.error +. error .endif # Braces do not have any special meaning when parsing arguments. .if !defined(VAR{value}) -.error +. error +.endif + +# There may be spaces around the operators and parentheses, and even +# inside the parentheses. The spaces inside the parentheses are not +# allowed for the empty() function (see cond-func-empty.mk), therefore +# they are typically omitted for the other functions as well. +.if ! defined ( DEF ) +. error .endif all: diff --git a/unit-tests/cond-op-and.exp b/unit-tests/cond-op-and.exp index 39a9383953dd..173b6861a98b 100644 --- a/unit-tests/cond-op-and.exp +++ b/unit-tests/cond-op-and.exp @@ -1 +1,4 @@ -exit status 0 +make: "cond-op-and.mk" line 43: Malformed conditional (0 &&& 0) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-op-and.mk b/unit-tests/cond-op-and.mk index a204a227c4f0..83c694f15723 100644 --- a/unit-tests/cond-op-and.mk +++ b/unit-tests/cond-op-and.mk @@ -1,21 +1,21 @@ -# $NetBSD: cond-op-and.mk,v 1.3 2020/08/28 14:48:37 rillig Exp $ +# $NetBSD: cond-op-and.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ # # Tests for the && operator in .if conditions. .if 0 && 0 -.error +. error .endif .if 1 && 0 -.error +. error .endif .if 0 && 1 -.error +. error .endif .if !(1 && 1) -.error +. error .endif # The right-hand side is not evaluated since the left-hand side is already @@ -23,5 +23,26 @@ .if 0 && ${UNDEF} .endif +# The && operator may be abbreviated as &. This is not widely known though +# and is also not documented in the manual page. + +.if 0 & 0 +. error +.endif +.if 1 & 0 +. error +.endif +.if 0 & 1 +. error +.endif +.if !(1 & 1) +. error +.endif + +# There is no operator &&&. +.if 0 &&& 0 +. error +.endif + all: @:; diff --git a/unit-tests/cond-op-not.mk b/unit-tests/cond-op-not.mk index ad0a0939eecf..d929318785a5 100644 --- a/unit-tests/cond-op-not.mk +++ b/unit-tests/cond-op-not.mk @@ -1,21 +1,21 @@ -# $NetBSD: cond-op-not.mk,v 1.3 2020/08/28 14:48:37 rillig Exp $ +# $NetBSD: cond-op-not.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for the ! operator in .if conditions. # The exclamation mark negates its operand. .if !1 -.error +. error .endif # Exclamation marks can be chained. # This doesn't happen in practice though. .if !!!1 -.error +. error .endif # The ! binds more tightly than the &&. .if !!0 && 1 -.error +. error .endif all: diff --git a/unit-tests/cond-op-or.exp b/unit-tests/cond-op-or.exp index 39a9383953dd..7888a475e3e4 100644 --- a/unit-tests/cond-op-or.exp +++ b/unit-tests/cond-op-or.exp @@ -1 +1,4 @@ -exit status 0 +make: "cond-op-or.mk" line 43: Malformed conditional (0 ||| 0) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-op-or.mk b/unit-tests/cond-op-or.mk index 696b9dd23062..c6993e7c277e 100644 --- a/unit-tests/cond-op-or.mk +++ b/unit-tests/cond-op-or.mk @@ -1,21 +1,21 @@ -# $NetBSD: cond-op-or.mk,v 1.3 2020/08/28 14:48:37 rillig Exp $ +# $NetBSD: cond-op-or.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $ # # Tests for the || operator in .if conditions. .if 0 || 0 -.error +. error .endif .if !(1 || 0) -.error +. error .endif .if !(0 || 1) -.error +. error .endif .if !(1 || 1) -.error +. error .endif # The right-hand side is not evaluated since the left-hand side is already @@ -23,5 +23,26 @@ .if 1 || ${UNDEF} .endif +# The || operator may be abbreviated as |. This is not widely known though +# and is also not documented in the manual page. + +.if 0 | 0 +. error +.endif +.if !(1 | 0) +. error +.endif +.if !(0 | 1) +. error +.endif +.if !(1 | 1) +. error +.endif + +# There is no operator |||. +.if 0 ||| 0 +. error +.endif + all: @:; diff --git a/unit-tests/cond-op.exp b/unit-tests/cond-op.exp index 4caec4df659b..fd5bc20f673c 100644 --- a/unit-tests/cond-op.exp +++ b/unit-tests/cond-op.exp @@ -1,5 +1,7 @@ make: "cond-op.mk" line 45: Malformed conditional ("!word" == !word) -make: "cond-op.mk" line 57: Parsing continues until here. +make: "cond-op.mk" line 70: Malformed conditional (0 ${ERR::=evaluated}) +make: "cond-op.mk" line 74: warning: After detecting a parse error, the rest is evaluated. +make: "cond-op.mk" line 78: Parsing continues until here. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-op.mk b/unit-tests/cond-op.mk index a22e4181e8fe..71c4f7b66441 100644 --- a/unit-tests/cond-op.mk +++ b/unit-tests/cond-op.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-op.mk,v 1.4 2020/08/28 14:07:51 rillig Exp $ +# $NetBSD: cond-op.mk,v 1.8 2020/10/24 08:46:08 rillig Exp $ # # Tests for operators like &&, ||, ! in .if conditions. # @@ -14,24 +14,24 @@ # If || were to bind more tightly than &&, the result would be different # as well. .if !(1 || 1 && 0) -.error +. error .endif # If make were to interpret the && and || operators like the shell, the # implicit binding would be this: .if (1 || 1) && 0 -.error +. error .endif # The precedence of the ! operator is different from C though. It has a # lower precedence than the comparison operators. .if !"word" == "word" -.error +. error .endif # This is how the above condition is actually interpreted. .if !("word" == "word") -.error +. error .endif # TODO: Demonstrate that the precedence of the ! and == operators actually @@ -41,16 +41,37 @@ # This condition is malformed because the '!' on the right-hand side must not # appear unquoted. If any, it must be enclosed in quotes. # In any case, it is not interpreted as a negation of an unquoted string. -# See CondGetString. +# See CondParser_String. .if "!word" == !word -.error +. error .endif # Surprisingly, the ampersand and pipe are allowed in bare strings. # That's another opportunity for writing confusing code. -# See CondGetString, which only has '!' in the list of stop characters. +# See CondParser_String, which only has '!' in the list of stop characters. .if "a&&b||c" != a&&b||c -.error +. error +.endif + +# As soon as the parser sees the '$', it knows that the condition will +# be malformed. Therefore there is no point in evaluating it. +# +# As of 2020-09-11, that part of the condition is evaluated nevertheless, +# since CondParser_Expr just requests the next token, without restricting +# the token to the expected tokens. If the parser were to restrict the +# valid follow tokens for the token "0" to those that can actually produce +# a correct condition (which in this case would be comparison operators, +# TOK_AND, TOK_OR or TOK_RPAREN), the variable expression would not have +# to be evaluated. +# +# This would add a good deal of complexity to the code though, for almost +# no benefit, especially since most expressions and conditions are side +# effect free. +.if 0 ${ERR::=evaluated} +. error +.endif +.if ${ERR:Uundefined} == evaluated +. warning After detecting a parse error, the rest is evaluated. .endif # Just in case that parsing should ever stop on the first error. diff --git a/unit-tests/cond-short.mk b/unit-tests/cond-short.mk index ae441d80f7d9..9e3b0575eb78 100644 --- a/unit-tests/cond-short.mk +++ b/unit-tests/cond-short.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-short.mk,v 1.9 2020/08/19 22:47:09 rillig Exp $ +# $NetBSD: cond-short.mk,v 1.11 2020/10/24 08:50:17 rillig Exp $ # # Demonstrates that in conditions, the right-hand side of an && or || # is only evaluated if it can actually influence the result. @@ -76,7 +76,7 @@ VAR= # empty again, for the following tests .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} +. warning first=${FIRST} last=${LAST} appended=${APPENDED} ran=${RAN} .endif # The || operator. @@ -113,59 +113,59 @@ VAR= # empty again, for the following tests # make sure these do not cause complaint #.MAKEFLAGS: -dc -V42 = 42 -iV1 = ${V42} -iV2 = ${V66} +V42= 42 +iV1= ${V42} +iV2= ${V66} .if defined(V42) && ${V42} > 0 -x=Ok +x= Ok .else -x=Fail +x= Fail .endif -x!= echo 'defined(V42) && ${V42} > 0: $x' >&2; echo +x!= echo 'defined(V42) && ${V42} > 0: $x' >&2; echo # this one throws both String comparison operator and # Malformed conditional with cond.c 1.78 # indirect iV2 would expand to "" and treated as 0 .if defined(V66) && ( ${iV2} < ${V42} ) -x=Fail +x= Fail .else -x=Ok +x= Ok .endif -x!= echo 'defined(V66) && ( "${iV2}" < ${V42} ): $x' >&2; echo +x!= echo 'defined(V66) && ( "${iV2}" < ${V42} ): $x' >&2; echo # next two thow String comparison operator with cond.c 1.78 # indirect iV1 would expand to 42 .if 1 || ${iV1} < ${V42} -x=Ok +x= Ok .else -x=Fail +x= Fail .endif -x!= echo '1 || ${iV1} < ${V42}: $x' >&2; echo +x!= echo '1 || ${iV1} < ${V42}: $x' >&2; echo .if 1 || ${iV2:U2} < ${V42} -x=Ok +x= Ok .else -x=Fail +x= Fail .endif -x!= echo '1 || ${iV2:U2} < ${V42}: $x' >&2; echo +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 +x= Ok .else -x=Fail +x= Fail .endif -x!= echo '0 || ${iV1} <= ${V42}: $x' >&2; echo +x!= echo '0 || ${iV1} <= ${V42}: $x' >&2; echo # ${iV2:U2} expands to 2 .if 0 || ${iV2:U2} < ${V42} -x=Ok +x= Ok .else -x=Fail +x= Fail .endif -x!= echo '0 || ${iV2:U2} < ${V42}: $x' >&2; echo +x!= echo '0 || ${iV2:U2} < ${V42}: $x' >&2; echo all: @:;: diff --git a/unit-tests/cond-token-number.exp b/unit-tests/cond-token-number.exp index 39a9383953dd..ea1f92797ff4 100644 --- a/unit-tests/cond-token-number.exp +++ b/unit-tests/cond-token-number.exp @@ -1 +1,8 @@ -exit status 0 +make: "cond-token-number.mk" line 13: Malformed conditional (-0) +make: "cond-token-number.mk" line 21: Malformed conditional (+0) +make: "cond-token-number.mk" line 29: Malformed conditional (!-1) +make: "cond-token-number.mk" line 37: Malformed conditional (!+1) +make: "cond-token-number.mk" line 54: End of the tests. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-token-number.mk b/unit-tests/cond-token-number.mk index 30e7e84e81bb..4171a07d56c6 100644 --- a/unit-tests/cond-token-number.mk +++ b/unit-tests/cond-token-number.mk @@ -1,8 +1,56 @@ -# $NetBSD: cond-token-number.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: cond-token-number.mk,v 1.3 2020/09/14 06:22:59 rillig Exp $ # # Tests for number tokens in .if conditions. -# TODO: Implementation +.if 0 +. error +.endif -all: - @:; +# Even though -0 is a number and would be accepted by strtod, it is not +# accepted by the condition parser. +# +# See the ch_isdigit call in CondParser_String. +.if -0 +. error +.endif + +# Even though +0 is a number and would be accepted by strtod, it is not +# accepted by the condition parser. +# +# See the ch_isdigit call in CondParser_String. +.if +0 +. error +.endif + +# Even though -1 is a number and would be accepted by strtod, it is not +# accepted by the condition parser. +# +# See the ch_isdigit call in CondParser_String. +.if !-1 +. error +.endif + +# Even though +1 is a number and would be accepted by strtod, it is not +# accepted by the condition parser. +# +# See the ch_isdigit call in CondParser_String. +.if !+1 +. error +.endif + +# When the number comes from a variable expression though, it may be signed. +# XXX: This is inconsistent. +.if ${:U+0} +. error +.endif + +# When the number comes from a variable expression though, it may be signed. +# XXX: This is inconsistent. +.if !${:U+1} +. error +.endif + +# Ensure that parsing continues until here. +.info End of the tests. + +all: # nothing diff --git a/unit-tests/cond-token-plain.exp b/unit-tests/cond-token-plain.exp index 39a9383953dd..a87ce35186f3 100644 --- a/unit-tests/cond-token-plain.exp +++ b/unit-tests/cond-token-plain.exp @@ -1 +1,29 @@ +CondParser_Eval: ${:Uvalue} != value +lhs = "value", rhs = "value", op = != +CondParser_Eval: ${:U} != " +lhs = "", rhs = "", op = != +CondParser_Eval: ${:U#hash} != "#hash" +lhs = "#hash", rhs = "#hash", op = != +CondParser_Eval: ${:U\\} != "\\ +lhs = "\", rhs = "\", op = != +CondParser_Eval: ${:U#hash} != #hash +lhs = "#hash", rhs = "#hash", op = != +CondParser_Eval: 0 # This is treated as a comment, but why? +CondParser_Eval: ${0 # comment :?yes:no} != no +CondParser_Eval: 0 # comment +lhs = "no", rhs = "no", op = != +CondParser_Eval: ${1 # comment :?yes:no} != yes +CondParser_Eval: 1 # comment +lhs = "yes", rhs = "yes", op = != +CondParser_Eval: ${UNDEF:Uundefined}!=undefined +lhs = "undefined", rhs = "undefined", op = != +CondParser_Eval: ${UNDEF:U12345}>12345 +lhs = 12345.000000, rhs = 12345.000000, op = >1 +CondParser_Eval: ${UNDEF:U12345}<12345 +lhs = 12345.000000, rhs = 12345.000000, op = <1 +CondParser_Eval: (${UNDEF:U0})||0 +CondParser_Eval: ${:Uvar}&&name != "var&&name" +lhs = "var&&name", rhs = "var&&name", op = != +CondParser_Eval: ${:Uvar}||name != "var||name" +lhs = "var||name", rhs = "var||name", op = != exit status 0 diff --git a/unit-tests/cond-token-plain.mk b/unit-tests/cond-token-plain.mk index fc13245382f1..ba9934f5b882 100644 --- a/unit-tests/cond-token-plain.mk +++ b/unit-tests/cond-token-plain.mk @@ -1,9 +1,94 @@ -# $NetBSD: cond-token-plain.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: cond-token-plain.mk,v 1.4 2020/09/12 17:47:24 rillig Exp $ # # Tests for plain tokens (that is, string literals without quotes) # in .if conditions. -# TODO: Implementation +.MAKEFLAGS: -dc + +.if ${:Uvalue} != value +. error +.endif + +# Malformed condition since comment parsing is done in an early phase +# and removes the '#' and everything behind it long before the condition +# parser gets to see it. +# +# XXX: The error message is missing for this malformed condition. +# The right-hand side of the comparison is just a '"'. +.if ${:U} != "#hash" +. error +.endif + +# To get a '#' into a condition, it has to be escaped using a backslash. +# This prevents the comment parser from removing it, and in turn, it becomes +# visible to CondParser_String. +.if ${:U\#hash} != "\#hash" +. error +.endif + +# Since 2002-12-30, and still as of 2020-09-11, CondParser_Token handles +# the '#' specially, even though at this point, there should be no need for +# comment handling anymore. The comments are supposed to be stripped off +# in a very early parsing phase. +# +# XXX: Missing error message for the malformed condition. The right-hand +# side is double-quotes, backslash, backslash. +# XXX: It is unexpected that the right-hand side evaluates to a single +# backslash. +.if ${:U\\} != "\\#hash" +. error +.endif + +# The right-hand side of a comparison is not parsed as a token, therefore +# the code from CondParser_Token does not apply to it. +.if ${:U\#hash} != \#hash +. error +.endif + +# XXX: What is the purpose of treating an escaped '#' in the following +# condition as a comment? And why only at the beginning of a token, +# just as in the shell? +.if 0 \# This is treated as a comment, but why? +. error +.endif + +# Ah, ok, this can be used to add an end-of-condition comment. But does +# anybody really use this? This is neither documented nor obvious since +# the '#' is escaped. It's much clearer to write a comment in the line +# above the condition. +.if ${0 \# comment :?yes:no} != no +. error +.endif +.if ${1 \# comment :?yes:no} != yes +. error +.endif + +# Usually there is whitespace around the comparison operator, but this is +# not required. +.if ${UNDEF:Uundefined}!=undefined +. error +.endif +.if ${UNDEF:U12345}>12345 +. error +.endif +.if ${UNDEF:U12345}<12345 +. error +.endif +.if (${UNDEF:U0})||0 +. error +.endif + +# Only the comparison operator terminates the comparison operand, and it's +# a coincidence that the '!' is both used in the '!=' comparison operator +# as well as for negating a comparison result. +# +# The boolean operators '&' and '|' don't terminate a comparison operand. +.if ${:Uvar}&&name != "var&&name" +. error +.endif +.if ${:Uvar}||name != "var||name" +. error +.endif all: @:; diff --git a/unit-tests/cond-token-var.mk b/unit-tests/cond-token-var.mk index 5d5bbf2c7fc4..6e55bd2d552d 100644 --- a/unit-tests/cond-token-var.mk +++ b/unit-tests/cond-token-var.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-token-var.mk,v 1.3 2020/08/20 19:43:42 rillig Exp $ +# $NetBSD: cond-token-var.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for variables in .if conditions. @@ -6,19 +6,19 @@ DEF= defined # A defined variable may appear on either side of the comparison. .if ${DEF} == ${DEF} -.info ok +. info ok .else -.error +. error .endif # A variable that appears on the left-hand side must be defined. .if ${UNDEF} == ${DEF} -.error +. error .endif # A variable that appears on the right-hand side must be defined. .if ${DEF} == ${UNDEF} -.error +. error .endif # A defined variable may appear as an expression of its own. diff --git a/unit-tests/cond-undef-lint.exp b/unit-tests/cond-undef-lint.exp new file mode 100755 index 000000000000..365edae61275 --- /dev/null +++ b/unit-tests/cond-undef-lint.exp @@ -0,0 +1,7 @@ +make: "cond-undef-lint.mk" line 23: Variable "UNDEF" is undefined +make: "cond-undef-lint.mk" line 38: Variable "UNDEF" is undefined +make: "cond-undef-lint.mk" line 38: Variable "VAR." is undefined +make: "cond-undef-lint.mk" line 45: Variable "VAR.defined" is undefined +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-undef-lint.mk b/unit-tests/cond-undef-lint.mk new file mode 100755 index 000000000000..334ceb594054 --- /dev/null +++ b/unit-tests/cond-undef-lint.mk @@ -0,0 +1,69 @@ +# $NetBSD: cond-undef-lint.mk,v 1.2 2020/09/14 07:13:29 rillig Exp $ +# +# Tests for defined and undefined variables in .if conditions, in lint mode. +# +# As of 2020-09-14, lint mode contains experimental code for printing +# accurate error messages in case of undefined variables, instead of the +# wrong "Malformed condition". +# +# See also: +# opt-debug-lint.mk + +.MAKEFLAGS: -dL + +# DEF is defined, UNDEF is not. +DEF= defined + +# An expression based on a defined variable is fine. +.if !${DEF} +. error +.endif + +# Since the condition fails to evaluate, neither of the branches is taken. +.if ${UNDEF} +. error +.else +. error +.endif + +# The variable name depends on the undefined variable, which is probably a +# mistake. The variable UNDEF, as used here, can be easily turned into +# an expression that is always defined, using the :U modifier. +# +# The outer expression does not generate an error message since there was +# already an error evaluating this variable's name. +# +# TODO: Suppress the error message "Variable VAR. is undefined". That part +# of the expression must not be evaluated at all. +.if ${VAR.${UNDEF}} +. error +.else +. error +.endif + +# The variable VAR.defined is not defined and thus generates an error message. +.if ${VAR.${DEF}} +. error +.else +. error +.endif + + +# Variables that are referenced indirectly may be undefined in a condition. +# +# A practical example for this is CFLAGS, which consists of CWARNS, COPTS +# and a few others. Just because these nested variables are not defined, +# this does not make the condition invalid. +# +# The crucial point is that at the point where the variable appears in the +# condition, there is no way to influence the definedness of the nested +# variables. In particular, there is no modifier that would turn undefined +# nested variables into empty strings, as an equivalent to the :U modifier. +INDIRECT= ${NESTED_UNDEF} ${NESTED_DEF} +NESTED_DEF= nested-defined + +# Since NESTED_UNDEF is not controllable at this point, it must not generate +# an error message, and it doesn't do so, since 2020-09-14. +.if !${INDIRECT} +. error +.endif diff --git a/unit-tests/cond1.exp b/unit-tests/cond1.exp index 701d504fcc6b..38d616babdfb 100644 --- a/unit-tests/cond1.exp +++ b/unit-tests/cond1.exp @@ -16,7 +16,7 @@ Passed: 4 is not prime 5 is prime -make: warning: String comparison operator should be either == or != +make: warning: String comparison operator must be either == or != make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No OK diff --git a/unit-tests/cond1.mk b/unit-tests/cond1.mk index 2fc2c7c64b85..eedff8c1530b 100644 --- a/unit-tests/cond1.mk +++ b/unit-tests/cond1.mk @@ -1,4 +1,4 @@ -# $Id: cond1.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# $NetBSD: cond1.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ # hard code these! TEST_UNAME_S= NetBSD diff --git a/unit-tests/cond2.exp b/unit-tests/cond2.exp deleted file mode 100644 index 22e76a5b2c9e..000000000000 --- a/unit-tests/cond2.exp +++ /dev/null @@ -1,7 +0,0 @@ -make: Bad conditional expression ` == "empty"' in == "empty"?oops:ok -make: "cond2.mk" line 13: Malformed conditional ({TEST_TYPO} == "Ok") -TEST_NOT_SET is empty or not defined -make: "cond2.mk" line 20: Malformed conditional (${TEST_NOT_SET} == "empty") -make: Fatal errors encountered -- cannot continue -make: stopped in unit-tests -exit status 1 diff --git a/unit-tests/cond2.mk b/unit-tests/cond2.mk deleted file mode 100644 index 943df43fb623..000000000000 --- a/unit-tests/cond2.mk +++ /dev/null @@ -1,29 +0,0 @@ -# $Id: cond2.mk,v 1.1.1.2 2015/12/02 00:34:27 sjg Exp $ - -TEST_UNAME_S= NetBSD - -# this should be ok -X:= ${${TEST_UNAME_S} == "NetBSD":?Ok:fail} -.if $X == "Ok" -Y= good -.endif -# expect: Bad conditional expression ` == "empty"' in == "empty"?oops:ok -X:= ${${TEST_NOT_SET} == "empty":?oops:ok} -# expect: Malformed conditional ({TEST_TYPO} == "Ok") -.if {TEST_TYPO} == "Ok" -Y= oops -.endif -.if empty(TEST_NOT_SET) -Y!= echo TEST_NOT_SET is empty or not defined >&2; echo -.endif -# expect: Malformed conditional (${TEST_NOT_SET} == "empty") -.if ${TEST_NOT_SET} == "empty" -Y= oops -.endif - -.if defined(.NDEF) && ${.NDEF} > 0 -Z= yes -.endif - -all: - @echo $@ diff --git a/unit-tests/counter-append.exp b/unit-tests/counter-append.exp new file mode 100755 index 000000000000..e2439fe08717 --- /dev/null +++ b/unit-tests/counter-append.exp @@ -0,0 +1,2 @@ +A=1 B=2 C=3 COUNTER=3 +exit status 0 diff --git a/unit-tests/counter-append.mk b/unit-tests/counter-append.mk new file mode 100755 index 000000000000..1c4e00d6118c --- /dev/null +++ b/unit-tests/counter-append.mk @@ -0,0 +1,28 @@ +# $NetBSD: counter-append.mk,v 1.4 2020/10/17 16:57:17 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". +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/unit-tests/counter.exp b/unit-tests/counter.exp index c00b3e57056d..e2439fe08717 100644 --- a/unit-tests/counter.exp +++ b/unit-tests/counter.exp @@ -1,88 +1,2 @@ -Global:RELEVANT = yes (load-time part) -Global:COUNTER = -Global:NEXT = ${COUNTER::=${COUNTER} a}${COUNTER:[#]} -Global:A = -Var_Parse: ${NEXT} with VARE_WANTRES|VARE_ASSIGN -Var_Parse: ${COUNTER::=${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN -Applying ${COUNTER::...} to "" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES -Modifier part: " a" -Global:COUNTER = a -Result of ${COUNTER::=${COUNTER} a} is "" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN -Var_Parse: ${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN -Applying ${COUNTER:[...} to " a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Modifier part: "#" -Result of ${COUNTER:[#]} is "1" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Global:A = ${COUNTER::= a a}1 -Global:B = -Var_Parse: ${NEXT} with VARE_WANTRES|VARE_ASSIGN -Var_Parse: ${COUNTER::=${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN -Applying ${COUNTER::...} to " a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES -Modifier part: " a a" -Global:COUNTER = a a -Result of ${COUNTER::=${COUNTER} a} is "" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN -Var_Parse: ${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN -Applying ${COUNTER:[...} to " a a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Modifier part: "#" -Result of ${COUNTER:[#]} is "2" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Global:B = ${COUNTER::= a a a}2 -Global:C = -Var_Parse: ${NEXT} with VARE_WANTRES|VARE_ASSIGN -Var_Parse: ${COUNTER::=${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN -Applying ${COUNTER::...} to " a a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES -Modifier part: " a a a" -Global:COUNTER = a a a -Result of ${COUNTER::=${COUNTER} a} is "" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Var_Parse: ${COUNTER} a}${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN -Var_Parse: ${COUNTER:[#]} with VARE_WANTRES|VARE_ASSIGN -Applying ${COUNTER:[...} to " a a a" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Modifier part: "#" -Result of ${COUNTER:[#]} is "3" (eflags = VARE_WANTRES|VARE_ASSIGN, vflags = none) -Global:C = ${COUNTER::= a a a a}3 -Global:RELEVANT = no -Global:RELEVANT = yes (run-time part) -Result of ${RELEVANT::=yes (run-time part)} is "" (eflags = VARE_WANTRES, vflags = none) -Var_Parse: ${A:Q} B=${B:Q} C=${C:Q} COUNTER=${COUNTER:[#]:Q} with VARE_WANTRES -Var_Parse: ${COUNTER::= a a}1 with VARE_WANTRES -Applying ${COUNTER::...} to " a a a" (eflags = VARE_WANTRES, vflags = none) -Modifier part: " a a" -Global:COUNTER = a a -Result of ${COUNTER::= a a} is "" (eflags = VARE_WANTRES, vflags = none) -Applying ${A:Q} to "1" (eflags = VARE_WANTRES, vflags = none) -QuoteMeta: [1] -Result of ${A:Q} is "1" (eflags = VARE_WANTRES, vflags = none) -Var_Parse: ${B:Q} C=${C:Q} COUNTER=${COUNTER:[#]:Q} with VARE_WANTRES -Var_Parse: ${COUNTER::= a a a}2 with VARE_WANTRES -Applying ${COUNTER::...} to " a a" (eflags = VARE_WANTRES, vflags = none) -Modifier part: " a a a" -Global:COUNTER = a a a -Result of ${COUNTER::= a a a} is "" (eflags = VARE_WANTRES, vflags = none) -Applying ${B:Q} to "2" (eflags = VARE_WANTRES, vflags = none) -QuoteMeta: [2] -Result of ${B:Q} is "2" (eflags = VARE_WANTRES, vflags = none) -Var_Parse: ${C:Q} COUNTER=${COUNTER:[#]:Q} with VARE_WANTRES -Var_Parse: ${COUNTER::= a a a a}3 with VARE_WANTRES -Applying ${COUNTER::...} to " a a a" (eflags = VARE_WANTRES, vflags = none) -Modifier part: " a a a a" -Global:COUNTER = a a a a -Result of ${COUNTER::= a a a a} is "" (eflags = VARE_WANTRES, vflags = none) -Applying ${C:Q} to "3" (eflags = VARE_WANTRES, vflags = none) -QuoteMeta: [3] -Result of ${C:Q} is "3" (eflags = VARE_WANTRES, vflags = none) -Var_Parse: ${COUNTER:[#]:Q} with VARE_WANTRES -Applying ${COUNTER:[...} to " a a a a" (eflags = VARE_WANTRES, vflags = none) -Modifier part: "#" -Result of ${COUNTER:[#]} is "4" (eflags = VARE_WANTRES, vflags = none) -Applying ${COUNTER:Q} to "4" (eflags = VARE_WANTRES, vflags = none) -QuoteMeta: [4] -Result of ${COUNTER:Q} is "4" (eflags = VARE_WANTRES, vflags = none) -A=1 B=2 C=3 COUNTER=4 -Var_Parse: ${RELEVANT::=no} with VARE_WANTRES -Applying ${RELEVANT::...} to "yes (run-time part)" (eflags = VARE_WANTRES, vflags = none) -Modifier part: "no" -Global:RELEVANT = no +A=1 B=2 C=3 COUNTER=3 exit status 0 diff --git a/unit-tests/counter.mk b/unit-tests/counter.mk index 23e7dcc0e8d2..3c75d7a5032a 100644 --- a/unit-tests/counter.mk +++ b/unit-tests/counter.mk @@ -1,15 +1,12 @@ -# $NetBSD: counter.mk,v 1.1 2020/08/02 14:53:02 rillig Exp $ +# $NetBSD: counter.mk,v 1.5 2020/10/17 16:57:17 rillig Exp $ # -# Demonstrates that it is not easily possible to let make count -# the number of times a variable is actually accessed. +# Demonstrates how to let make count the number of times a variable +# is actually accessed, using the ::= variable modifier. # -# As of 2020-08-02, the counter ends up at having 4 words, even -# though the NEXT variable is only accessed 3 times. This is -# surprising. -# -# A hint to this surprising behavior is that the variables don't -# get fully expanded. For example, A does not simply contain the -# value "1" but an additional unexpanded ${COUNTER:...} before it. +# 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 diff --git a/unit-tests/dep-colon-bug-cross-file.exp b/unit-tests/dep-colon-bug-cross-file.exp new file mode 100644 index 000000000000..855b575c48bc --- /dev/null +++ b/unit-tests/dep-colon-bug-cross-file.exp @@ -0,0 +1,4 @@ +make: "dep-colon-bug-cross-file.mk" line 31: warning: duplicate script for target "all" ignored +make: "dep-colon-bug-cross-file.mk" line 40: warning: using previous script for "all" defined here +: pass 1 +exit status 0 diff --git a/unit-tests/dep-colon-bug-cross-file.mk b/unit-tests/dep-colon-bug-cross-file.mk new file mode 100644 index 000000000000..57fbf478163c --- /dev/null +++ b/unit-tests/dep-colon-bug-cross-file.mk @@ -0,0 +1,41 @@ +# $NetBSD: dep-colon-bug-cross-file.mk,v 1.4 2020/09/27 09:53:41 rillig Exp $ +# +# Until 2020-09-25, the very last dependency group of a top-level makefile +# was not finished properly. This made it possible to add further commands +# to that target. +# +# In pass 1, there is a dependency group at the bottom of the file. +# This dependency group is not finished properly. Finishing the dependency +# group would add the OP_HAS_COMMANDS flag to the "all" target, thereby +# preventing any commands from being added later. +# +# After the file has been parsed completely, it is parsed again in pass 2. +# In this pass, another command is added to the "current dependency group", +# which was still the one from pass 1, which means it was possible to later +# add commands to an existing target, even across file boundaries. +# +# Oops, even worse. Running this test in a make from 2020-09-25 or earlier +# on NetBSD 8.0 x86_64 with MALLOC_OPTIONS=JA produces this or a similar +# output: +# +# make: cannot open ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ. +# +# The 'Z' means access to already freed memory; see jemalloc(3). The cause +# for this is that in MainParseArgs, the command line arguments were not +# properly copied before storing them in global variables. + +PASS?= 1 + +.if ${PASS} == 2 +all: + : pass 2 +.endif + +.if ${PASS} == 1 + +PASS= 2 +.MAKEFLAGS: -f ${.PARSEDIR:q}/${.PARSEFILE:q} + +all: + : pass 1 +.endif diff --git a/unit-tests/dep-colon.exp b/unit-tests/dep-colon.exp index 39a9383953dd..55330d95a36d 100644 --- a/unit-tests/dep-colon.exp +++ b/unit-tests/dep-colon.exp @@ -1 +1,3 @@ +making target1 from source1 +making target2 from source2 exit status 0 diff --git a/unit-tests/dep-colon.mk b/unit-tests/dep-colon.mk index 7355c59ae557..e9931c9b2cf8 100644 --- a/unit-tests/dep-colon.mk +++ b/unit-tests/dep-colon.mk @@ -1,8 +1,20 @@ -# $NetBSD: dep-colon.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: dep-colon.mk,v 1.3 2020/10/17 20:10:04 rillig Exp $ # # Tests for the : operator in dependency declarations. # TODO: Implementation -all: - @:; +# In a dependency declaration line, there may be a shell command after the +# sources. It is separated by a semicolon. This "feature" is required by +# POSIX. It is seldom used, if at all. +all: target1 +target1: source1; @echo making ${.TARGET} from ${.ALLSRC} +source1: .PHONY + +# The semicolon for separating the sources from the creation commands must +# appear at the top-level. The semicolons inside the :S;1;2; modifier are +# skipped when looking for the semicolon that separates the sources from +# the commands. +all: target2 +target2: source${:U1:S;1;2;}; @echo making ${.TARGET} from ${.ALLSRC} +source2: .PHONY diff --git a/unit-tests/dep-double-colon-indep.exp b/unit-tests/dep-double-colon-indep.exp new file mode 100644 index 000000000000..b425144754e1 --- /dev/null +++ b/unit-tests/dep-double-colon-indep.exp @@ -0,0 +1,3 @@ +: 'Making 1400 dep-double-colon-1300 from dep-double-colon-1400 oodate dep-double-colon-1400' +: 'Making 1500 dep-double-colon-1300 from dep-double-colon-1500 oodate dep-double-colon-1500' +exit status 0 diff --git a/unit-tests/dep-double-colon-indep.mk b/unit-tests/dep-double-colon-indep.mk new file mode 100644 index 000000000000..9f004cf88ba2 --- /dev/null +++ b/unit-tests/dep-double-colon-indep.mk @@ -0,0 +1,32 @@ +# $NetBSD: dep-double-colon-indep.mk,v 1.1 2020/10/23 19:11:30 rillig Exp $ +# +# Tests for the :: operator in dependency declarations, which allows multiple +# dependency groups with the same target. Each group is evaluated on its own, +# independent of the other groups. +# +# This is useful for targets that are updatable, such as a database or a log +# file. Be careful with parallel mode though, to avoid lost updates and +# other inconsistencies. +# +# The target 1300 depends on 1200, 1400 and 1500. The target 1200 is older +# than 1300, therefore nothing is done for it. The other targets are newer +# than 1300, therefore each of them is made, independently from the other. + +.END: + @rm -f dep-double-colon-1??? + +_!= touch -t 202001011200 dep-double-colon-1200 +_!= touch -t 202001011300 dep-double-colon-1300 +_!= touch -t 202001011400 dep-double-colon-1400 +_!= touch -t 202001011500 dep-double-colon-1500 + +all: dep-double-colon-1300 + +dep-double-colon-1300:: dep-double-colon-1200 + : 'Making 1200 ${.TARGET} from ${.ALLSRC} oodate ${.OODATE}' + +dep-double-colon-1300:: dep-double-colon-1400 + : 'Making 1400 ${.TARGET} from ${.ALLSRC} oodate ${.OODATE}' + +dep-double-colon-1300:: dep-double-colon-1500 + : 'Making 1500 ${.TARGET} from ${.ALLSRC} oodate ${.OODATE}' diff --git a/unit-tests/dep-double-colon.mk b/unit-tests/dep-double-colon.mk index de4cd9bc9e33..67a28a4315cd 100644 --- a/unit-tests/dep-double-colon.mk +++ b/unit-tests/dep-double-colon.mk @@ -1,4 +1,4 @@ -# $NetBSD: dep-double-colon.mk,v 1.3 2020/08/22 12:42:32 rillig Exp $ +# $NetBSD: dep-double-colon.mk,v 1.4 2020/09/26 15:41:53 rillig Exp $ # # Tests for the :: operator in dependency declarations. @@ -9,3 +9,12 @@ all:: all:: @echo 'command 2a' @echo 'command 2b' + +# When there are multiple command groups for a '::' target, each of these +# groups is added separately to the .ALLTARGETS variable. +# +# XXX: What is this good for? +# XXX: Where does the leading space come from? +.if ${.ALLTARGETS} != " all all" +. error +.endif diff --git a/unit-tests/dep-percent.exp b/unit-tests/dep-percent.exp new file mode 100644 index 000000000000..2b5b5bf1fa50 --- /dev/null +++ b/unit-tests/dep-percent.exp @@ -0,0 +1,3 @@ +make: don't know how to make dep-percent.o (continuing) +`all' not remade because of errors. +exit status 0 diff --git a/unit-tests/dep-percent.mk b/unit-tests/dep-percent.mk new file mode 100644 index 000000000000..3d6dbeebb6d1 --- /dev/null +++ b/unit-tests/dep-percent.mk @@ -0,0 +1,14 @@ +# $NetBSD: dep-percent.mk,v 1.1 2020/10/23 19:54:35 rillig Exp $ +# +# Test for transformation rules of the form '%.o: %.c', which are supported +# by GNU make but not this make. + +.SUFFIXES: .c .o + +all: dep-percent.o + +%.o: %.c + : 'Making ${.TARGET} from ${.IMPSRC} or ${.ALLSRC}.' + +dep-percent.c: + : 'Making ${.TARGET} out of nothing.' diff --git a/unit-tests/dep-var.exp b/unit-tests/dep-var.exp index 819a939ed70a..d32aca455ceb 100755 --- a/unit-tests/dep-var.exp +++ b/unit-tests/dep-var.exp @@ -1,2 +1,6 @@ +make: Malformed variable expression at "$)" def2 +a-def2-b +1-2-NDIRECT_2-2-1 +) exit status 0 diff --git a/unit-tests/dep-var.mk b/unit-tests/dep-var.mk index b0ce4b2c53ae..438a8a84a60d 100755 --- a/unit-tests/dep-var.mk +++ b/unit-tests/dep-var.mk @@ -1,4 +1,4 @@ -# $NetBSD: dep-var.mk,v 1.1 2020/08/22 16:51:26 rillig Exp $ +# $NetBSD: dep-var.mk,v 1.5 2020/09/13 20:04:26 rillig Exp $ # # Tests for variable references in dependency declarations. # @@ -17,7 +17,7 @@ all: ${UNDEF1} # # At the point where the expression ${DEF2} is expanded, the variable DEF2 # is defined, so everything's fine. -all: $${DEF2} +all: $${DEF2} a-$${DEF2}-b # This variable is not defined at all. # XXX: The -dv log says: @@ -26,8 +26,63 @@ all: $${DEF2} # 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 -undef1 def2: - @echo ${.TARGET} +# 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 +# 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/unit-tests/dep-wildcards.exp b/unit-tests/dep-wildcards.exp index 39a9383953dd..fb8a44e2c80a 100644 --- a/unit-tests/dep-wildcards.exp +++ b/unit-tests/dep-wildcards.exp @@ -1 +1,10 @@ +dep-colon-bug-cross-file.mk +dep-colon.mk +dep-double-colon-indep.mk +dep-double-colon.mk +dep-exclam.mk +dep-none.mk +dep-percent.mk +dep-var.mk +dep-wildcards.mk exit status 0 diff --git a/unit-tests/dep-wildcards.mk b/unit-tests/dep-wildcards.mk index c557efab3ebc..781b149f5a70 100644 --- a/unit-tests/dep-wildcards.mk +++ b/unit-tests/dep-wildcards.mk @@ -1,8 +1,9 @@ -# $NetBSD: dep-wildcards.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: dep-wildcards.mk,v 1.3 2020/09/08 05:33:05 rillig Exp $ # # Tests for wildcards such as *.c in dependency declarations. -# TODO: Implementation - -all: - @:; +all: ${.PARSEDIR}/dep-*.mk + # The :T is necessary to run this test from another directory. + # The :O is necessary since the result of the dependency resolution + # does not order the directory entries itself. + @printf '%s\n' ${.ALLSRC:T:O} diff --git a/unit-tests/depsrc-end.exp b/unit-tests/depsrc-end.exp new file mode 100644 index 000000000000..de0815f2bcb8 --- /dev/null +++ b/unit-tests/depsrc-end.exp @@ -0,0 +1,4 @@ +: 'Making .END.' +: 'Making .BEGIN.' +: 'Making all.' +exit status 0 diff --git a/unit-tests/depsrc-end.mk b/unit-tests/depsrc-end.mk new file mode 100644 index 000000000000..eb7543d5dfad --- /dev/null +++ b/unit-tests/depsrc-end.mk @@ -0,0 +1,14 @@ +# $NetBSD: depsrc-end.mk,v 1.1 2020/10/23 19:23:01 rillig Exp $ +# +# Demonstrate the edge case that .BEGIN depends on .END, which sounds a bit +# paradox but works since these special nodes are not in the dependency +# hierarchy where the cycles are detected. + +.BEGIN: + : 'Making ${.TARGET}.' +.END: + : 'Making ${.TARGET}.' +all: + : 'Making ${.TARGET}.' + +.BEGIN: .END diff --git a/unit-tests/depsrc-exec.exp b/unit-tests/depsrc-exec.exp index 39a9383953dd..5106d5c2a7ac 100644 --- a/unit-tests/depsrc-exec.exp +++ b/unit-tests/depsrc-exec.exp @@ -1 +1,2 @@ +: depsrc-exec.mk: This is always executed. exit status 0 diff --git a/unit-tests/depsrc-exec.mk b/unit-tests/depsrc-exec.mk index a71284d33b3c..e2c2b98aa6e8 100644 --- a/unit-tests/depsrc-exec.mk +++ b/unit-tests/depsrc-exec.mk @@ -1,8 +1,16 @@ -# $NetBSD: depsrc-exec.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-exec.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ # -# Tests for the special source .EXEC in dependency declarations. +# Tests for the special source .EXEC in dependency declarations, +# which always executes the commands, even if the target is up to date. +# The target itself is considered up to date. +# +# TODO: Describe possible use cases for .EXEC. + +all: ${MAKEFILE} ${MAKEFILE:H}/depsrc.mk -# TODO: Implementation +${MAKEFILE}: .EXEC + : ${.TARGET:T}: This is always executed. -all: - @:; +${MAKEFILE:H}/depsrc.mk: + : This is not executed. + +: ${.TARGET:T}: This is not executed as well. diff --git a/unit-tests/depsrc-made.exp b/unit-tests/depsrc-made.exp index 39a9383953dd..9d20ddec7b75 100644 --- a/unit-tests/depsrc-made.exp +++ b/unit-tests/depsrc-made.exp @@ -1 +1,3 @@ +: Making chapter21 +: Making chapter22 exit status 0 diff --git a/unit-tests/depsrc-made.mk b/unit-tests/depsrc-made.mk index 9adeb2487496..851593e38fcc 100644 --- a/unit-tests/depsrc-made.mk +++ b/unit-tests/depsrc-made.mk @@ -1,8 +1,15 @@ -# $NetBSD: depsrc-made.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-made.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ # -# Tests for the special source .MADE in dependency declarations. +# Tests for the special source .MADE in dependency declarations, +# which marks all its dependencies as already made, so their commands +# don't need to be executed. +# +# TODO: Describe a possible use case for .MADE. + +all: part1 part2 -# TODO: Implementation +part1: chapter11 chapter12 .MADE +part2: chapter21 chapter22 -all: - @:; +chapter11 chapter12 chapter21 chapter22: + : Making ${.TARGET} diff --git a/unit-tests/depsrc-make.exp b/unit-tests/depsrc-make.exp index 39a9383953dd..85c936ea4356 100644 --- a/unit-tests/depsrc-make.exp +++ b/unit-tests/depsrc-make.exp @@ -1 +1,3 @@ +this-is-made is made. +echo this-is-not-made is just echoed. exit status 0 diff --git a/unit-tests/depsrc-make.mk b/unit-tests/depsrc-make.mk index 9631f3e913d0..f7b5a9cca8ff 100644 --- a/unit-tests/depsrc-make.mk +++ b/unit-tests/depsrc-make.mk @@ -1,8 +1,16 @@ -# $NetBSD: depsrc-make.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-make.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ # -# Tests for the special source .MAKE in dependency declarations. +# Tests for the special source .MAKE in dependency declarations, which +# executes the commands of the target even if the -n or -t command line +# options are given. -# TODO: Implementation +.MAKEFLAGS: -n -all: - @:; +all: this-is-made +all: this-is-not-made + +this-is-made: .MAKE + @echo ${.TARGET} is made. + +this-is-not-made: + @echo ${.TARGET} is just echoed. diff --git a/unit-tests/depsrc-notmain.exp b/unit-tests/depsrc-notmain.exp index 39a9383953dd..931cfc06f600 100644 --- a/unit-tests/depsrc-notmain.exp +++ b/unit-tests/depsrc-notmain.exp @@ -1 +1,2 @@ +: all exit status 0 diff --git a/unit-tests/depsrc-notmain.mk b/unit-tests/depsrc-notmain.mk index 6e4a68c3b70d..7ba572be7087 100644 --- a/unit-tests/depsrc-notmain.mk +++ b/unit-tests/depsrc-notmain.mk @@ -1,8 +1,11 @@ -# $NetBSD: depsrc-notmain.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-notmain.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ # -# Tests for the special source .NOTMAIN in dependency declarations. +# Tests for the special source .NOTMAIN in dependency declarations, +# which prevents the associated target from becoming the default target +# to be made. -# TODO: Implementation +ignored: .NOTMAIN + : ${.TARGET} all: - @:; + : ${.TARGET} diff --git a/unit-tests/depsrc-optional.exp b/unit-tests/depsrc-optional.exp index 39a9383953dd..05f2ac6624c4 100644 --- a/unit-tests/depsrc-optional.exp +++ b/unit-tests/depsrc-optional.exp @@ -1 +1,2 @@ +`all' is up to date. exit status 0 diff --git a/unit-tests/depsrc-optional.mk b/unit-tests/depsrc-optional.mk index 074dd0b4ef8f..75ae38bf3194 100644 --- a/unit-tests/depsrc-optional.mk +++ b/unit-tests/depsrc-optional.mk @@ -1,8 +1,18 @@ -# $NetBSD: depsrc-optional.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-optional.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ # -# Tests for the special source .OPTIONAL in dependency declarations. +# Tests for the special source .OPTIONAL in dependency declarations, +# which ignores the target if make cannot find out how to create it. +# +# TODO: Describe practical use cases for this feature. + +# TODO: Explain why the commands for "important" are not executed. +# I had thought that only the "optional" commands were skipped. + +all: important + : ${.TARGET} is made. -# TODO: Implementation +important: optional + : ${.TARGET} is made. -all: - @:; +optional: .OPTIONAL + : This is not executed. diff --git a/unit-tests/depsrc-phony.exp b/unit-tests/depsrc-phony.exp index 39a9383953dd..fa062132b0db 100644 --- a/unit-tests/depsrc-phony.exp +++ b/unit-tests/depsrc-phony.exp @@ -1 +1,2 @@ +: depsrc-phony.mk is made. exit status 0 diff --git a/unit-tests/depsrc-phony.mk b/unit-tests/depsrc-phony.mk index 73186e81a5f9..c41efac369a8 100644 --- a/unit-tests/depsrc-phony.mk +++ b/unit-tests/depsrc-phony.mk @@ -1,8 +1,9 @@ -# $NetBSD: depsrc-phony.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-phony.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ # -# Tests for the special source .PHONY in dependency declarations. +# Tests for the special source .PHONY in dependency declarations, +# which executes the commands for the target even if a file of the same +# name exists and would be considered up to date. -# TODO: Implementation - -all: - @:; +# Without the .PHONY, this target would be "up to date". +${MAKEFILE}: .PHONY + : ${.TARGET:T} is made. diff --git a/unit-tests/depsrc-recursive.exp b/unit-tests/depsrc-recursive.exp index 39a9383953dd..85c936ea4356 100644 --- a/unit-tests/depsrc-recursive.exp +++ b/unit-tests/depsrc-recursive.exp @@ -1 +1,3 @@ +this-is-made is made. +echo this-is-not-made is just echoed. exit status 0 diff --git a/unit-tests/depsrc-recursive.mk b/unit-tests/depsrc-recursive.mk index 5c629338bc8d..c1f153787037 100644 --- a/unit-tests/depsrc-recursive.mk +++ b/unit-tests/depsrc-recursive.mk @@ -1,8 +1,16 @@ -# $NetBSD: depsrc-recursive.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-recursive.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ # -# Tests for the special source .RECURSIVE in dependency declarations. +# Tests for the special source .RECURSIVE in dependency declarations, +# which executes the commands of the target even if the -n or -t command +# line options are given. -# TODO: Implementation +.MAKEFLAGS: -n -all: - @:; +all: this-is-made +all: this-is-not-made + +this-is-made: .RECURSIVE + @echo ${.TARGET} is made. + +this-is-not-made: + @echo ${.TARGET} is just echoed. diff --git a/unit-tests/depsrc-wait.exp b/unit-tests/depsrc-wait.exp index 39a9383953dd..d1a60fbaa6e6 100644 --- a/unit-tests/depsrc-wait.exp +++ b/unit-tests/depsrc-wait.exp @@ -1 +1,13 @@ +--- a --- +echo a +a +--- b1 --- +echo b1 +b1 +--- b --- +echo b +b +--- x --- +echo x +x exit status 0 diff --git a/unit-tests/depsrc-wait.mk b/unit-tests/depsrc-wait.mk index bdf3be4608bb..95b0ea96e0a5 100644 --- a/unit-tests/depsrc-wait.mk +++ b/unit-tests/depsrc-wait.mk @@ -1,8 +1,21 @@ -# $NetBSD: depsrc-wait.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-wait.mk,v 1.3 2020/09/07 18:40:32 rillig Exp $ # -# Tests for the special source .WAIT in dependency declarations. +# Tests for the special source .WAIT in dependency declarations, +# which adds a sequence point between the nodes to its left and the nodes +# to its right. -# TODO: Implementation +# Even though the build could run massively parallel, the .WAIT imposes a +# strict ordering in this example, which forces the targets to be made in +# exactly this order. +.MAKEFLAGS: -j8 -all: - @:; +# This is the example from the manual page. +.PHONY: x a b b1 +x: a .WAIT b + echo x +a: + echo a +b: b1 + echo b +b1: + echo b1 diff --git a/unit-tests/deptgt-delete_on_error.exp b/unit-tests/deptgt-delete_on_error.exp index 39a9383953dd..9d9f1dc3e5ec 100644 --- a/unit-tests/deptgt-delete_on_error.exp +++ b/unit-tests/deptgt-delete_on_error.exp @@ -1 +1,51 @@ +Compatibility mode +> deptgt-delete_on_error-regular; false +*** Error code 1 (continuing) +make: *** deptgt-delete_on_error-regular removed +> deptgt-delete_on_error-regular-delete; false +*** Error code 1 (continuing) +make: *** deptgt-delete_on_error-regular-delete removed +> deptgt-delete_on_error-phony; false +*** Error code 1 (continuing) +make: *** deptgt-delete_on_error-phony removed +> deptgt-delete_on_error-phony-delete; false +*** Error code 1 (continuing) +make: *** deptgt-delete_on_error-phony-delete removed +> deptgt-delete_on_error-precious; false +*** Error code 1 (continuing) +> deptgt-delete_on_error-precious-delete; false +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests + +Parallel mode +> deptgt-delete_on_error-regular; false +*** [deptgt-delete_on_error-regular] Error code 1 +make: *** deptgt-delete_on_error-regular removed + +make: stopped in unit-tests +> deptgt-delete_on_error-regular-delete; false +*** [deptgt-delete_on_error-regular-delete] Error code 1 +make: *** deptgt-delete_on_error-regular-delete removed + +make: stopped in unit-tests +> deptgt-delete_on_error-phony; false +*** [deptgt-delete_on_error-phony] Error code 1 + +make: stopped in unit-tests +> deptgt-delete_on_error-phony-delete; false +*** [deptgt-delete_on_error-phony-delete] Error code 1 + +make: stopped in unit-tests +> deptgt-delete_on_error-precious; false +*** [deptgt-delete_on_error-precious] Error code 1 + +make: stopped in unit-tests +> deptgt-delete_on_error-precious-delete; false +*** [deptgt-delete_on_error-precious-delete] Error code 1 + +make: stopped in unit-tests +*** Error code 1 (ignored) +*** Error code 1 (ignored) exit status 0 diff --git a/unit-tests/deptgt-delete_on_error.mk b/unit-tests/deptgt-delete_on_error.mk index 476bb8a54c52..e6d0610f4672 100644 --- a/unit-tests/deptgt-delete_on_error.mk +++ b/unit-tests/deptgt-delete_on_error.mk @@ -1,8 +1,45 @@ -# $NetBSD: deptgt-delete_on_error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ + # $NetBSD: deptgt-delete_on_error.mk,v 1.3 2020/10/25 21:31:00 rillig Exp $ # -# Tests for the special target .DELETE_ON_ERROR in dependency declarations. +# Tests for the special target .DELETE_ON_ERROR in dependency declarations, +# which controls whether the target is deleted if a shell command fails or +# is interrupted. +# +# In compatibility mode, regular and phony targets are deleted, but precious +# targets are preserved. +# +# In parallel mode, regular targets are deleted, while phony and precious +# targets are preserved. +# +# See also: +# CompatDeleteTarget +# JobDeleteTarget -# TODO: Implementation +THIS= deptgt-delete_on_error +TARGETS= ${THIS}-regular ${THIS}-regular-delete +TARGETS+= ${THIS}-phony ${THIS}-phony-delete +TARGETS+= ${THIS}-precious ${THIS}-precious-delete all: - @:; + @rm -f ${TARGETS} + @echo 'Compatibility mode' + @-${.MAKE} -f ${MAKEFILE} -k ${TARGETS} + @rm -f ${TARGETS} + @echo + @echo 'Parallel mode' + @-${.MAKE} -f ${MAKEFILE} -k -j1 ${TARGETS} + @rm -f ${TARGETS} + +${THIS}-regular{,-delete}: + > ${.TARGET}; false + +${THIS}-phony{,-delete}: .PHONY + > ${.TARGET}; false + +${THIS}-precious{,-delete}: .PRECIOUS + > ${.TARGET}; false + +# The special target .DELETE_ON_ERROR is a global setting. +# It does not apply to single targets. +# The following line is therefore misleading but does not generate any +# warning or even an error message. +.DELETE_ON_ERROR: ${TARGETS:M*-delete} diff --git a/unit-tests/deptgt-end-jobs.exp b/unit-tests/deptgt-end-jobs.exp new file mode 100755 index 000000000000..8dd934b146e6 --- /dev/null +++ b/unit-tests/deptgt-end-jobs.exp @@ -0,0 +1,8 @@ +: .BEGIN '${VAR}' +--- all --- +: all '${VAR}' +: .END '${VAR}' +: .END '${VAR}' deferred +: .BEGIN 'Should not be expanded.' deferred +: all 'Should not be expanded.' deferred +exit status 0 diff --git a/unit-tests/deptgt-end-jobs.mk b/unit-tests/deptgt-end-jobs.mk new file mode 100755 index 000000000000..1a5d5c33af99 --- /dev/null +++ b/unit-tests/deptgt-end-jobs.mk @@ -0,0 +1,46 @@ +# $NetBSD: deptgt-end-jobs.mk,v 1.1 2020/09/23 03:06:38 rillig Exp $ +# +# Tests for the special target .END in dependency declarations, +# which is run after making the desired targets. +# +# This test is very similar to deptgt-end.mk, except for the -j option. +# This option enables parallel mode, in which the code from job.c partially +# replaces the code from compat.c. +# +# Before 2020-08-22, this test crashed with a null pointer dereference. +# Before 2020-09-23, this test crashed with an assertion failure. +.MAKEFLAGS: -j 8 + +VAR= Should not be expanded. + +.BEGIN: + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred +# Oops: The deferred command must not be expanded twice. +# The Var_Subst in Compat_RunCommand looks suspicious. +# The Var_Subst in JobSaveCommand looks suspicious. + +.END: + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred + +all: + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred +# Oops: The deferred command must not be expanded twice. +# The Var_Subst in Compat_RunCommand looks suspicious. +# The Var_Subst in JobSaveCommand looks suspicious. + +# The deferred commands are run in the order '.END .BEGIN all'. +# This may be unexpected at first since the natural order would be +# '.BEGIN all .END', but it is implemented correctly. +# +# At the point where the commands of a node with deferred commands are run, +# the deferred commands are appended to the commands of the .END node. +# This happens in Compat_RunCommand, and to prevent an endless loop, the +# deferred commands of the .END node itself are not appended to itself. +# Instead, the deferred commands of the .END node are run as if they were +# immediate commands. diff --git a/unit-tests/deptgt-end.exp b/unit-tests/deptgt-end.exp index 3effe2e3182f..144ee0071fb3 100644 --- a/unit-tests/deptgt-end.exp +++ b/unit-tests/deptgt-end.exp @@ -1,4 +1,9 @@ -: .BEGIN -: all -: .END +: .BEGIN '${VAR}' +: all '${VAR}' +: end-action '${VAR}' +: .END '${VAR}' +: .END '${VAR}' deferred +: .BEGIN 'Should not be expanded.' deferred +: all 'Should not be expanded.' deferred +: end-action 'Should not be expanded.' deferred exit status 0 diff --git a/unit-tests/deptgt-end.mk b/unit-tests/deptgt-end.mk index b3d59a610a2f..1240d6714a25 100644 --- a/unit-tests/deptgt-end.mk +++ b/unit-tests/deptgt-end.mk @@ -1,13 +1,45 @@ -# $NetBSD: deptgt-end.mk,v 1.3 2020/08/29 17:34:21 rillig Exp $ +# $NetBSD: deptgt-end.mk,v 1.6 2020/10/23 19:28:17 rillig Exp $ # # Tests for the special target .END in dependency declarations, # which is run after making the desired targets. +VAR= Should not be expanded. + .BEGIN: - : $@ + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred +# Oops: The deferred command must not be expanded twice. +# The Var_Subst in Compat_RunCommand looks suspicious. +# The Var_Subst in JobSaveCommand looks suspicious. .END: - : $@ + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred + +# The .END node can define dependencies, just like a regular target. +.END: end-action +end-action: .NOTMAIN + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred all: - : $@ + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred +# Oops: The deferred command must not be expanded twice. +# The Var_Subst in Compat_RunCommand looks suspicious. +# The Var_Subst in JobSaveCommand looks suspicious. + +# The deferred commands are run in the order '.END .BEGIN all'. +# This may be unexpected at first since the natural order would be +# '.BEGIN all .END', but it is implemented correctly. +# +# At the point where the commands of a node with deferred commands are run, +# the deferred commands are appended to the commands of the .END node. +# This happens in Compat_RunCommand, and to prevent an endless loop, the +# deferred commands of the .END node itself are not appended to itself. +# Instead, the deferred commands of the .END node are run as if they were +# immediate commands. diff --git a/unit-tests/deptgt-makeflags.exp b/unit-tests/deptgt-makeflags.exp index 39a9383953dd..a226cafc9ba1 100644 --- a/unit-tests/deptgt-makeflags.exp +++ b/unit-tests/deptgt-makeflags.exp @@ -1 +1,9 @@ +Global:delete DOLLAR (not found) +Command:DOLLAR = $$$$ +Global:.MAKEOVERRIDES = VAR DOLLAR +CondParser_Eval: ${DOLLAR} != "\$\$" +Var_Parse: ${DOLLAR} != "\$\$" with VARE_UNDEFERR|VARE_WANTRES +lhs = "$$", rhs = "$$", op = != +Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d +Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d 0 exit status 0 diff --git a/unit-tests/deptgt-makeflags.mk b/unit-tests/deptgt-makeflags.mk index 958751b64cfa..70a9cd0b590b 100644 --- a/unit-tests/deptgt-makeflags.mk +++ b/unit-tests/deptgt-makeflags.mk @@ -1,8 +1,51 @@ -# $NetBSD: deptgt-makeflags.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-makeflags.mk,v 1.4 2020/10/23 14:48:49 rillig Exp $ # -# Tests for the special target .MAKEFLAGS in dependency declarations. +# Tests for the special target .MAKEFLAGS in dependency declarations, +# which adds command line options later, at parse time. -# TODO: Implementation +# The -D option sets a variable in the "Global" scope and thus can be +# undefined later. +.MAKEFLAGS: -D VAR + +.if ${VAR} != 1 +. error +.endif + +.undef VAR + +.if defined(VAR) +. error +.endif + +.MAKEFLAGS: -D VAR + +.if ${VAR} != 1 +. error +.endif + +.MAKEFLAGS: VAR="value"' with'\ spaces + +.if ${VAR} != "value with spaces" +. error +.endif + +# Variables set on the command line as VAR=value are placed in the +# "Command" scope and thus cannot be undefined. +.undef VAR + +.if ${VAR} != "value with spaces" +. error +.endif + +# When parsing this line, each '$$' becomes '$', resulting in '$$$$'. +# This is assigned to the variable DOLLAR. +# In the condition, that variable is expanded, and at that point, each '$$' +# becomes '$' again, the final expression is thus '$$'. +.MAKEFLAGS: -dcv +.MAKEFLAGS: DOLLAR=$$$$$$$$ +.if ${DOLLAR} != "\$\$" +.endif +.MAKEFLAGS: -d0 all: @:; diff --git a/unit-tests/deptgt-silent.exp b/unit-tests/deptgt-silent.exp index 39a9383953dd..6cab88f53144 100644 --- a/unit-tests/deptgt-silent.exp +++ b/unit-tests/deptgt-silent.exp @@ -1 +1,3 @@ +This is not echoed because of the @. +This is not echoed because of the .SILENT. exit status 0 diff --git a/unit-tests/deptgt-silent.mk b/unit-tests/deptgt-silent.mk index 9ae64567fb97..64d54c00e632 100644 --- a/unit-tests/deptgt-silent.mk +++ b/unit-tests/deptgt-silent.mk @@ -1,8 +1,10 @@ -# $NetBSD: deptgt-silent.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-silent.mk,v 1.3 2020/09/10 21:40:50 rillig Exp $ # # Tests for the special target .SILENT in dependency declarations. -# TODO: Implementation +.SILENT: all all: - @:; + @echo 'This is not echoed because of the @.' + # Without the .SILENT, the following command would be echoed. + echo 'This is not echoed because of the .SILENT.' diff --git a/unit-tests/deptgt-suffixes.exp b/unit-tests/deptgt-suffixes.exp index b24f47becdaf..65dc36cfe001 100644 --- a/unit-tests/deptgt-suffixes.exp +++ b/unit-tests/deptgt-suffixes.exp @@ -1,5 +1,5 @@ #*** Suffixes: -# `.custom-null' [1] (SUFF_NULL) +# ".custom-null" (num 1, ref 1) (SUFF_NULL) # To: # From: # Search Path: . .. diff --git a/unit-tests/deptgt.exp b/unit-tests/deptgt.exp index 39a9383953dd..2b899313b938 100644 --- a/unit-tests/deptgt.exp +++ b/unit-tests/deptgt.exp @@ -1 +1,5 @@ -exit status 0 +make: "deptgt.mk" line 10: warning: Extra target ignored +make: "deptgt.mk" line 28: Unassociated shell command ": command3 # parse error, since targets == NULL" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/deptgt.mk b/unit-tests/deptgt.mk index add21dc0baee..517ed5aa2b75 100644 --- a/unit-tests/deptgt.mk +++ b/unit-tests/deptgt.mk @@ -1,9 +1,31 @@ -# $NetBSD: deptgt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt.mk,v 1.8 2020/10/18 13:02:10 rillig Exp $ # # Tests for special targets like .BEGIN or .SUFFIXES in dependency # declarations. # TODO: Implementation +# Just in case anyone tries to compile several special targets in a single +# dependency line: That doesn't work, and make immediately rejects it. +.SUFFIXES .PHONY: .c.o + +# The following lines demonstrate how 'targets' is set and reset during +# parsing of dependencies. To see it in action, set breakpoints in: +# +# ParseDoDependency at the beginning +# FinishDependencyGroup at "targets = NULL" +# Parse_File at "Lst_Free(targets)" +# Parse_File at "targets = Lst_New()" +# ParseLine_ShellCommand at "targets == NULL" +# +# Keywords: +# parse.c:targets + +target1 target2: sources # targets := [target1, target2] + : command1 # targets == [target1, target2] + : command2 # targets == [target1, target2] +VAR=value # targets := NULL + : command3 # parse error, since targets == NULL + all: @:; diff --git a/unit-tests/dir.exp b/unit-tests/dir.exp index 874a081e97ab..1adb42e6c1f7 100644 --- a/unit-tests/dir.exp +++ b/unit-tests/dir.exp @@ -1,19 +1,50 @@ -1 -2 -3 -4 -5 -13 -14 -15 -pre-patch -pre-configure -patch -configure -fetch -fetch-post -extract -extract-post -dup-1 +Searching for .depend ... + failed. +Searching for .depend ... + / ... + failed. +Expanding "{one,two,three}"... one two three +Expanding "f{our,ive}"... four five +Expanding "{{thi,fou}r,fif}teen"... Expanding "{thi,fou}rteen"... thirteen fourteen +thirteen fourteen fifteen +Expanding "{pre-,}{patch,configure}"... Expanding "pre-{patch,configure}"... pre-patch pre-configure +Expanding "{patch,configure}"... pre-patch pre-configure patch configure +pre-patch pre-configure patch configure +Expanding "{fetch,extract}{,-post}"... Expanding "fetch{,-post}"... fetch fetch-post +Expanding "extract{,-post}"... fetch fetch-post extract extract-post +fetch fetch-post extract extract-post +Expanding "dup-{1,1,1,1,1,1,1}"... dup-1 dup-1 dup-1 dup-1 dup-1 dup-1 dup-1 +Expanding "{{{{{{{{{{single-word}}}}}}}}}}"... Expanding "{{{{{{{{{single-word}}}}}}}}}"... Expanding "{{{{{{{{single-word}}}}}}}}"... Expanding "{{{{{{{single-word}}}}}}}"... Expanding "{{{{{{single-word}}}}}}"... Expanding "{{{{{single-word}}}}}"... Expanding "{{{{single-word}}}}"... Expanding "{{{single-word}}}"... Expanding "{{single-word}}"... Expanding "{single-word}"... single-word single-word +single-word +single-word +single-word +single-word +single-word +single-word +single-word +single-word +: 1 +: 2 +: 3 +: 4 +: 5 +: 13 +: 14 +: 15 +: pre-patch +: pre-configure +: patch +: configure +: fetch +: fetch-post +: extract +: extract-post +: dup-1 +: single-word +Searching for .END ... + failed. +Searching for .END ... + failed. +Found '.END' as '(not found)' exit status 0 diff --git a/unit-tests/dir.mk b/unit-tests/dir.mk index f5926375312e..24ce47d959ed 100644 --- a/unit-tests/dir.mk +++ b/unit-tests/dir.mk @@ -1,58 +1,93 @@ -# $NetBSD: dir.mk,v 1.4 2020/07/31 20:16:21 rillig Exp $ +# $NetBSD: dir.mk,v 1.7 2020/10/31 21:30:03 rillig Exp $ # # Tests for dir.c. +.MAKEFLAGS: -m / # hide /usr/share/mk from the debug log + # Dependency lines may use braces for expansion. +# See DirExpandCurly for the implementation. all: {one,two,three} +# XXX: The above dependency line is parsed as a single node named +# "{one,two,three}". There are no individual targets "one", "two", "three" +# yet. The node exists but is not a target since it never appeared +# on the left-hand side of a dependency operator. However, it is listed +# in .ALLTARGETS (which lists all nodes, not only targets). +.if target(one) +. error +.endif +.if target({one,two,three}) +. error +.endif +.if ${.ALLTARGETS:M{one,two,three}} != "{one,two,three}" +. error +.endif + one: - @echo 1 + : 1 two: - @echo 2 + : 2 three: - @echo 3 + : 3 # The braces may start in the middle of a word. all: f{our,ive} four: - @echo 4 + : 4 five: - @echo 5 + : 5 six: - @echo 6 + : 6 -# But nested braces don't work. +# Nested braces work as expected since 2020-07-31 19:06 UTC. +# They had been broken at least since 2003-01-01, probably even longer. all: {{thi,fou}r,fif}teen thirteen: - @echo 13 + : 13 fourteen: - @echo 14 + : 14 fifteen: - @echo 15 + : 15 # There may be multiple brace groups side by side. all: {pre-,}{patch,configure} pre-patch patch pre-configure configure: - @echo $@ + : $@ # Empty pieces are allowed in the braces. all: {fetch,extract}{,-post} fetch fetch-post extract extract-post: - @echo $@ + : $@ # The expansions may have duplicates. # These are merged together because of the dependency line. all: dup-{1,1,1,1,1,1,1} dup-1: - @echo $@ + : $@ # Other than in Bash, the braces are also expanded if there is no comma. all: {{{{{{{{{{single-word}}}}}}}}}} single-word: - @echo $@ + : $@ + +# Demonstrate debug logging for filename expansion, especially curly braces. +.MAKEFLAGS: -dd +# The below line does not call Dir_Expand yet. +# It is expanded only when necessary, that is, when the 'debug' target is +# indeed made. +debug: {{thi,fou}r,fif}twen +# Therefore, keep the debug logging active. + +.PHONY: one two three four five six +.PHONY: thirteen fourteen fifteen +.PHONY: single-word +.PHONY: pre-patch patch pre-configure configure +.PHONY: fetch fetch-post extract extract-post +.PHONY: dup-1 single-word +.PHONY: all diff --git a/unit-tests/directive-dinclude.exp b/unit-tests/directive-dinclude.exp new file mode 100755 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/directive-dinclude.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/directive-dinclude.mk b/unit-tests/directive-dinclude.mk new file mode 100755 index 000000000000..94fa5fb4429b --- /dev/null +++ b/unit-tests/directive-dinclude.mk @@ -0,0 +1,9 @@ +# $NetBSD: directive-dinclude.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $ +# +# Tests for the .dinclude directive, which includes another file, +# typically named .depend. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/directive-else.mk b/unit-tests/directive-else.mk index 3afa5648af67..8fbbb5189ad5 100644 --- a/unit-tests/directive-else.mk +++ b/unit-tests/directive-else.mk @@ -1,19 +1,19 @@ -# $NetBSD: directive-else.mk,v 1.3 2020/08/29 18:50:25 rillig Exp $ +# $NetBSD: directive-else.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for the .else directive. # The .else directive does not take any arguments. # As of 2020-08-29, make doesn't warn about this. .if 0 -.warning must not be reached +. warning must not be reached .else 123 -.info ok +. info ok .endif .if 1 -.info ok +. info ok .else 123 -.warning must not be reached +. warning must not be reached .endif # An .else without a corresponding .if is an error. @@ -21,11 +21,11 @@ # Accidental extra .else directives are detected too. .if 0 -.warning must not be reached +. warning must not be reached .else -.info ok +. info ok .else -.info After an extra .else, everything is skipped. +. info After an extra .else, everything is skipped. .endif all: diff --git a/unit-tests/directive-export-gmake.exp b/unit-tests/directive-export-gmake.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/directive-export-gmake.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/directive-export-gmake.mk b/unit-tests/directive-export-gmake.mk new file mode 100644 index 000000000000..51de8fe55f64 --- /dev/null +++ b/unit-tests/directive-export-gmake.mk @@ -0,0 +1,64 @@ +# $NetBSD: directive-export-gmake.mk,v 1.2 2020/10/19 18:59:53 rillig Exp $ +# +# Tests for the export directive (without leading dot), as in GNU make. + +# The "export" directive only affects the environment of the make process +# and its child processes. It does not affect the global variables or any +# other variables. +VAR= before +export VAR=exported +.if ${VAR} != "before" +. error +.endif + +# Ensure that the name-value pair is actually exported. +.if ${:!echo "\$VAR"!} != "exported" +. error +.endif + +# This line looks like it would export 2 variables, but it doesn't. +# It only exports VAR and appends everything else as the variable value. +export VAR=exported VAR2=exported-as-well +.if ${:!echo "\$VAR"!} != "exported VAR2=exported-as-well" +. error ${:!echo "\$VAR"!} +.endif + +# Contrary to the usual variable assignments, spaces are significant +# after the '=' sign and are prepended to the value of the environment +# variable. +export VAR= leading spaces +.if ${:!echo "\$VAR"!} != " leading spaces" +. error +.endif + +# Contrary to the usual variable assignments, spaces are significant +# before the '=' sign and are appended to the name of the environment +# variable. +# +# Depending on the shell, environment variables with such exotic names +# may be silently discarded. One such shell is dash, which is the default +# shell on Ubuntu and Debian. +export VAR =trailing space in varname +.if ${:!env | grep trailing!} != "VAR =trailing space in varname" +. if ${:!env | grep trailing!} != "" # for dash +. error +. endif +.endif + +# The right-hand side of the exported variable is expanded exactly once. +TWICE= expanded twice +ONCE= expanded once, leaving $${TWICE} as-is +export VAR=${ONCE} +.if ${:!echo "\$VAR"!} != "expanded once, leaving \${TWICE} as-is" +. error +.endif + +# Undefined variables are allowed on the right-hand side, they expand +# to an empty string, as usual. +export VAR=an ${UNDEF} variable +.if ${:!echo "\$VAR"!} != "an variable" +. error +.endif + +all: + @:; diff --git a/unit-tests/directive-export-literal.exp b/unit-tests/directive-export-literal.exp index 39a9383953dd..c5557e363666 100644 --- a/unit-tests/directive-export-literal.exp +++ b/unit-tests/directive-export-literal.exp @@ -1 +1,2 @@ +value with ${UNEXPANDED} expression exit status 0 diff --git a/unit-tests/directive-export-literal.mk b/unit-tests/directive-export-literal.mk index ed3957b288d6..817c836fd6c4 100644 --- a/unit-tests/directive-export-literal.mk +++ b/unit-tests/directive-export-literal.mk @@ -1,8 +1,11 @@ -# $NetBSD: directive-export-literal.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-export-literal.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $ # -# Tests for the .export-literal directive. +# Tests for the .export-literal directive, which exports a variable value +# without expanding it. -# TODO: Implementation +UT_VAR= value with ${UNEXPANDED} expression + +.export-literal UT_VAR all: - @:; + @echo "$$UT_VAR" diff --git a/unit-tests/directive-export.mk b/unit-tests/directive-export.mk index c7f9181b4b5a..6fbf8f09d4dc 100644 --- a/unit-tests/directive-export.mk +++ b/unit-tests/directive-export.mk @@ -1,8 +1,25 @@ -# $NetBSD: directive-export.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-export.mk,v 1.3 2020/10/29 17:27:12 rillig Exp $ # # Tests for the .export directive. # TODO: Implementation +INDIRECT= indirect +VAR= value $$ ${INDIRECT} + +# A variable is exported using the .export directive. +# During that, its value is expanded, just like almost everywhere else. +.export VAR +.if ${:!env | grep '^VAR'!} != "VAR=value \$ indirect" +. error +.endif + +# Undefining a variable that has been exported implicitly removes it from +# the environment of all child processes. +.undef VAR +.if ${:!env | grep '^VAR' || true!} != "" +. error +.endif + all: @:; diff --git a/unit-tests/directive-for.exp b/unit-tests/directive-for.exp index 39a9383953dd..88ab45529e3f 100755 --- a/unit-tests/directive-for.exp +++ b/unit-tests/directive-for.exp @@ -1 +1,19 @@ +make: "directive-for.mk" line 100: outer +make: "directive-for.mk" line 125: a:\ a:\file.txt +make: "directive-for.mk" line 125: d:\\ +make: "directive-for.mk" line 125: d:\\file.txt +make: "directive-for.mk" line 132: ( ( ( +make: "directive-for.mk" line 132: [ [ [ +make: "directive-for.mk" line 132: { { { +make: "directive-for.mk" line 132: ) ) ) +make: "directive-for.mk" line 132: ] ] ] +make: "directive-for.mk" line 132: } } } +make: "directive-for.mk" line 132: (()) (()) (()) +make: "directive-for.mk" line 132: [[]] [[]] [[]] +make: "directive-for.mk" line 132: {{}} {{}} {{}} +make: "directive-for.mk" line 132: )( )( )( +make: "directive-for.mk" line 132: ][ ][ ][ +make: "directive-for.mk" line 132: }{ }{ }{ +make: "directive-for.mk" line 140: outer value value +make: "directive-for.mk" line 140: outer "quoted" \"quoted\" exit status 0 diff --git a/unit-tests/directive-for.mk b/unit-tests/directive-for.mk index 5b3f299e5711..e0987d331a14 100755 --- a/unit-tests/directive-for.mk +++ b/unit-tests/directive-for.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for.mk,v 1.2 2020/09/02 22:58:59 rillig Exp $ +# $NetBSD: directive-for.mk,v 1.8 2020/10/25 15:49:03 rillig Exp $ # # Tests for the .for directive. @@ -81,8 +81,8 @@ var2= value before # Since that date, the .for loop expands to: # EXPANSION${:U+}= value # -EXPANSION= before -EXPANSION+ = before +EXPANSION= before +EXPANSION+ = before .for plus in + EXPANSION${plus}= value .endfor @@ -93,5 +93,52 @@ EXPANSION${plus}= value . error This must be a make from before 2009. .endif +# When the outer .for loop is expanded, it sees the expression ${i} and +# expands it. The inner loop then has nothing more to expand. +.for i in outer +. for i in inner +. info ${i} +. endfor +.endfor + +# From https://gnats.netbsd.org/29985. +# +# Until 2008-12-21, the .for loop was expanded by replacing the variable +# value literally in the body. This could lead to situations where the +# characters from the variable value were interpreted as markup rather than +# plain text. +# +# Until 2012-06-03, the .for loop had split the words at whitespace, without +# taking quotes into account. This made it possible to have variable values +# like "a:\ a:\file.txt" that ended in a single backslash. Since then, the +# variable values have been replaced with expressions of the form ${:U...}, +# which are not interpreted as code anymore. +# +# As of 2020-09-22, a comment in for.c says that it may be possible to +# produce an "unwanted substitution", but there is no demonstration code yet. +# +# The above changes prevent a backslash at the end of a word from being +# interpreted as part of the code. Because of this, the trailingBackslash +# hack in Var_Subst is no longer needed and as of 2020-09-22, has been +# removed. +.for path in a:\ a:\file.txt d:\\ d:\\file.txt +. info ${path} +.endfor + +# Ensure that braces and parentheses are properly escaped by the .for loop. +# Each line must print the same word 3 times. +# See GetEscapes. +.for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{ +. info $v ${v} $(v) +.endfor + +# As of 2020-10-25, the variable names may contain arbitrary characters, +# except for whitespace. This allows for creative side effects. Hopefully +# nobody is misusing this "feature". +var= outer +.for var:Q in value "quoted" +. info ${var} ${var:Q} ${var:Q:Q} +.endfor + all: @:; diff --git a/unit-tests/directive-hyphen-include.exp b/unit-tests/directive-hyphen-include.exp new file mode 100755 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/directive-hyphen-include.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/directive-hyphen-include.mk b/unit-tests/directive-hyphen-include.mk new file mode 100755 index 000000000000..8c851be43215 --- /dev/null +++ b/unit-tests/directive-hyphen-include.mk @@ -0,0 +1,9 @@ +# $NetBSD: directive-hyphen-include.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $ +# +# Tests for the .-include directive, which includes another file, +# silently skipping it if it cannot be opened. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/directive-ifndef.exp b/unit-tests/directive-ifndef.exp index 39a9383953dd..c653f6344429 100644 --- a/unit-tests/directive-ifndef.exp +++ b/unit-tests/directive-ifndef.exp @@ -1 +1,2 @@ +make: "directive-ifndef.mk" line 10: guarded section exit status 0 diff --git a/unit-tests/directive-ifndef.mk b/unit-tests/directive-ifndef.mk index 0981f817fcfd..bf509ef8075e 100644 --- a/unit-tests/directive-ifndef.mk +++ b/unit-tests/directive-ifndef.mk @@ -1,8 +1,24 @@ -# $NetBSD: directive-ifndef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-ifndef.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $ # -# Tests for the .ifndef directive. +# Tests for the .ifndef directive, which can be used for multiple-inclusion +# guards. In contrast to C, where #ifndef and #define nicely line up the +# macro name, there is no such syntax in make. Therefore, it is more +# common to use .if !defined(GUARD) instead. -# TODO: Implementation +.ifndef GUARD +GUARD= # defined +. info guarded section +.endif + +.ifndef GUARD +GUARD= # defined +. info guarded section +.endif + +.if !defined(GUARD) +GUARD= # defined +. info guarded section +.endif all: @:; diff --git a/unit-tests/directive-ifnmake.exp b/unit-tests/directive-ifnmake.exp index 39a9383953dd..31a5a8043036 100644 --- a/unit-tests/directive-ifnmake.exp +++ b/unit-tests/directive-ifnmake.exp @@ -1 +1,3 @@ +Don't forget to run the tests (1) +Running the tests exit status 0 diff --git a/unit-tests/directive-ifnmake.mk b/unit-tests/directive-ifnmake.mk index 2a20249f6c76..4bdb12a46aa1 100644 --- a/unit-tests/directive-ifnmake.mk +++ b/unit-tests/directive-ifnmake.mk @@ -1,8 +1,22 @@ -# $NetBSD: directive-ifnmake.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-ifnmake.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $ # -# Tests for the .ifnmake directive. - -# TODO: Implementation +# Tests for the .ifnmake directive, which evaluates to true if its argument +# is _not_ listed in the command-line targets to be created. all: @:; + +.ifnmake(test) +.BEGIN: + @echo "Don't forget to run the tests (1)" +.endif + +.MAKEFLAGS: test + +.ifnmake(test) +.BEGIN: + @echo "Don't forget to run the tests (2)" +.endif + +test: + @echo "Running the tests" diff --git a/unit-tests/directive-include-fatal.exp b/unit-tests/directive-include-fatal.exp new file mode 100755 index 000000000000..c8ca97a0fd5f --- /dev/null +++ b/unit-tests/directive-include-fatal.exp @@ -0,0 +1,4 @@ +make: "directive-include-fatal.mk" line 13: Malformed conditional (${UNDEF}) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-include-fatal.mk b/unit-tests/directive-include-fatal.mk new file mode 100755 index 000000000000..22b30ae909c2 --- /dev/null +++ b/unit-tests/directive-include-fatal.mk @@ -0,0 +1,27 @@ +# $NetBSD: directive-include-fatal.mk,v 1.2 2020/09/13 10:20:11 rillig Exp $ +# +# Test for the .include directive combined with fatal errors. +# +# At 2020-09-13, the code in Parse_File that sets "fatals = 0" looked +# suspicious, as if it were possible to suppress fatal errors by including +# another file. It was a false alarm though, since Parse_File only handles +# the top-level makefiles from the command line. Any included files are +# handled by Parse_include_file instead, and that function does not reset +# the "fatals" counter. + +# Using an undefined variable in a condition generates a fatal error. +.if ${UNDEF} +.endif + +# Including another file does not reset the global variable "fatals". +# The exit status will be 1. +.include "/dev/null" + +# Adding another file to be included has no effect either. +# When the command line is parsed, the additional file is only enqueued +# in the global "makefiles" variable, but not immediately run through +# Parse_File. +.MAKEFLAGS: -f "/dev/null" + +all: + @:; diff --git a/unit-tests/directive-include.exp b/unit-tests/directive-include.exp new file mode 100755 index 000000000000..3a75ce38728b --- /dev/null +++ b/unit-tests/directive-include.exp @@ -0,0 +1,5 @@ +CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" +lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = != +CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" +lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = != +exit status 0 diff --git a/unit-tests/directive-include.mk b/unit-tests/directive-include.mk new file mode 100755 index 000000000000..d71b27ef2c63 --- /dev/null +++ b/unit-tests/directive-include.mk @@ -0,0 +1,26 @@ +# $NetBSD: directive-include.mk,v 1.3 2020/10/31 23:01:23 rillig Exp $ +# +# Tests for the .include directive, which includes another file. + +# TODO: Implementation + +.MAKEFLAGS: -dc + +# All included files are recorded in the variable .MAKE.MAKEFILES. +# In this test, only the basenames of the files are compared since +# the directories can differ. +.include "/dev/null" +.if ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" +. error +.endif + +# Each file is recorded only once in the variable .MAKE.MAKEFILES. +# Between 2015-11-26 and 2020-10-31, the very last file could be repeated, +# due to an off-by-one bug in ParseTrackInput. +.include "/dev/null" +.if ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" +. error +.endif + +all: + @:; diff --git a/unit-tests/directive-sinclude.exp b/unit-tests/directive-sinclude.exp new file mode 100755 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/directive-sinclude.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/directive-sinclude.mk b/unit-tests/directive-sinclude.mk new file mode 100755 index 000000000000..b1faa7d41e65 --- /dev/null +++ b/unit-tests/directive-sinclude.mk @@ -0,0 +1,9 @@ +# $NetBSD: directive-sinclude.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $ +# +# Tests for the .sinclude directive, which includes another file, +# silently skipping it if it cannot be opened. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/directive-undef.mk b/unit-tests/directive-undef.mk index ff91eeddb41f..aac110437686 100644 --- a/unit-tests/directive-undef.mk +++ b/unit-tests/directive-undef.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-undef.mk,v 1.3 2020/08/23 19:30:13 rillig Exp $ +# $NetBSD: directive-undef.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for the .undef directive. @@ -10,7 +10,7 @@ 3= 3 .undef 1 2 3 .if ${1:U_}${2:U_}${3:U_} != _23 -.warning $1$2$3 +. warning $1$2$3 .endif all: diff --git a/unit-tests/directive-unexport.exp b/unit-tests/directive-unexport.exp index 39a9383953dd..263a2fedcc0b 100644 --- a/unit-tests/directive-unexport.exp +++ b/unit-tests/directive-unexport.exp @@ -1 +1,5 @@ +make: "directive-unexport.mk" line 14: UT_A=a UT_B=b UT_C=c +make: "directive-unexport.mk" line 15: UT_A UT_B UT_C +make: "directive-unexport.mk" line 23: UT_A=a UT_B=b UT_C=c +make: "directive-unexport.mk" line 24: exit status 0 diff --git a/unit-tests/directive-unexport.mk b/unit-tests/directive-unexport.mk index 679387aac083..b44932904805 100644 --- a/unit-tests/directive-unexport.mk +++ b/unit-tests/directive-unexport.mk @@ -1,8 +1,27 @@ -# $NetBSD: directive-unexport.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-unexport.mk,v 1.4 2020/10/30 23:54:42 sjg Exp $ # # Tests for the .unexport directive. # TODO: Implementation +# First, export 3 variables. +UT_A= a +UT_B= b +UT_C= c +.export UT_A UT_B UT_C + +# Show the exported variables and their values. +.info ${:!env|sort|grep '^UT_'!} +.info ${.MAKE.EXPORTED} + +# XXX: Now try to unexport all of them. The variables are still exported +# but not mentioned in .MAKE.EXPORTED anymore. +# See the ":N" in Var_UnExport for the implementation. +*= asterisk +.unexport * + +.info ${:!env|sort|grep '^UT_'!} +.info ${.MAKE.EXPORTED} + all: @:; diff --git a/unit-tests/directives.mk b/unit-tests/directives.mk index 50e8e1315dda..436a5d35e4ec 100644 --- a/unit-tests/directives.mk +++ b/unit-tests/directives.mk @@ -1,4 +1,4 @@ -# $NetBSD: directives.mk,v 1.5 2020/07/28 20:57:59 rillig Exp $ +# $NetBSD: directives.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $ # # Tests for parsing directives, in the same order as in the manual page. # @@ -120,21 +120,21 @@ .info which branch is taken on misspelling after false? .if 0 .elsif 1 -.info 1 taken +. info 1 taken .elsif 2 -.info 2 taken +. info 2 taken .else -.info else taken +. info else taken .endif .info which branch is taken on misspelling after true? .if 1 .elsif 1 -.info 1 taken +. info 1 taken .elsif 2 -.info 2 taken +. info 2 taken .else -.info else taken +. info else taken .endif .indented none diff --git a/unit-tests/doterror.mk b/unit-tests/doterror.mk index 9030dce93bee..d46fb3581a25 100644 --- a/unit-tests/doterror.mk +++ b/unit-tests/doterror.mk @@ -1,4 +1,4 @@ -# $Id: doterror.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# $NetBSD: doterror.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ .BEGIN: diff --git a/unit-tests/dotwait.mk b/unit-tests/dotwait.mk index bab5993e7481..dbc502651c54 100644 --- a/unit-tests/dotwait.mk +++ b/unit-tests/dotwait.mk @@ -1,9 +1,9 @@ -# $NetBSD: dotwait.mk,v 1.2 2017/10/08 20:44:19 sjg Exp $ +# $NetBSD: dotwait.mk,v 1.3 2020/10/24 08:50:17 rillig Exp $ -THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE} +THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE} -TESTS= simple recursive shared cycle -PAUSE= sleep 1 +TESTS= simple recursive shared cycle +PAUSE= sleep 1 # Use a .for loop rather than dependencies here, to ensure # that the tests are run one by one, with parallelism diff --git a/unit-tests/envfirst.mk b/unit-tests/envfirst.mk index f4744bd61285..fedcc0a75056 100644 --- a/unit-tests/envfirst.mk +++ b/unit-tests/envfirst.mk @@ -1,41 +1,41 @@ -# $NetBSD: envfirst.mk,v 1.2 2020/07/27 18:57:42 rillig Exp $ +# $NetBSD: envfirst.mk,v 1.3 2020/10/24 08:46:08 rillig Exp $ # # The -e option makes environment variables stronger than global variables. .if ${FROM_ENV} != value-from-env -.error ${FROM_ENV} +. error ${FROM_ENV} .endif # Try to override the variable; this does not have any effect. FROM_ENV= value-from-mk .if ${FROM_ENV} != value-from-env -.error ${FROM_ENV} +. error ${FROM_ENV} .endif # Try to append to the variable; this also doesn't have any effect. FROM_ENV+= appended .if ${FROM_ENV} != value-from-env -.error ${FROM_ENV} +. error ${FROM_ENV} .endif # The default assignment also cannot change the variable. FROM_ENV?= default .if ${FROM_ENV} != value-from-env -.error ${FROM_ENV} +. error ${FROM_ENV} .endif # Neither can the assignment modifiers. .if ${FROM_ENV::=from-condition} .endif .if ${FROM_ENV} != value-from-env -.error ${FROM_ENV} +. error ${FROM_ENV} .endif # Even .undef doesn't work since it only affects the global context, # which is independent from the environment variables. .undef FROM_ENV .if ${FROM_ENV} != value-from-env -.error ${FROM_ENV} +. error ${FROM_ENV} .endif all: diff --git a/unit-tests/error.exp b/unit-tests/error.exp index a2bf71b7127f..8d0ebef89e8e 100644 --- a/unit-tests/error.exp +++ b/unit-tests/error.exp @@ -1,4 +1,6 @@ make: "error.mk" line 3: just FYI make: "error.mk" line 4: warning: this could be serious make: "error.mk" line 5: this is fatal + +make: stopped in unit-tests exit status 1 diff --git a/unit-tests/error.mk b/unit-tests/error.mk index c6b553e2e48a..6e2654dbea72 100644 --- a/unit-tests/error.mk +++ b/unit-tests/error.mk @@ -1,4 +1,4 @@ -# $Id: error.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# $NetBSD: error.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ .info just FYI .warning this could be serious diff --git a/unit-tests/escape.mk b/unit-tests/escape.mk index be9c4dd60e3f..264b124bf1ad 100644 --- a/unit-tests/escape.mk +++ b/unit-tests/escape.mk @@ -1,4 +1,4 @@ -# $Id: escape.mk,v 1.1.1.3 2020/01/22 01:07:14 sjg Exp $ +# $NetBSD: escape.mk,v 1.13 2020/10/24 08:50:17 rillig Exp $ # # Test backslash escaping. @@ -44,8 +44,8 @@ all: .PHONY # Some variables to be expanded in tests # -a = aaa -A = ${a} +a= aaa +A= ${a} # Backslash at end of line in a comment\ should continue the comment. \ @@ -57,13 +57,13 @@ __printvars: .USE .MADE # Embedded backslash in variable should be taken literally. # -VAR1BS = 111\111 -VAR1BSa = 111\${a} -VAR1BSA = 111\${A} -VAR1BSda = 111\$${a} -VAR1BSdA = 111\$${A} -VAR1BSc = 111\# backslash escapes comment char, so this is part of the value -VAR1BSsc = 111\ # This is a comment. Value ends with <backslash><space> +VAR1BS= 111\111 +VAR1BSa= 111\${a} +VAR1BSA= 111\${A} +VAR1BSda= 111\$${a} +VAR1BSdA= 111\$${A} +VAR1BSc= 111\# backslash escapes comment char, so this is part of the value +VAR1BSsc= 111\ # This is a comment. Value ends with <backslash><space> all: var-1bs var-1bs: .PHONY __printvars VAR1BS VAR1BSa VAR1BSA VAR1BSda VAR1BSdA \ @@ -71,13 +71,13 @@ var-1bs: .PHONY __printvars VAR1BS VAR1BSa VAR1BSA VAR1BSda VAR1BSdA \ # Double backslash in variable should be taken as two literal backslashes. # -VAR2BS = 222\\222 -VAR2BSa = 222\\${a} -VAR2BSA = 222\\${A} -VAR2BSda = 222\\$${a} -VAR2BSdA = 222\\$${A} -VAR2BSc = 222\\# backslash does not escape comment char, so this is a comment -VAR2BSsc = 222\\ # This is a comment. Value ends with <backslash><backslash> +VAR2BS= 222\\222 +VAR2BSa= 222\\${a} +VAR2BSA= 222\\${A} +VAR2BSda= 222\\$${a} +VAR2BSdA= 222\\$${A} +VAR2BSc= 222\\# backslash does not escape comment char, so this is a comment +VAR2BSsc= 222\\ # This is a comment. Value ends with <backslash><backslash> all: var-2bs var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \ @@ -85,19 +85,19 @@ var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \ # Backslash-newline in a variable setting is replaced by a single space. # -VAR1BSNL = 111\ +VAR1BSNL= 111\ 111 -VAR1BSNLa = 111\ +VAR1BSNLa= 111\ ${a} -VAR1BSNLA = 111\ +VAR1BSNLA= 111\ ${A} -VAR1BSNLda = 111\ +VAR1BSNLda= 111\ $${a} -VAR1BSNLdA = 111\ +VAR1BSNLdA= 111\ $${A} -VAR1BSNLc = 111\ +VAR1BSNLc= 111\ # this should be processed as a comment -VAR1BSNLsc = 111\ +VAR1BSNLsc= 111\ # this should be processed as a comment all: var-1bsnl @@ -113,19 +113,19 @@ var-1bsnl: .PHONY __printvars \ # generate syntax errors regardless of whether or not they are # treated as part of the value. # -VAR2BSNL = 222\\ +VAR2BSNL= 222\\ 222= -VAR2BSNLa = 222\\ +VAR2BSNLa= 222\\ ${a}= -VAR2BSNLA = 222\\ +VAR2BSNLA= 222\\ ${A}= -VAR2BSNLda = 222\\ +VAR2BSNLda= 222\\ $${a}= -VAR2BSNLdA = 222\\ +VAR2BSNLdA= 222\\ $${A}= -VAR2BSNLc = 222\\ +VAR2BSNLc= 222\\ # this should be processed as a comment -VAR2BSNLsc = 222\\ +VAR2BSNLsc= 222\\ # this should be processed as a comment all: var-2bsnl @@ -140,19 +140,19 @@ var-2bsnl: .PHONY __printvars \ # generate syntax errors regardless of whether or not they are # treated as part of the value. # -VAR3BSNL = 333\\\ +VAR3BSNL= 333\\\ 333= -VAR3BSNLa = 333\\\ +VAR3BSNLa= 333\\\ ${a}= -VAR3BSNLA = 333\\\ +VAR3BSNLA= 333\\\ ${A}= -VAR3BSNLda = 333\\\ +VAR3BSNLda= 333\\\ $${a}= -VAR3BSNLdA = 333\\\ +VAR3BSNLdA= 333\\\ $${A}= -VAR3BSNLc = 333\\\ +VAR3BSNLc= 333\\\ # this should be processed as a comment -VAR3BSNLsc = 333\\\ +VAR3BSNLsc= 333\\\ # this should be processed as a comment all: var-3bsnl @@ -163,20 +163,20 @@ var-3bsnl: .PHONY __printvars \ # Backslash-newline in a variable setting, plus any amount of white space # on the next line, is replaced by a single space. # -VAR1BSNL00= first line\ +VAR1BSNL00= first line\ # above line is entirely empty, and this is a comment -VAR1BSNL0= first line\ +VAR1BSNL0= first line\ no space on second line -VAR1BSNLs= first line\ +VAR1BSNLs= first line\ one space on second line -VAR1BSNLss= first line\ +VAR1BSNLss= first line\ two spaces on second line -VAR1BSNLt= first line\ +VAR1BSNLt= first line\ one tab on second line -VAR1BSNLtt= first line\ +VAR1BSNLtt= first line\ two tabs on second line -VAR1BSNLxx= first line\ +VAR1BSNLxx= first line\ many spaces and tabs [ ] on second line all: var-1bsnl-space diff --git a/unit-tests/export-all.mk b/unit-tests/export-all.mk index c1910cac96b9..0d741083441b 100644 --- a/unit-tests/export-all.mk +++ b/unit-tests/export-all.mk @@ -1,24 +1,24 @@ -# $Id: export-all.mk,v 1.1.1.3 2020/07/28 16:57:18 sjg Exp $ +# $NetBSD: export-all.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $ -UT_OK=good -UT_F=fine +UT_OK= good +UT_F= fine # the old way to do :tA -M_tAbad = C,.*,cd & \&\& 'pwd',:sh +M_tAbad= C,.*,cd & \&\& 'pwd',:sh # the new -M_tA = tA +M_tA= tA here := ${.PARSEDIR} # this will cause trouble (recursing if we let it) -UT_BADDIR = ${${here}/../${here:T}:L:${M_tAbad}:T} +UT_BADDIR= ${${here}/../${here:T}:L:${M_tAbad}:T} # this will be ok -UT_OKDIR = ${${here}/../${here:T}:L:${M_tA}:T} +UT_OKDIR= ${${here}/../${here:T}:L:${M_tA}:T} .export FILTER_CMD= grep ^UT_ .include "export.mk" -UT_TEST=export-all -UT_ALL=even this gets exported +UT_TEST= export-all +UT_ALL= even this gets exported diff --git a/unit-tests/export-env.mk b/unit-tests/export-env.mk index 93aa6721a23e..1605b1a71d61 100644 --- a/unit-tests/export-env.mk +++ b/unit-tests/export-env.mk @@ -1,14 +1,14 @@ -# $Id: export-env.mk,v 1.1.1.2 2016/02/18 20:35:24 sjg Exp $ +# $NetBSD: export-env.mk,v 1.4 2020/10/24 08:50:17 rillig Exp $ # our normal .export, subsequent changes affect the environment -UT_TEST=this +UT_TEST= this .export UT_TEST -UT_TEST:= ${.PARSEFILE} +UT_TEST:= ${.PARSEFILE} # not so with .export-env -UT_ENV=exported +UT_ENV= exported .export-env UT_ENV -UT_ENV=not-exported +UT_ENV= not-exported # gmake style export goes further; affects nothing but the environment UT_EXP=before-export diff --git a/unit-tests/export-variants.mk b/unit-tests/export-variants.mk index 1e254f1816cf..fdbbccccc6c6 100755 --- a/unit-tests/export-variants.mk +++ b/unit-tests/export-variants.mk @@ -1,4 +1,4 @@ -# $NetBSD: export-variants.mk,v 1.2 2020/08/08 13:00:07 rillig Exp $ +# $NetBSD: export-variants.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Test whether exported variables apply to each variant of running # external commands: @@ -9,15 +9,15 @@ SHVAR!= env | grep ^UT_ || true .if ${SHVAR} != "" -.warning At this point, no variable should be exported. +. warning At this point, no variable should be exported. .endif .if ${:!env | grep ^UT_ || true!} != "" -.warning At this point, no variable should be exported. +. warning At this point, no variable should be exported. .endif .if ${env | grep ^UT_ || true:L:sh} != "" -.warning At this point, no variable should be exported. +. warning At this point, no variable should be exported. .endif UT_VAR= value @@ -25,15 +25,15 @@ UT_VAR= value SHVAR!= env | grep ^UT_ || true .if ${SHVAR} != "UT_VAR=value" -.warning At this point, no variable should be exported. +. warning At this point, a single variable should be exported. .endif .if ${:!env | grep ^UT_ || true!} != "UT_VAR=value" -.warning At this point, some variables should be exported. +. warning At this point, a single variable should be exported. .endif .if ${env | grep ^UT_ || true:L:sh} != "UT_VAR=value" -.warning At this point, some variables should be exported. +. warning At this point, a single variable should be exported. .endif all: diff --git a/unit-tests/export.exp b/unit-tests/export.exp index c0e9b98c5a44..b5ccf2168dfc 100644 --- a/unit-tests/export.exp +++ b/unit-tests/export.exp @@ -1,4 +1,3 @@ -&=ampersand MAKELEVEL=1 UT_DOLLAR=This is $UT_FU UT_FOO=foobar is fubar diff --git a/unit-tests/export.mk b/unit-tests/export.mk index b98d175af314..94e3a862dce1 100644 --- a/unit-tests/export.mk +++ b/unit-tests/export.mk @@ -1,18 +1,18 @@ -# $Id: export.mk,v 1.1.1.4 2020/08/08 22:34:25 sjg Exp $ +# $NetBSD: export.mk,v 1.10 2020/10/24 08:50:17 rillig Exp $ -UT_TEST=export -UT_FOO=foo${BAR} -UT_FU=fubar -UT_ZOO=hoopie -UT_NO=all +UT_TEST= export +UT_FOO= foo${BAR} +UT_FU= fubar +UT_ZOO= hoopie +UT_NO= all # believe it or not, we expect this one to come out with $UT_FU unexpanded. -UT_DOLLAR= This is $$UT_FU +UT_DOLLAR= This is $$UT_FU .export UT_FU UT_FOO .export UT_DOLLAR .if !defined(.MAKE.PID) -.error .MAKE.PID must be defined +. error .MAKE.PID must be defined .endif @= at %= percent @@ -29,15 +29,18 @@ ${:U!}= exclamation # A direct != would try to run "exclamation" .export % .export * .export ! +# This is exported (see the .rawout file) but not displayed since the dash +# shell filters it out. To reach consistent output for each shell, the +# ampersand is filtered out already by FILTER_CMD. .export & # This is ignored because it is undefined. .export UNDEFINED -BAR=bar is ${UT_FU} +BAR= bar is ${UT_FU} -.MAKE.EXPORTED+= UT_ZOO UT_TEST +.MAKE.EXPORTED+= UT_ZOO UT_TEST -FILTER_CMD?= egrep -v '^(MAKEFLAGS|PATH|PWD|SHLVL|_)=' +FILTER_CMD?= egrep -v '^(MAKEFLAGS|MALLOC_OPTIONS|PATH|PWD|SHLVL|_|&)=' all: @env | ${FILTER_CMD} | sort diff --git a/unit-tests/forloop.exp b/unit-tests/forloop.exp index 998456190737..63b67c15bcc6 100644 --- a/unit-tests/forloop.exp +++ b/unit-tests/forloop.exp @@ -13,7 +13,7 @@ a=four b="five" a=ONE b="TWO AND THREE" a=FOUR b="FIVE" We expect an error next: -make: "forloop.mk" line 46: Wrong number of words (9) in .for substitution list with 2 vars +make: "forloop.mk" line 46: Wrong number of words (9) in .for substitution list with 2 variables make: Fatal errors encountered -- cannot continue make: stopped in unit-tests OK diff --git a/unit-tests/forloop.mk b/unit-tests/forloop.mk index d61a53816df7..0162ffa27a07 100644 --- a/unit-tests/forloop.mk +++ b/unit-tests/forloop.mk @@ -1,50 +1,50 @@ -# $Id: forloop.mk,v 1.1.1.3 2020/09/02 18:35:14 sjg Exp $ +# $NetBSD: forloop.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $ all: for-loop -LIST = one "two and three" four "five" +LIST= one "two and three" four "five" .if make(for-fail) for-fail: -XTRA_LIST = xtra +XTRA_LIST= xtra .else -.for x in ${LIST} -X!= echo 'x=$x' >&2; echo -.endfor +. for x in ${LIST} +X!= echo 'x=$x' >&2; echo +. endfor -CFL = -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" +CFL= -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" cfl= -.for x in ${CFL} -X!= echo 'x=$x' >&2; echo -.if empty(cfl) -cfl= $x -.else -cfl+= $x -.endif -.endfor -X!= echo 'cfl=${cfl}' >&2; echo - -.if ${cfl} != ${CFL} -.error ${.newline}'${cfl}' != ${.newline}'${CFL}' -.endif - -.for a b in ${EMPTY} -X!= echo 'a=$a b=$b' >&2; echo -.endfor +. for x in ${CFL} +X!= echo 'x=$x' >&2; echo +. if empty(cfl) +cfl= $x +. else +cfl+= $x +. endif +. endfor +X!= echo 'cfl=${cfl}' >&2; echo + +. if ${cfl} != ${CFL} +. error ${.newline}'${cfl}' != ${.newline}'${CFL}' +. endif + +. for a b in ${EMPTY} +X!= echo 'a=$a b=$b' >&2; echo +. endfor # Since at least 1993, iteration stops at the first newline. # Back then, the .newline variable didn't exist, therefore it was unlikely # that a newline ever occurred. -.for var in a${.newline}b${.newline}c -X!= echo 'newline-item=('${var:Q}')' 1>&2; echo -.endfor +. for var in a${.newline}b${.newline}c +X!= echo 'newline-item=('${var:Q}')' 1>&2; echo +. endfor .endif # for-fail .for a b in ${LIST} ${LIST:tu} ${XTRA_LIST} -X!= echo 'a=$a b=$b' >&2; echo +X!= echo 'a=$a b=$b' >&2; echo .endfor for-loop: diff --git a/unit-tests/forsubst.mk b/unit-tests/forsubst.mk index 2923e4b369f9..79af3d9a45fb 100644 --- a/unit-tests/forsubst.mk +++ b/unit-tests/forsubst.mk @@ -1,4 +1,4 @@ -# $Id: forsubst.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# $NetBSD: forsubst.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ all: for-subst diff --git a/unit-tests/hanoi-include.exp b/unit-tests/hanoi-include.exp new file mode 100644 index 000000000000..ead78b9eb908 --- /dev/null +++ b/unit-tests/hanoi-include.exp @@ -0,0 +1,32 @@ +Move the upper disk from stack A to stack C. +Move the upper disk from stack A to stack B. +Move the upper disk from stack C to stack B. +Move the upper disk from stack A to stack C. +Move the upper disk from stack B to stack A. +Move the upper disk from stack B to stack C. +Move the upper disk from stack A to stack C. +Move the upper disk from stack A to stack B. +Move the upper disk from stack C to stack B. +Move the upper disk from stack C to stack A. +Move the upper disk from stack B to stack A. +Move the upper disk from stack C to stack B. +Move the upper disk from stack A to stack C. +Move the upper disk from stack A to stack B. +Move the upper disk from stack C to stack B. +Move the upper disk from stack A to stack C. +Move the upper disk from stack B to stack A. +Move the upper disk from stack B to stack C. +Move the upper disk from stack A to stack C. +Move the upper disk from stack B to stack A. +Move the upper disk from stack C to stack B. +Move the upper disk from stack C to stack A. +Move the upper disk from stack B to stack A. +Move the upper disk from stack B to stack C. +Move the upper disk from stack A to stack C. +Move the upper disk from stack A to stack B. +Move the upper disk from stack C to stack B. +Move the upper disk from stack A to stack C. +Move the upper disk from stack B to stack A. +Move the upper disk from stack B to stack C. +Move the upper disk from stack A to stack C. +exit status 0 diff --git a/unit-tests/hanoi-include.mk b/unit-tests/hanoi-include.mk new file mode 100644 index 000000000000..3ad0a751845a --- /dev/null +++ b/unit-tests/hanoi-include.mk @@ -0,0 +1,41 @@ +# $NetBSD: hanoi-include.mk,v 1.1 2020/10/03 17:30:54 rillig Exp $ +# +# Implements the Towers of Hanoi puzzle, thereby demonstrating a bunch of +# useful programming techniques: +# +# * default assignment using the ?= assignment operator +# * including the same file recursively +# * extracting the current value of a variable using the .for loop +# * using shell commands for calculations since make is a text processor +# * using the :: dependency operator for adding commands to a target +# * on-the-fly variable assignment expressions using the ::= modifier +# +# usage: +# env N=3 make -f hanoi-include.mk +# endless loop: +# make -f hanoi-include.mk N=3 + +N?= 5 # Move this number of disks ... +FROM?= A # ... from this stack ... +VIA?= B # ... via this stack ... +TO?= C # ... to this stack. + +.if $N == 1 +. for from to in ${FROM} ${TO} +all:: + @echo "Move the upper disk from stack ${from} to stack ${to}." +. endfor +.else +_:= ${N::!=expr $N - 1} ${TMP::=${VIA}} ${VIA::=${TO}} ${TO::=${TMP}} +. include "${.PARSEDIR}/${.PARSEFILE}" +_:= ${N::!=expr $N + 1} ${TMP::=${VIA}} ${VIA::=${TO}} ${TO::=${TMP}} + +. for from to in ${FROM} ${TO} +all:: + @echo "Move the upper disk from stack ${from} to stack ${to}." +. endfor + +_:= ${N::!=expr $N - 1} ${TMP::=${VIA}} ${VIA::=${FROM}} ${FROM::=${TMP}} +. include "${.PARSEDIR}/${.PARSEFILE}" +_:= ${N::!=expr $N + 1} ${TMP::=${VIA}} ${VIA::=${FROM}} ${FROM::=${TMP}} +.endif diff --git a/unit-tests/hash.exp b/unit-tests/hash.exp deleted file mode 100644 index 0a2423436e77..000000000000 --- a/unit-tests/hash.exp +++ /dev/null @@ -1,9 +0,0 @@ -b2af338b -3360ac65 -7747f046 -9ca87054 -880fe816 -208fcbd3 -d5d376eb -de41416c -exit status 0 diff --git a/unit-tests/hash.mk b/unit-tests/hash.mk deleted file mode 100644 index 1ed84e776d09..000000000000 --- a/unit-tests/hash.mk +++ /dev/null @@ -1,18 +0,0 @@ -STR1= -STR2= a -STR3= ab -STR4= abc -STR5= abcd -STR6= abcde -STR7= abcdef -STR8= abcdefghijklmnopqrstuvwxyz - -all: - @echo ${STR1:hash} - @echo ${STR2:hash} - @echo ${STR3:hash} - @echo ${STR4:hash} - @echo ${STR5:hash} - @echo ${STR6:hash} - @echo ${STR7:hash} - @echo ${STR8:hash} diff --git a/unit-tests/include-main.exp b/unit-tests/include-main.exp index 7a55c6e97dce..f94ae722f095 100644 --- a/unit-tests/include-main.exp +++ b/unit-tests/include-main.exp @@ -1,6 +1,10 @@ -main-before-ok -sub-before-ok -subsub-ok -sub-after-fail(include-sub.mk) -main-after-fail(include-sub.mk) +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 +make: "include-subsub.mk" line 4: subsub-ok +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/unit-tests/include-main.mk b/unit-tests/include-main.mk index a53dd886b800..0d3ab999843d 100644 --- a/unit-tests/include-main.mk +++ b/unit-tests/include-main.mk @@ -1,30 +1,43 @@ -# $NetBSD: include-main.mk,v 1.2 2020/07/27 20:55:59 rillig Exp $ +# $NetBSD: include-main.mk,v 1.5 2020/09/05 18:18:05 rillig Exp $ # -# Demonstrates that the .INCLUDEDFROMFILE magic variable does not behave +# Until 2020-09-05, the .INCLUDEDFROMFILE magic variable did not behave # as described in the manual page. # # The manual page says that it is the "filename of the file this Makefile -# was included from", while in reality it is the "filename in which the -# latest .include happened". See parse.c, function ParseSetIncludeFile. +# was included from", while before 2020-09-05 it was the "filename in which +# the latest .include happened". See parse.c, function ParseSetIncludeFile. # +# Since 2020-09-05, the .INCLUDEDFROMDIR and .INCLUDEDFROMFILE variables +# properly handle nested includes and even .for loops. .if !defined(.INCLUDEDFROMFILE) -LOG+= main-before-ok +. info main-before-ok .else -. for f in ${.INCLUDEDFROMFILE} -LOG+= main-before-fail\(${f:Q}\) -. endfor +. warning main-before-fail(${.INCLUDEDFROMFILE}) .endif +.for i in once +. if !defined(${.INCLUDEDFROMFILE}) +. info main-before-for-ok +. else +. warning main-before-for-fail(${.INCLUDEDFROMFILE}) +. endif +.endfor + .include "include-sub.mk" .if !defined(.INCLUDEDFROMFILE) -LOG+= main-after-ok +. info main-after-ok .else -. for f in ${.INCLUDEDFROMFILE} -LOG+= main-after-fail\(${f:Q}\) -. endfor +. warning main-after-fail(${.INCLUDEDFROMFILE}) .endif -all: - @printf '%s\n' ${LOG} +.for i in once +. if !defined(${.INCLUDEDFROMFILE}) +. info main-after-for-ok +. else +. warning main-after-for-fail(${.INCLUDEDFROMFILE}) +. endif +.endfor + +all: # nothing diff --git a/unit-tests/include-sub.mk b/unit-tests/include-sub.mk index 28856fcc6e60..aeb7c3a69082 100644 --- a/unit-tests/include-sub.mk +++ b/unit-tests/include-sub.mk @@ -1,17 +1,49 @@ -# $NetBSD: include-sub.mk,v 1.1 2020/05/17 12:36:26 rillig Exp $ +# $NetBSD: include-sub.mk,v 1.6 2020/10/25 12:08:53 rillig Exp $ .if ${.INCLUDEDFROMFILE} == "include-main.mk" -LOG+= sub-before-ok +. info sub-before-ok .else -LOG+= sub-before-fail +. warning sub-before-fail(${.INCLUDEDFROMFILE}) .endif +# As of 2020-09-05, the .for loop is implemented as "including a file" +# with a custom buffer. Therefore this loop has side effects on these +# variables. +.for i in once +. if ${.INCLUDEDFROMFILE} == "include-main.mk" +. info sub-before-for-ok +. else +. warning sub-before-for-fail(${.INCLUDEDFROMFILE}) +. endif +.endfor + +# To see the variable 'includes' in action: +# +# Breakpoints: +# Parse_File at "PtrVector_Push(&includes, curFile)" +# ParseMessage at entry +# Watches: +# ((const IFile *[10])(*includes.items)) +# *curFile + +.for i in deeply +. for i in nested +. for i in include .include "include-subsub.mk" +. endfor +. endfor +.endfor .if ${.INCLUDEDFROMFILE} == "include-main.mk" -LOG+= sub-after-ok +. info sub-after-ok .else -. for f in ${.INCLUDEDFROMFILE} -LOG+= sub-after-fail\(${f:Q}\) -. endfor +. warning sub-after-fail(${.INCLUDEDFROMFILE}) .endif + +.for i in once +. if ${.INCLUDEDFROMFILE} == "include-main.mk" +. info sub-after-for-ok +. else +. warning sub-after-for-fail(${.INCLUDEDFROMFILE}) +. endif +.endfor diff --git a/unit-tests/include-subsub.mk b/unit-tests/include-subsub.mk index e8fa1186f41d..915d36d8a2a2 100644 --- a/unit-tests/include-subsub.mk +++ b/unit-tests/include-subsub.mk @@ -1,7 +1,7 @@ -# $NetBSD: include-subsub.mk,v 1.1 2020/05/17 12:36:26 rillig Exp $ +# $NetBSD: include-subsub.mk,v 1.3 2020/09/05 18:13:47 rillig Exp $ -.if ${.INCLUDEDFROMFILE:T} == "include-sub.mk" -LOG+= subsub-ok +.if ${.INCLUDEDFROMFILE} == "include-sub.mk" +. info subsub-ok .else -LOG+= subsub-fail +. warning subsub-fail(${.INCLUDEDFROMFILE}) .endif diff --git a/unit-tests/job-output-long-lines.exp b/unit-tests/job-output-long-lines.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/job-output-long-lines.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/job-output-long-lines.mk b/unit-tests/job-output-long-lines.mk new file mode 100644 index 000000000000..5a83bde0da3a --- /dev/null +++ b/unit-tests/job-output-long-lines.mk @@ -0,0 +1,32 @@ +# $NetBSD: job-output-long-lines.mk,v 1.4 2020/11/01 17:29:13 rillig Exp $ +# +# The jobs may produce long lines of output. A practical case are the echoed +# command lines from compiler invocations, with their many -D options. +# +# Each of these lines must be written atomically to the actual output. +# The markers for switching jobs must always be written at the beginning of +# the line, to make them clearly visible in large log files. +# +# As of 2020-09-27, the default job buffer size is 1024. When a job produces +# output lines that are longer than this buffer size, these output pieces are +# not terminated by a newline. Because of this missing newline, the job +# markers "--- job-a ---" and "--- job-b ---" are not always written at the +# beginning of a line, even though this is expected by anyone reading the log +# files. + +.MAKEFLAGS: -j2 + +100:= ${:U1:S,1,2222222222,g:S,2,3333333333,g} +5000:= ${100:S,3,4444444444,g:S,4,xxxxx,g} + +all: job-a job-b + +job-a: +.for i in ${:U:range=20} + @echo ${5000:S,x,a,g} +.endfor + +job-b: +.for i in ${:U:range=20} + @echo ${5000:S,x,b,g} +.endfor diff --git a/unit-tests/lint.exp b/unit-tests/lint.exp index 9cd0eeb1ddf4..d7068b5e006a 100755 --- a/unit-tests/lint.exp +++ b/unit-tests/lint.exp @@ -1,4 +1,4 @@ make: In the :@ modifier of "VAR", the variable name "${:Ubar:S,b,v,}" must not contain a dollar. y@:Q} xvaluey -exit status 0 +exit status 2 diff --git a/unit-tests/lint.mk b/unit-tests/lint.mk index 56cd6be27860..cfcb6fe7ba93 100755 --- a/unit-tests/lint.mk +++ b/unit-tests/lint.mk @@ -1,4 +1,4 @@ -# $NetBSD: lint.mk,v 1.2 2020/08/08 13:00:07 rillig Exp $ +# $NetBSD: lint.mk,v 1.3 2020/09/15 16:22:04 rillig Exp $ # # Demonstrates stricter checks that are only enabled in the lint mode, # using the -dL option. @@ -10,6 +10,8 @@ # for a moment, but is continued after the wrongly-guessed end of the # variable, which echoes "y@:Q}". +.MAKEFLAGS: -dL + all: mod-loop-varname mod-loop-varname: diff --git a/unit-tests/make-exported.exp b/unit-tests/make-exported.exp index 7408f16c4e6c..05a916760b26 100755 --- a/unit-tests/make-exported.exp +++ b/unit-tests/make-exported.exp @@ -1,3 +1,2 @@ --literal=make-exported-value UT_VAR= exit status 0 diff --git a/unit-tests/make-exported.mk b/unit-tests/make-exported.mk index 36f4b356ae53..db7f09dc490f 100755 --- a/unit-tests/make-exported.mk +++ b/unit-tests/make-exported.mk @@ -1,16 +1,25 @@ -# $NetBSD: make-exported.mk,v 1.1 2020/08/09 12:59:16 rillig Exp $ +# $NetBSD: make-exported.mk,v 1.6 2020/10/05 19:27:48 rillig Exp $ # # As of 2020-08-09, the code in Var_Export is shared between the .export # directive and the .MAKE.EXPORTED variable. This leads to non-obvious # behavior for certain variable assignments. --env= make-exported-value --literal= make-exported-value +-env= make-exported-value-env +-literal= make-exported-value-literal UT_VAR= ${UNEXPANDED} -# The following behavior is probably not intended. -.MAKE.EXPORTED= -env # like .export-env -.MAKE.EXPORTED= -literal UT_VAR # like .export-literal PATH +# Before 2020-10-03, the following line took the code path of .export-env, +# which was surprising behavior. Since 2020-10-03 this line tries to +# export the variable named "-env", but that is rejected because the +# variable name starts with a hyphen. +.MAKE.EXPORTED= -env + +# Before 2020-10-03, if the value of .MAKE.EXPORTED started with "-literal", +# make behaved like a mixture of .export-literal and a regular .export. +# +# Since 2020-10-03, the "variable" named "-literal" is not exported anymore, +# it is just ignored since its name starts with '-'. +.MAKE.EXPORTED= -literal UT_VAR all: @env | sort | grep -E '^UT_|make-exported-value' || true diff --git a/unit-tests/misc.mk b/unit-tests/misc.mk deleted file mode 100644 index 030115079a94..000000000000 --- a/unit-tests/misc.mk +++ /dev/null @@ -1,16 +0,0 @@ -# $Id: misc.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ - -.if !exists(${.CURDIR}/) -.warning ${.CURDIR}/ doesn't exist ? -.endif - -.if !exists(${.CURDIR}/.) -.warning ${.CURDIR}/. doesn't exist ? -.endif - -.if !exists(${.CURDIR}/..) -.warning ${.CURDIR}/.. doesn't exist ? -.endif - -all: - @: all is well diff --git a/unit-tests/moderrs.exp b/unit-tests/moderrs.exp index f017cae4633a..0ca1aa2aedd5 100644 --- a/unit-tests/moderrs.exp +++ b/unit-tests/moderrs.exp @@ -1,54 +1,62 @@ -Expect: Unknown modifier 'Z' +mod-unknown-direct: +want: Unknown modifier 'Z' make: Unknown modifier 'Z' -VAR:Z= -Expect: Unknown modifier 'Z' +VAR:Z=before--after + +mod-unknown-indirect: +want: Unknown modifier 'Z' make: Unknown modifier 'Z' -VAR:Z= -Expect: Unclosed variable specification for VAR +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 VAR:S,V,v,=Thevariable -Expect: Unclosed variable specification for VAR + +unclosed-indirect: +want: Unclosed variable specification after complex modifier (expecting '}') for VAR make: Unclosed variable specification after complex modifier (expecting '}') for VAR VAR:S,V,v,=Thevariable -Expect: Unfinished modifier for VAR (',' missing) + +unfinished-indirect: +want: Unfinished modifier for VAR (',' missing) make: Unfinished modifier for VAR (',' missing) VAR:S,V,v= -Expect: 2 errors about missing @ delimiter + +unfinished-loop: +want: Unfinished modifier for UNDEF ('@' missing) make: Unfinished modifier for UNDEF ('@' missing) +want: Unfinished modifier for UNDEF ('@' missing) make: Unfinished modifier for UNDEF ('@' missing) 1 2 3 -modloop-close: + +loop-close: make: Unclosed variable specification (expecting '}') for "UNDEF" (value "1}... 2}... 3}...") modifier @ 1}... 2}... 3}... 1}... 2}... 3}... -Expect: 2 errors about missing ] delimiter + +words: +want: Unfinished modifier for UNDEF (']' missing) make: Unfinished modifier for UNDEF (']' missing) +want: Unfinished modifier for UNDEF (']' missing) make: Unfinished modifier for UNDEF (']' missing) 13= -12345=ok -Expect: 2 errors about missing ! delimiter +make: Bad modifier `:[123451234512345123451234512345]' for UNDEF +12345=S,^ok,:S,^3ok,} + +exclam: +want: Unfinished modifier for VARNAME ('!' missing) make: Unfinished modifier for VARNAME ('!' missing) +want: Unfinished modifier for ! ('!' missing) make: Unfinished modifier for ! ('!' missing) -mod-subst-delimiter: -make: Missing delimiter for :S modifier - -make: Unfinished modifier for VAR (',' missing) - -make: Unfinished modifier for VAR (',' missing) -make: Unfinished modifier for VAR (',' missing) - -make: Unfinished modifier for VAR (',' missing) - -make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier S -TheVariable -TheVariable +mod-subst-delimiter: make: Missing delimiter for :S modifier 1: make: Unfinished modifier for VAR (',' missing) @@ -56,26 +64,14 @@ make: Unfinished modifier for VAR (',' missing) make: Unfinished modifier for VAR (',' missing) 3: make: Unfinished modifier for VAR (',' missing) - +4: make: Unfinished modifier for VAR (',' missing) - +5: make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier S -TheVariable -TheVariable -mod-regex-delimiter: -make: Missing delimiter for :C modifier - -make: Unfinished modifier for VAR (',' missing) - -make: Unfinished modifier for VAR (',' missing) +6: TheVariable +7: TheVariable -make: Unfinished modifier for VAR (',' missing) - -make: Unfinished modifier for VAR (',' missing) - -make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier C -TheVariable -TheVariable +mod-regex-delimiter: make: Missing delimiter for :C modifier 1: make: Unfinished modifier for VAR (',' missing) @@ -83,12 +79,13 @@ make: Unfinished modifier for VAR (',' missing) make: Unfinished modifier for VAR (',' missing) 3: make: Unfinished modifier for VAR (',' missing) - +4: make: Unfinished modifier for VAR (',' missing) - +5: make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier C -TheVariable -TheVariable +6: TheVariable +7: TheVariable + mod-regex-undefined-subexpression: one one 2 3 5 8 one3 2one 34 make: No match for subexpression \2 @@ -97,6 +94,7 @@ 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 @@ -104,6 +102,7 @@ make: Bad modifier `:ts\65oct' for FIB 65oct} make: Bad modifier `:tsxy' for FIB xy} + mod-t-parse: make: Bad modifier `:t' for FIB @@ -113,6 +112,7 @@ make: Bad modifier `:t' for FIB make: Bad modifier `:t' for FIB M*} + mod-ifelse-parse: make: Unfinished modifier for FIB (':' missing) @@ -123,10 +123,12 @@ 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 '_' + mod-sysv-parse: make: Unknown modifier '3' make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3 @@ -138,4 +140,5 @@ make: Unknown modifier '3' make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3 1 1 2 x3 5 8 1x3 21 34 + exit status 0 diff --git a/unit-tests/moderrs.mk b/unit-tests/moderrs.mk index 931158c8ecb9..77ba39a3d57d 100644 --- a/unit-tests/moderrs.mk +++ b/unit-tests/moderrs.mk @@ -1,19 +1,22 @@ -# $Id: moderrs.mk,v 1.1.1.8 2020/08/26 16:40:43 sjg Exp $ +# $NetBSD: moderrs.mk,v 1.24 2020/11/01 14:36:25 rillig Exp $ # # various modifier error tests -VAR=TheVariable -# incase we have to change it ;-) -MOD_UNKN=Z -MOD_TERM=S,V,v -MOD_S:= ${MOD_TERM}, +'= '\'' +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: modunkn modunknV varterm vartermV modtermV modloop -all: modloop-close -all: modwords -all: modexclam +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 @@ -23,29 +26,30 @@ all: mod-ifelse-parse all: mod-remember-parse all: mod-sysv-parse -modunkn: - @echo "Expect: Unknown modifier 'Z'" - @echo "VAR:Z=${VAR:Z}" +mod-unknown-direct: print-header print-footer + @echo 'want: Unknown modifier $'Z$'' + @echo 'VAR:Z=before-${VAR:Z}-after' -modunknV: - @echo "Expect: Unknown modifier 'Z'" - @echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}" +mod-unknown-indirect: print-header print-footer + @echo 'want: Unknown modifier $'Z$'' + @echo 'VAR:${MOD_UNKN}=before-${VAR:${MOD_UNKN}:inner}-after' -varterm: - @echo "Expect: Unclosed variable specification for VAR" +unclosed-direct: print-header print-footer + @echo 'want: Unclosed variable specification (expecting $'}$') for "VAR" (value "Thevariable") modifier S' @echo VAR:S,V,v,=${VAR:S,V,v, -vartermV: - @echo "Expect: Unclosed variable specification for VAR" +unclosed-indirect: print-header print-footer + @echo 'want: Unclosed variable specification after complex modifier (expecting $'}$') for VAR' @echo VAR:${MOD_TERM},=${VAR:${MOD_S} -modtermV: - @echo "Expect: Unfinished modifier for VAR (',' missing)" +unfinished-indirect: print-header print-footer + @echo 'want: Unfinished modifier for VAR ($',$' missing)' -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}" -modloop: - @echo "Expect: 2 errors about missing @ delimiter" +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}@} @@ -55,14 +59,14 @@ modloop: # 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. -modloop-close: - @echo $@: +loop-close: print-header print-footer @echo ${UNDEF:U1 2 3:@var@${var}}...@ @echo ${UNDEF:U1 2 3:@var@${var}}...@} -modwords: - @echo "Expect: 2 errors about missing ] delimiter" +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 @@ -70,59 +74,50 @@ modwords: # Word index out of bounds. # - # On LP64I32, strtol returns LONG_MAX, - # which is then truncated to int (undefined behavior), - # typically resulting in -1. - # This -1 is interpreted as "the last word". + # 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 results in a range from LONG_MAX - 1 to 3, - # which is empty. + # 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,} -modexclam: - @echo "Expect: 2 errors about missing ! delimiter" +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: - @echo $@: - @echo ${VAR:S - @echo ${VAR:S, - @echo ${VAR:S,from - @echo ${VAR:S,from, - @echo ${VAR:S,from,to - @echo ${VAR:S,from,to, - @echo ${VAR:S,from,to,} +mod-subst-delimiter: print-header print-footer @echo 1: ${VAR:S @echo 2: ${VAR:S, @echo 3: ${VAR:S,from - @echo ${VAR:S,from, - @echo ${VAR:S,from,to - @echo ${VAR:S,from,to, - @echo ${VAR:S,from,to,} + @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: - @echo $@: - @echo ${VAR:C - @echo ${VAR:C, - @echo ${VAR:C,from - @echo ${VAR:C,from, - @echo ${VAR:C,from,to - @echo ${VAR:C,from,to, - @echo ${VAR:C,from,to,} +mod-regex-delimiter: print-header print-footer @echo 1: ${VAR:C @echo 2: ${VAR:C, @echo 3: ${VAR:C,from - @echo ${VAR:C,from, - @echo ${VAR:C,from,to - @echo ${VAR:C,from,to, - @echo ${VAR:C,from,to,} + @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. @@ -135,42 +130,42 @@ mod-regex-delimiter: # 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 :C modifier would have to be chosen wisely. -mod-regex-undefined-subexpression: - @echo $@: +# 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: - @echo $@: +mod-ts-parse: print-header print-footer @echo ${FIB:ts} @echo ${FIB:ts\65} # octal 065 == U+0035 == '5' @echo ${FIB:ts\65oct} # bad modifier @echo ${FIB:tsxy} # modifier too long -mod-t-parse: - @echo $@: +mod-t-parse: print-header print-footer @echo ${FIB:t @echo ${FIB:txy} @echo ${FIB:t} @echo ${FIB:t:M*} -mod-ifelse-parse: - @echo $@: +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: - @echo $@: +mod-remember-parse: print-header print-footer @echo ${FIB:_} # ok @echo ${FIB:__} # modifier name too long -mod-sysv-parse: - @echo $@: +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/unit-tests/modmatch.mk b/unit-tests/modmatch.mk index f15b3d699a8e..7dcacf09da6d 100644 --- a/unit-tests/modmatch.mk +++ b/unit-tests/modmatch.mk @@ -1,21 +1,21 @@ -# $NetBSD: modmatch.mk,v 1.8 2020/08/16 20:03:53 rillig Exp $ +# $NetBSD: modmatch.mk,v 1.9 2020/10/24 08:50:17 rillig Exp $ # # Tests for the :M and :S modifiers. -X=a b c d e +X= a b c d e .for x in $X -LIB${x:tu}=/tmp/lib$x.a +LIB${x:tu}= /tmp/lib$x.a .endfor -X_LIBS= ${LIBA} ${LIBD} ${LIBE} +X_LIBS= ${LIBA} ${LIBD} ${LIBE} -LIB?=a +LIB?= a -var = head -res = no +var= head +res= no .if !empty(var:M${:Uhead\:tail:C/:.*//}) -res = OK +res= OK .endif all: show-libs diff --git a/unit-tests/modmisc.mk b/unit-tests/modmisc.mk index f2977da8a0ba..f57e679cd4da 100644 --- a/unit-tests/modmisc.mk +++ b/unit-tests/modmisc.mk @@ -1,19 +1,19 @@ -# $Id: modmisc.mk,v 1.1.1.15 2020/08/23 15:52:08 sjg Exp $ +# $NetBSD: modmisc.mk,v 1.49 2020/10/24 08:50:17 rillig Exp $ # # miscellaneous modifier tests # do not put any dirs in this list which exist on some # but not all target systems - an exists() check is below. -path=:/bin:/tmp::/:.:/no/such/dir:. +path= :/bin:/tmp::/:.:/no/such/dir:. # strip cwd from path. -MOD_NODOT=S/:/ /g:N.:ts: -# and decorate, note that $'s need to be doubled. Also note that +MOD_NODOT= S/:/ /g:N.:ts: +# and decorate, note that $'s need to be doubled. Also note that # the modifier_variable can be used with other modifiers. -MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@ +MOD_NODOTX= S/:/ /g:N.:@d@'$$d'@ # another mod - pretend it is more interesting -MOD_HOMES=S,/home/,/homes/, -MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@ -MOD_SEP=S,:, ,g +MOD_HOMES= S,/home/,/homes/, +MOD_OPT= @d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@ +MOD_SEP= S,:, ,g all: modvar modvarloop modsysv emptyvar undefvar all: mod-quote @@ -32,8 +32,8 @@ modvar: @echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}" .for d in ${path:${MOD_SEP}:N.} /usr/xbin -path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/ -paths+= ${d:${MOD_OPT}:${MOD_HOMES}} +path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/ +paths+= ${d:${MOD_OPT}:${MOD_HOMES}} .endfor modvarloop: @@ -67,18 +67,18 @@ mod-break-many-words: # To apply a modifier indirectly via another variable, the whole # modifier must be put into a single variable. .if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}" -.warning unexpected +. 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 +. 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 +. warning unexpected .endif # An indirect variable that evaluates to the empty string is allowed though. @@ -87,6 +87,6 @@ mod-break-many-words: # M.little-endian= S,1234,4321, # M.big-endian= # none .if ${value:L:${:Dempty}S,a,A,} != "vAlue" -.warning unexpected +. warning unexpected .endif diff --git a/unit-tests/modts.mk b/unit-tests/modts.mk index 3219538d5e4a..67ba7eb97078 100644 --- a/unit-tests/modts.mk +++ b/unit-tests/modts.mk @@ -1,23 +1,24 @@ +# $NetBSD: modts.mk,v 1.7 2020/10/24 08:50:17 rillig Exp $ -LIST= one two three -LIST+= four five six +LIST= one two three +LIST+= four five six -FU_mod-ts = a / b / cool +FU_mod-ts= a / b / cool -AAA= a a a -B.aaa= Baaa +AAA= a a a +B.aaa= Baaa all: mod-ts mod-ts-space # Use print or printf iff they are builtin. -# XXX note that this causes problems, when make decides +# XXX note that this causes problems, when make decides # there is no need to use a shell, so avoid where possible. .if ${(type print) 2> /dev/null || echo:L:sh:Mbuiltin} != "" -PRINT= print -r -- +PRINT= print -r -- .elif ${(type printf) 2> /dev/null || echo:L:sh:Mbuiltin} != "" -PRINT= printf '%s\n' +PRINT= printf '%s\n' .else -PRINT= echo +PRINT= echo .endif mod-ts: diff --git a/unit-tests/modword.exp b/unit-tests/modword.exp index 258d7eadd6ce..9fd7f1b494fe 100644 --- a/unit-tests/modword.exp +++ b/unit-tests/modword.exp @@ -66,6 +66,7 @@ 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 LIST:[1.]="" is an error make: Bad modifier `:[1..]' for LIST @@ -99,6 +100,7 @@ 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" diff --git a/unit-tests/modword.mk b/unit-tests/modword.mk index 1327624f7e19..43edf6a32b77 100644 --- a/unit-tests/modword.mk +++ b/unit-tests/modword.mk @@ -1,28 +1,29 @@ -# $Id: modword.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# $NetBSD: modword.mk,v 1.4 2020/11/01 13:55:31 rillig Exp $ # # Test behaviour of new :[] modifier all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw -LIST= one two three -LIST+= four five six -LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 +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 +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-nested \ + mod-squarebrackets-space mod-squarebrackets-0-star-at: @echo 'LIST:[]="${LIST:[]}" is an error' @@ -92,6 +93,7 @@ mod-squarebrackets-n: @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' @@ -124,6 +126,12 @@ mod-squarebrackets-nested: @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}"' diff --git a/unit-tests/opt-debug-all.exp b/unit-tests/opt-debug-all.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-all.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-all.mk b/unit-tests/opt-debug-all.mk new file mode 100644 index 000000000000..400189eb6d01 --- /dev/null +++ b/unit-tests/opt-debug-all.mk @@ -0,0 +1,10 @@ +# $NetBSD: opt-debug-all.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dA command line option, which enables all debug options +# except for -dL (lint), since that option is not related to debug logging +# but to static analysis. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-archive.exp b/unit-tests/opt-debug-archive.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-archive.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-archive.mk b/unit-tests/opt-debug-archive.mk new file mode 100644 index 000000000000..d70641f9739c --- /dev/null +++ b/unit-tests/opt-debug-archive.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-archive.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -da command line option, which adds debug logging for the +# archive handling. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-cond.exp b/unit-tests/opt-debug-cond.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-cond.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-cond.mk b/unit-tests/opt-debug-cond.mk new file mode 100644 index 000000000000..2b9d1029c7d9 --- /dev/null +++ b/unit-tests/opt-debug-cond.mk @@ -0,0 +1,10 @@ +# $NetBSD: opt-debug-cond.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dc command line option, which adds debug logging for the +# evaluation of conditional expressions, such as in .if directives and +# ${cond:?then:else} expressions. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-curdir.exp b/unit-tests/opt-debug-curdir.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-curdir.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-curdir.mk b/unit-tests/opt-debug-curdir.mk new file mode 100644 index 000000000000..3c37d2988675 --- /dev/null +++ b/unit-tests/opt-debug-curdir.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-debug-curdir.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dC command line option, which does nothing, as of 2020-09-05. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-dir.exp b/unit-tests/opt-debug-dir.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-dir.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-dir.mk b/unit-tests/opt-debug-dir.mk new file mode 100644 index 000000000000..5036a361cb23 --- /dev/null +++ b/unit-tests/opt-debug-dir.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-dir.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dd command line option, which adds debug logging for +# directory searching and caching. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-errors.exp b/unit-tests/opt-debug-errors.exp new file mode 100644 index 000000000000..dd13e66526b0 --- /dev/null +++ b/unit-tests/opt-debug-errors.exp @@ -0,0 +1,34 @@ +echo '3 spaces'; false +3 spaces + +*** Failed target: fail-spaces +*** Failed command: echo '3 spaces'; false +*** Error code 1 (continuing) +echo \ indented; false + indented + +*** Failed target: fail-escaped-space +*** Failed command: echo \ indented; false +*** Error code 1 (continuing) +echo 'line1 +line2'; false +line1 +line2 + +*** Failed target: fail-newline +*** Failed command: echo 'line1 line2'; false +*** Error code 1 (continuing) +echo 'line1 line2'; false +line1 line2 + +*** Failed target: fail-multiline +*** Failed command: echo 'line1 line2'; false +*** Error code 1 (continuing) +echo 'word1' 'word2'; false +word1 word2 + +*** Failed target: fail-multiline-intention +*** Failed command: echo 'word1' 'word2'; false +*** Error code 1 (continuing) +`all' not remade because of errors. +exit status 0 diff --git a/unit-tests/opt-debug-errors.mk b/unit-tests/opt-debug-errors.mk new file mode 100644 index 000000000000..1658c6b3ce3e --- /dev/null +++ b/unit-tests/opt-debug-errors.mk @@ -0,0 +1,42 @@ +# $NetBSD: opt-debug-errors.mk,v 1.2 2020/09/06 04:35:03 rillig Exp $ +# +# Tests for the -de command line option, which adds debug logging for +# failed commands and targets. + +.MAKEFLAGS: -de + +all: fail-spaces +all: fail-escaped-space +all: fail-newline +all: fail-multiline +all: fail-multiline-intention + +# XXX: The debug output folds the spaces, showing '3 spaces' instead of +# the correct '3 spaces'. +fail-spaces: + echo '3 spaces'; false + +# XXX: The debug output folds the spaces, showing 'echo \ indented' instead +# of the correct 'echo \ indented'. +fail-escaped-space: + echo \ indented; false + +# XXX: A newline is turned into an ordinary space in the debug log. +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. Having these spaces collapsed makes sense to show the command +# in its condensed form. +# +fail-multiline-intention: + echo 'word1' \ + 'word2'; false diff --git a/unit-tests/opt-debug-file.exp b/unit-tests/opt-debug-file.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-file.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-file.mk b/unit-tests/opt-debug-file.mk new file mode 100644 index 000000000000..1ed477ef3c40 --- /dev/null +++ b/unit-tests/opt-debug-file.mk @@ -0,0 +1,37 @@ +# $NetBSD: opt-debug-file.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# +# Tests for the -dF command line option, which redirects the debug log +# to a file instead of writing it to stderr. + +# Enable debug logging for variable assignments and evaluation (-dv) +# and redirect the debug logging to the given file. +.MAKEFLAGS: -dvFopt-debug-file.debuglog + +# This output goes to the debug log file. +VAR= value ${:Uexpanded} + +# Hide the logging output for the remaining actions. +# As of 2020-10-03, it is not possible to disable debug logging again. +.MAKEFLAGS: -dF/dev/null + +# Make sure that the debug logging file contains some logging. +DEBUG_OUTPUT:= ${:!cat opt-debug-file.debuglog!} +# Grmbl. Because of the := operator in the above line, the variable +# value contains ${:Uexpanded}. This variable expression is expanded +# upon further processing. Therefore, don't read from untrusted input. +#.MAKEFLAGS: -dc -dFstderr +.if !${DEBUG_OUTPUT:tW:M*VAR = value expanded*} +. error ${DEBUG_OUTPUT} +.endif + +# To get the unexpanded text that was actually written to the debug log +# file, the content of that log file must not be stored in a variable. +# XXX: In the :M modifier, a dollar is escaped as '$$', not '\$'. +.if !${:!cat opt-debug-file.debuglog!:tW:M*VAR = value $${:Uexpanded}*} +. error +.endif + +_!= rm opt-debug-file.debuglog + +all: + @:; diff --git a/unit-tests/opt-debug-for.exp b/unit-tests/opt-debug-for.exp new file mode 100644 index 000000000000..ea811b9bfcf5 --- /dev/null +++ b/unit-tests/opt-debug-for.exp @@ -0,0 +1,22 @@ +For: new loop 2 +For: end for 2 +For: end for 1 +For: loop body: +. for inner in 1 2 +VAR.${:Ua}${inner}= value +. endfor +For: end for 1 +For: loop body: +VAR.${:Ua}${:U1}= value +For: loop body: +VAR.${:Ua}${:U2}= value +For: loop body: +. for inner in 1 2 +VAR.${:Ub}${inner}= value +. endfor +For: end for 1 +For: loop body: +VAR.${:Ub}${:U1}= value +For: loop body: +VAR.${:Ub}${:U2}= value +exit status 0 diff --git a/unit-tests/opt-debug-for.mk b/unit-tests/opt-debug-for.mk new file mode 100644 index 000000000000..1de770de4a2f --- /dev/null +++ b/unit-tests/opt-debug-for.mk @@ -0,0 +1,26 @@ +# $NetBSD: opt-debug-for.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# +# Tests for the -df command line option, which adds debug logging for +# parsing and evaluating .for loops. + +.MAKEFLAGS: -df + +# XXX: In the debug log, the "new loop 2" appears out of context. +# There should be a "begin loop 1" before, and all these messages should +# contain line number information. +# +# XXX: The "loop body" should print the nesting level as well. +# +# XXX: It is hard to extract any information from the debug log since +# the "begin" and "end" events are not balanced and the nesting level +# is not printed consistently. It would also be helpful to mention the +# actual substitutions, such as "For 1: outer=b". +# +.for outer in a b +. for inner in 1 2 +VAR.${outer}${inner}= value +. endfor +.endfor + +all: + @:; diff --git a/unit-tests/opt-debug-g1.exp b/unit-tests/opt-debug-graph1.exp index d6d014a0353f..db8367c6f250 100755..100644 --- a/unit-tests/opt-debug-g1.exp +++ b/unit-tests/opt-debug-graph1.exp @@ -1,15 +1,17 @@ #*** Input graph: -# all, made UNMADE, type OP_DEPENDS, flags none +# all, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # made-target, made UNMADE, type OP_DEPENDS, flags none # made-target-no-sources, made UNMADE, type OP_DEPENDS, flags none # made-source, made UNMADE, type OP_DEPENDS, flags none # unmade-target, made UNMADE, type OP_DEPENDS, flags none # unmade-sources, made UNMADE, type none, flags none +# unmade-silent-source, made UNMADE, type OP_SILENT, flags none # unmade-target-no-sources, made UNMADE, type OP_DEPENDS, flags none # # Files that are only sources: -# unmade-sources [unmade-sources] +# unmade-sources [unmade-sources] +# unmade-silent-source [unmade-silent-source] .SILENT #*** Transformations: exit status 0 diff --git a/unit-tests/opt-debug-g1.mk b/unit-tests/opt-debug-graph1.mk index 3104fbf91bc1..618d49102a20 100755..100644 --- a/unit-tests/opt-debug-g1.mk +++ b/unit-tests/opt-debug-graph1.mk @@ -1,8 +1,10 @@ -# $NetBSD: opt-debug-g1.mk,v 1.1 2020/08/27 19:00:17 rillig Exp $ +# $NetBSD: opt-debug-graph1.mk,v 1.3 2020/09/05 06:46:12 rillig Exp $ # # Tests for the -dg1 command line option, which prints the input # graph before making anything. +.MAKEFLAGS: -dg1 + all: made-target made-target-no-sources made-target: made-source @@ -11,7 +13,9 @@ made-source: made-target-no-sources: -unmade-target: unmade-sources +unmade-target: unmade-sources unmade-silent-source + +.SILENT: unmade-silent-source unmade-target-no-sources: diff --git a/unit-tests/opt-debug-graph2.exp b/unit-tests/opt-debug-graph2.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-graph2.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-graph2.mk b/unit-tests/opt-debug-graph2.mk new file mode 100644 index 000000000000..92ffac6f7539 --- /dev/null +++ b/unit-tests/opt-debug-graph2.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-graph2.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dg2 command line option, which prints the input +# graph after making everything, or before exiting on error. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-graph3.exp b/unit-tests/opt-debug-graph3.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-graph3.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-graph3.mk b/unit-tests/opt-debug-graph3.mk new file mode 100644 index 000000000000..28166313d833 --- /dev/null +++ b/unit-tests/opt-debug-graph3.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-graph3.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dg3 command line option, which prints the input +# graph before exiting on error. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-hash.exp b/unit-tests/opt-debug-hash.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-hash.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-hash.mk b/unit-tests/opt-debug-hash.mk new file mode 100644 index 000000000000..c8cb99acd261 --- /dev/null +++ b/unit-tests/opt-debug-hash.mk @@ -0,0 +1,10 @@ +# $NetBSD: opt-debug-hash.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dh command line option, which adds debug logging for +# hash tables. Even more detailed logging is available by compiling +# make with -DDEBUG_HASH_LOOKUP. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-jobs.exp b/unit-tests/opt-debug-jobs.exp new file mode 100644 index 000000000000..1214dce781b5 --- /dev/null +++ b/unit-tests/opt-debug-jobs.exp @@ -0,0 +1,25 @@ +job_pipe -1 -1, maxjobs 1, tokens 1, compat 0 +Job_TokenWithdraw(<pid>): aborting 0, running 0 +(<pid>) withdrew token +echo ": expanded expression" +{ : expanded expression +} || exit $? +echo ": variable" +{ : variable +} || exit $? +echo ": 'single' and \"double\" quotes" +{ : 'single' and "double" quotes +} || exit $? +Running all locally + Command: sh +JobExec(all): pid <pid> added to jobs table +job table @ job started +job 0, status 3, flags 0, pid <pid> +: expanded expression +: variable +: 'single' and "double" quotes +Process <pid> exited/stopped status 0. +JobFinish: <pid> [all], status 0 +Job_TokenWithdraw(<pid>): aborting 0, running 0 +(<pid>) withdrew token +exit status 0 diff --git a/unit-tests/opt-debug-jobs.mk b/unit-tests/opt-debug-jobs.mk new file mode 100644 index 000000000000..fb65d4f3356c --- /dev/null +++ b/unit-tests/opt-debug-jobs.mk @@ -0,0 +1,26 @@ +# $NetBSD: opt-debug-jobs.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# +# Tests for the -dj command line option, which adds debug logging about +# running jobs in multiple shells. + +.MAKEFLAGS: -dj + +# Run in parallel mode since the debug logging is more interesting there +# than in compat mode. +.MAKEFLAGS: -j1 + +all: + # Only the actual command is logged. + # To see the evaluation of the variable expressions, use -dv. + : ${:Uexpanded} expression + + # Undefined variables expand to empty strings. + # Multiple spaces are preserved in the command, as they might be + # significant. + : ${UNDEF} variable + + # In the debug output, single quotes are not escaped, even though + # the whole command is enclosed in single quotes as well. + # This allows to copy and paste the whole command, without having + # to unescape anything. + : 'single' and "double" quotes diff --git a/unit-tests/opt-debug-lint.exp b/unit-tests/opt-debug-lint.exp new file mode 100644 index 000000000000..b0be460848fd --- /dev/null +++ b/unit-tests/opt-debug-lint.exp @@ -0,0 +1,9 @@ +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 67: Missing delimiter ':' after indirect modifier "${:UL}" +make: Unknown modifier '$' +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/opt-debug-lint.mk b/unit-tests/opt-debug-lint.mk new file mode 100644 index 000000000000..9075243208b1 --- /dev/null +++ b/unit-tests/opt-debug-lint.mk @@ -0,0 +1,72 @@ +# $NetBSD: opt-debug-lint.mk,v 1.11 2020/10/24 08:50:17 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 + +# Since 2020-10-03, in lint mode the variable modifier must be separated +# by colons. See varparse-mod.mk. +.if ${value:${:UL}PL} != "LPL}" # FIXME: "LPL}" is unexpected here. +. error ${value:${:UL}PL} +.endif + +all: + @:; diff --git a/unit-tests/opt-debug-loud.exp b/unit-tests/opt-debug-loud.exp new file mode 100644 index 000000000000..9e572fba2d01 --- /dev/null +++ b/unit-tests/opt-debug-loud.exp @@ -0,0 +1,3 @@ +echo all-word +all-word +exit status 0 diff --git a/unit-tests/opt-debug-loud.mk b/unit-tests/opt-debug-loud.mk new file mode 100644 index 000000000000..38a3c7d7a8e1 --- /dev/null +++ b/unit-tests/opt-debug-loud.mk @@ -0,0 +1,22 @@ +# $NetBSD: opt-debug-loud.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# +# Tests for the -dl command line option, which prints the commands before +# running them, ignoring the command line option for silent mode (-s) as +# well as the .SILENT special source and target, as well as the '@' prefix +# for shell commands. + +.MAKEFLAGS: -dl -s +.SILENT: + +# The -dl command line option does not affect commands that are run during +# variable expansion, such as :!cmd! or :sh. +.if ${:!echo word!} != "word" +. error +.endif + +all: .SILENT + # Even though the command line option -s is given, .SILENT is set + # for all targets and for this target in particular, the command + # is still printed. The -dl debugging option is stronger than all + # of these. + @echo all-word diff --git a/unit-tests/opt-debug-making.exp b/unit-tests/opt-debug-making.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-making.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-making.mk b/unit-tests/opt-debug-making.mk new file mode 100644 index 000000000000..bb37a9932d79 --- /dev/null +++ b/unit-tests/opt-debug-making.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-making.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dm command line option, which adds debug logging about +# making targets, including modification dates. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-meta.exp b/unit-tests/opt-debug-meta.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-meta.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-meta.mk b/unit-tests/opt-debug-meta.mk new file mode 100644 index 000000000000..fb4a0bf4939d --- /dev/null +++ b/unit-tests/opt-debug-meta.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-meta.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dM command line option, which adds debug logging about +# "meta" mode decisions about targets. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-no-rm.exp b/unit-tests/opt-debug-no-rm.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-no-rm.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-no-rm.mk b/unit-tests/opt-debug-no-rm.mk new file mode 100644 index 000000000000..fd185cca7b1d --- /dev/null +++ b/unit-tests/opt-debug-no-rm.mk @@ -0,0 +1,13 @@ +# $NetBSD: opt-debug-no-rm.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dn command line option, which prevents the temporary +# command scripts from being removed from the temporary directory. + +# TODO: Implementation + +# TODO: Does this apply to non-jobs mode? +# TODO: Does this apply to jobs mode? +# TODO: Are the generated filenames predictable? + +all: + @:; diff --git a/unit-tests/opt-debug-parse.exp b/unit-tests/opt-debug-parse.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-parse.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-parse.mk b/unit-tests/opt-debug-parse.mk new file mode 100644 index 000000000000..3427b68beb96 --- /dev/null +++ b/unit-tests/opt-debug-parse.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-parse.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dp command line option, which adds debug logging about +# makefile parsing. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-suff.exp b/unit-tests/opt-debug-suff.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-suff.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-suff.mk b/unit-tests/opt-debug-suff.mk new file mode 100644 index 000000000000..6ec466c90ca5 --- /dev/null +++ b/unit-tests/opt-debug-suff.mk @@ -0,0 +1,11 @@ +# $NetBSD: opt-debug-suff.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -ds command line option, which adds debug logging about +# suffix-transformation rules such as .c.o. + +# TODO: Implementation + +# TODO: What about %.o: %.c? + +all: + @:; diff --git a/unit-tests/opt-debug-targets.exp b/unit-tests/opt-debug-targets.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-targets.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-targets.mk b/unit-tests/opt-debug-targets.mk new file mode 100644 index 000000000000..c99a6eaf4376 --- /dev/null +++ b/unit-tests/opt-debug-targets.mk @@ -0,0 +1,11 @@ +# $NetBSD: opt-debug-targets.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dt command line option, which adds debug logging about +# target list maintenance. +# +# TODO: What exactly does "maintenance" mean? + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-var.exp b/unit-tests/opt-debug-var.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-var.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-var.mk b/unit-tests/opt-debug-var.mk new file mode 100644 index 000000000000..4d0ef9447324 --- /dev/null +++ b/unit-tests/opt-debug-var.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-var.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dv command line option, which adds debug logging about +# variable assignment and evaluation. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-varraw.exp b/unit-tests/opt-debug-varraw.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-varraw.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-varraw.mk b/unit-tests/opt-debug-varraw.mk new file mode 100644 index 000000000000..b334a2a89221 --- /dev/null +++ b/unit-tests/opt-debug-varraw.mk @@ -0,0 +1,12 @@ +# $NetBSD: opt-debug-varraw.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dV command line option, which forces the -V option to +# print the raw values of variables. + +# TODO: Does this make -V and -v equivalent, or are there any other subtle +# differences? + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug-x-trace.exp b/unit-tests/opt-debug-x-trace.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/opt-debug-x-trace.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/opt-debug-x-trace.mk b/unit-tests/opt-debug-x-trace.mk new file mode 100644 index 000000000000..0936ba506966 --- /dev/null +++ b/unit-tests/opt-debug-x-trace.mk @@ -0,0 +1,10 @@ +# $NetBSD: opt-debug-x-trace.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dx command line option, which runs shell commands with +# the -x option, thereby printing the actual commands as they are +# executed. + +# TODO: Implementation + +all: + @:; diff --git a/unit-tests/opt-debug.exp b/unit-tests/opt-debug.exp index 39a9383953dd..52a36c71b4ee 100644 --- a/unit-tests/opt-debug.exp +++ b/unit-tests/opt-debug.exp @@ -1 +1,4 @@ +Global:VAR = value +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/unit-tests/opt-debug.mk b/unit-tests/opt-debug.mk index afd740a6caab..973085ba2de7 100644 --- a/unit-tests/opt-debug.mk +++ b/unit-tests/opt-debug.mk @@ -1,8 +1,14 @@ -# $NetBSD: opt-debug.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-debug.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $ # -# Tests for the -d command line option. +# Tests for the -d command line option, which controls debug logging. -# TODO: Implementation +# Enable debug logging for the variables (var.c). +.MAKEFLAGS: -dv + +VAR= value + +# Disable all debug logging again. +.MAKEFLAGS: -d0 # -d0 is available since 2020-10-03 all: @:; diff --git a/unit-tests/opt-ignore.exp b/unit-tests/opt-ignore.exp index 265e143e3471..7d7c437ff0ea 100644 --- a/unit-tests/opt-ignore.exp +++ b/unit-tests/opt-ignore.exp @@ -1,12 +1,12 @@ dependency 1 +*** Error code 1 (ignored) dependency 2 +*** Error code 7 (ignored) dependency 3 other 1 +*** Error code 1 (ignored) other 2 main 1 -main 2 -*** Error code 1 (ignored) -*** Error code 7 (ignored) -*** Error code 1 (ignored) *** Error code 1 (ignored) +main 2 exit status 0 diff --git a/unit-tests/opt-ignore.mk b/unit-tests/opt-ignore.mk index 6d6d8dc3a339..ff11c1e15c31 100644 --- a/unit-tests/opt-ignore.mk +++ b/unit-tests/opt-ignore.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-ignore.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $ +# $NetBSD: opt-ignore.mk,v 1.4 2020/10/18 18:12:42 rillig Exp $ # # Tests for the -i command line option, which ignores the exit status of the # shell commands, and just continues with the next command, even from the same @@ -10,6 +10,8 @@ # file, where they cannot be related to the individual shell commands that # failed? +.MAKEFLAGS: -d0 # switch stdout to being line-buffered + all: dependency other dependency: diff --git a/unit-tests/opt-keep-going.exp b/unit-tests/opt-keep-going.exp index de1b1ae582f4..cdad54ac24f8 100644 --- a/unit-tests/opt-keep-going.exp +++ b/unit-tests/opt-keep-going.exp @@ -1,6 +1,6 @@ dependency 1 -other 1 *** Error code 1 (continuing) +other 1 *** Error code 1 (continuing) `all' not remade because of errors. exit status 0 diff --git a/unit-tests/opt-keep-going.mk b/unit-tests/opt-keep-going.mk index 1a2124ccb5f1..ec4adfa00e62 100644 --- a/unit-tests/opt-keep-going.mk +++ b/unit-tests/opt-keep-going.mk @@ -1,9 +1,11 @@ -# $NetBSD: opt-keep-going.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $ +# $NetBSD: opt-keep-going.mk,v 1.4 2020/10/18 18:12:42 rillig Exp $ # # Tests for the -k command line option, which stops building a target as soon # as an error is detected, but continues building the other, independent # targets, as far as possible. +.MAKEFLAGS: -d0 # switch stdout to being line-buffered + all: dependency other dependency: diff --git a/unit-tests/parse-var.exp b/unit-tests/parse-var.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/parse-var.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/parse-var.mk b/unit-tests/parse-var.mk new file mode 100644 index 000000000000..bd6c59f0e5cb --- /dev/null +++ b/unit-tests/parse-var.mk @@ -0,0 +1,13 @@ +# $NetBSD: parse-var.mk,v 1.1 2020/10/04 06:53:15 rillig Exp $ + +.MAKEFLAGS: -dL + +# In variable assignments, there may be spaces on the left-hand side of the +# assignment, but only if they occur inside variable expressions. +VAR.${:U param }= value +.if ${VAR.${:U param }} != "value" +. error +.endif + +all: + @:; diff --git a/unit-tests/phony-end.mk b/unit-tests/phony-end.mk index 07f4b029e31e..2992f640ec50 100644 --- a/unit-tests/phony-end.mk +++ b/unit-tests/phony-end.mk @@ -1,4 +1,4 @@ -# $Id: phony-end.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# $NetBSD: phony-end.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ all ok also.ok bug phony: @echo '${.TARGET .PREFIX .IMPSRC:L:@v@$v="${$v}"@}' diff --git a/unit-tests/posix.mk b/unit-tests/posix.mk index 608a24ab83f7..fc4cbead3263 100644 --- a/unit-tests/posix.mk +++ b/unit-tests/posix.mk @@ -1,4 +1,4 @@ -# $Id: posix.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# $NetBSD: posix.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ all: x plus subs err diff --git a/unit-tests/posix1.mk b/unit-tests/posix1.mk index f5d8e21678ea..746bf2194733 100644 --- a/unit-tests/posix1.mk +++ b/unit-tests/posix1.mk @@ -1,4 +1,4 @@ -# $NetBSD: posix1.mk,v 1.4 2020/08/10 18:19:58 rillig Exp $ +# $NetBSD: posix1.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $ # Keep the default suffixes from interfering, just in case. .SUFFIXES: @@ -55,14 +55,14 @@ suffix-substitution: # In the past substitutions did not work with the D/F forms and those # forms were not available for $?. (PR 49085) -ARFLAGS = -rcv +ARFLAGS= -rcv localvars: lib.a # $@ = target or archive name $< = implied source -# $* = target without suffix $? = sources newer than target +# $* = target without suffix $? = sources newer than target # $% = archive member name -LOCALS = \ +LOCALS= \ "Local variables\n\ \$${@}=\"${@}\" \$${<}=\"${<}\"\n\ \$${*}=\"${*}\" \$${?}=\"${?}\"\n\ @@ -70,7 +70,7 @@ LOCALS = \ # $XD = directory part of X $XF = file part of X # X is one of the local variables. -LOCAL_ALTERNATIVES = \ +LOCAL_ALTERNATIVES= \ "Directory and filename parts of local variables\n\ \$${@D}=\"${@D}\" \$${@F}=\"${@F}\"\n\ \$${<D}=\"${<D}\" \$${<F}=\"${<F}\"\n\ @@ -80,15 +80,15 @@ LOCAL_ALTERNATIVES = \ # Do all kinds of meaningless substitutions on local variables to see # if they work. Add, remove and replace things. -VAR2 = .o -VAR3 = foo -LOCAL_SUBSTITUTIONS = \ +VAR2= .o +VAR3= foo +LOCAL_SUBSTITUTIONS= \ "Local variable substitutions\n\ \$${@:.o=}=\"${@:.o=}\" \$${<:.c=.C}=\"${<:.c=.C}\"\n\ \$${*:=.h}=\"${*:=.h}\" \$${?:.h=.H}=\"${?:.h=.H}\"\n\ \$${%%:=}=\"${%:=}\"\n\n" -LOCAL_ALTERNATIVE_SUBSTITUTIONS = \ +LOCAL_ALTERNATIVE_SUBSTITUTIONS= \ "Target with suffix transformations\n\ \$${@D:=append}=\"${@D:=append}\"\n\ \$${@F:.o=.O}=\"${@F:.o=.O}\"\n\ diff --git a/unit-tests/qequals.mk b/unit-tests/qequals.mk index 67a48ad32ef3..a964e99b2645 100644 --- a/unit-tests/qequals.mk +++ b/unit-tests/qequals.mk @@ -1,8 +1,8 @@ -# $Id: qequals.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# $NetBSD: qequals.mk,v 1.3 2020/10/24 08:50:17 rillig Exp $ -M= i386 -V.i386= OK -V.$M ?= bug +M= i386 +V.i386= OK +V.$M?= bug all: @echo 'V.$M ?= ${V.$M}' diff --git a/unit-tests/recursive.mk b/unit-tests/recursive.mk index bc5a2817b333..6e5d8a2ca8b5 100644 --- a/unit-tests/recursive.mk +++ b/unit-tests/recursive.mk @@ -1,4 +1,4 @@ -# $NetBSD: recursive.mk,v 1.2 2020/08/06 05:52:45 rillig Exp $ +# $NetBSD: recursive.mk,v 1.3 2020/10/24 08:50:17 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 @@ -11,16 +11,16 @@ # Seen in pkgsrc/x11/libXfixes, and probably many more package that use # GNU Automake. -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 = +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) ... +libXfixes_la_LINK= ... ${AM_V_lt} ... # somewhere later ... -AM_DEFAULT_VERBOSITY = 1 +AM_DEFAULT_VERBOSITY= 1 # The purpose of the -dL flag is to detect unclosed variables. This @@ -31,7 +31,7 @@ AM_DEFAULT_VERBOSITY = 1 # 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} +UNCLOSED= $(MISSING_PAREN +UNCLOSED= ${MISSING_BRACE +UNCLOSED= ${MISSING_BRACE_INDIRECT} diff --git a/unit-tests/sh-dots.exp b/unit-tests/sh-dots.exp index 19482717087b..4d935096c48a 100755 --- a/unit-tests/sh-dots.exp +++ b/unit-tests/sh-dots.exp @@ -1,15 +1,22 @@ first first hidden hidden make: exec(...) failed (No such file or directory) +*** Error code 1 (ignored) hidden delayed hidden repeated repeated commented commented -*** Error code 1 (ignored) ... # Run the below commands later <normalized: ...: not found> +*** Error code 127 (ignored) commented delayed commented +indirect regular +indirect-space regular +... +make: exec(...) failed (No such file or directory) +*** Error code 1 (ignored) +indirect-space deferred first delayed first repeated delayed repeated repeated delayed twice repeated -*** Error code 127 (ignored) +indirect deferred exit status 0 diff --git a/unit-tests/sh-dots.mk b/unit-tests/sh-dots.mk index 36da5bce7a53..f85af9025e55 100755 --- a/unit-tests/sh-dots.mk +++ b/unit-tests/sh-dots.mk @@ -1,10 +1,12 @@ -# $NetBSD: sh-dots.mk,v 1.1 2020/08/22 11:27:02 rillig Exp $ +# $NetBSD: sh-dots.mk,v 1.3 2020/10/25 22:04:24 rillig Exp $ # # Tests for the special shell command line "...", which does not run the # commands below it but appends them to the list of commands that are run # at the end. -all: first hidden repeated commented +.MAKEFLAGS: -d0 # switch stdout to being line-buffered + +all: first hidden repeated commented indirect indirect-space # The ${.TARGET} correctly expands to the target name, even though the # commands are run separately from the main commands. @@ -27,6 +29,21 @@ commented: .IGNORE ... # Run the below commands later @echo commented delayed ${.TARGET} +# The dots don't have to be written literally, they can also come from a +# variable expression. +indirect: + @echo indirect regular + ${:U...} + @echo indirect deferred + +# If the dots are followed by a space, that space is part of the command and +# thus does not defer the command below it. +indirect-space: .IGNORE + @echo indirect-space regular + ${:U... } + @echo indirect-space deferred + + # The "..." can appear more than once, even though that doesn't make sense. # The second "..." is a no-op. repeated: .IGNORE diff --git a/unit-tests/shell-csh.exp b/unit-tests/shell-csh.exp new file mode 100644 index 000000000000..eafef2d45fcc --- /dev/null +++ b/unit-tests/shell-csh.exp @@ -0,0 +1,9 @@ +echo normal +normal +hidden +echo always +always +echo ignore errors +ignore errors +They chatted in the sy. +exit status 0 diff --git a/unit-tests/shell-csh.mk b/unit-tests/shell-csh.mk new file mode 100644 index 000000000000..590775dbcb5a --- /dev/null +++ b/unit-tests/shell-csh.mk @@ -0,0 +1,40 @@ +# $NetBSD: shell-csh.mk,v 1.5 2020/10/19 19:14:11 rillig Exp $ +# +# Tests for using a C shell for running the commands. + +CSH!= which csh || 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 commandShell->noPrint command is filtered from +# the output, rather naively (in JobOutput). +# +# 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/unit-tests/shell-custom.exp b/unit-tests/shell-custom.exp new file mode 100644 index 000000000000..ea733fb3226d --- /dev/null +++ b/unit-tests/shell-custom.exp @@ -0,0 +1,8 @@ +: normal +-c : normal +-c : hidden +: always +-c : always +: ignore errors +-c : ignore errors +exit status 0 diff --git a/unit-tests/shell-custom.mk b/unit-tests/shell-custom.mk new file mode 100644 index 000000000000..9edbc75bb9cd --- /dev/null +++ b/unit-tests/shell-custom.mk @@ -0,0 +1,14 @@ +# $NetBSD: shell-custom.mk,v 1.1 2020/10/03 14:39:36 rillig Exp $ +# +# Tests for using a custom shell for running the commands. + +.SHELL: name="sh" path="echo" +# TODO: demonstrate the other shell features as well: +# - error control +# - output control + +all: + : normal + @: hidden + +: always + -: ignore errors diff --git a/unit-tests/shell-ksh.exp b/unit-tests/shell-ksh.exp new file mode 100644 index 000000000000..0bf83203a23a --- /dev/null +++ b/unit-tests/shell-ksh.exp @@ -0,0 +1,4 @@ +: normal +: always +: ignore errors +exit status 0 diff --git a/unit-tests/shell-ksh.mk b/unit-tests/shell-ksh.mk new file mode 100644 index 000000000000..3acf98cdb5d1 --- /dev/null +++ b/unit-tests/shell-ksh.mk @@ -0,0 +1,11 @@ +# $NetBSD: shell-ksh.mk,v 1.1 2020/10/03 14:39:36 rillig Exp $ +# +# Tests for using a korn shell for running the commands. + +.SHELL: name="ksh" path="ksh" + +all: + : normal + @: hidden + +: always + -: ignore errors diff --git a/unit-tests/shell-sh.exp b/unit-tests/shell-sh.exp new file mode 100644 index 000000000000..0bf83203a23a --- /dev/null +++ b/unit-tests/shell-sh.exp @@ -0,0 +1,4 @@ +: normal +: always +: ignore errors +exit status 0 diff --git a/unit-tests/shell-sh.mk b/unit-tests/shell-sh.mk new file mode 100644 index 000000000000..b3d4f18bbac9 --- /dev/null +++ b/unit-tests/shell-sh.mk @@ -0,0 +1,12 @@ +# $NetBSD: shell-sh.mk,v 1.1 2020/10/03 14:39:36 rillig Exp $ +# +# Tests for using a bourne shell for running the commands. +# This is the default shell, so there's nothing surprising. + +.SHELL: name="sh" path="sh" + +all: + : normal + @: hidden + +: always + -: ignore errors diff --git a/unit-tests/suff-add-later.exp b/unit-tests/suff-add-later.exp new file mode 100644 index 000000000000..0556529457f7 --- /dev/null +++ b/unit-tests/suff-add-later.exp @@ -0,0 +1,15 @@ +defining transformation from `.c' to `.d' +inserting ".c" (1) at end of list +inserting ".d" (2) at end of list +defining transformation from `.d' to `.e' +inserting ".d" (2) at end of list +inserting ".e" (3) at end of list +: 'Making issue5a.c out of nothing.' +make: don't know how to make issue5a.d (continuing) +make: don't know how to make issue5b.c (continuing) +make: don't know how to make issue5c (continuing) +: 'Making issue5d.d out of nothing.' +make: don't know how to make issue5d.e (continuing) +make: don't know how to make issue5e.d (continuing) +`all' not remade because of errors. +exit status 0 diff --git a/unit-tests/suff-add-later.mk b/unit-tests/suff-add-later.mk new file mode 100644 index 000000000000..aacc7ed9e92a --- /dev/null +++ b/unit-tests/suff-add-later.mk @@ -0,0 +1,34 @@ +# $NetBSD: suff-add-later.mk,v 1.2 2020/10/21 08:18:24 rillig Exp $ +# +# https://gnats.netbsd.org/49086, issue 5: +# Adding more suffixes does not turn existing rules into suffix rules. + +.MAKEFLAGS: -ds + +all: issue5a.d issue5b.c issue5c issue5d.e issue5e.d + +.SUFFIXES: .c + +# At this point, only .c is a suffix, therefore the following are all regular +# rules. +.c.d .d.c .d .d.e .e.d: + : 'Making ${.TARGET} from ${.IMPSRC}.' + +# Adding .d and .e as suffixes should turn the above regular rules into +# suffix rules. +.SUFFIXES: .d .e + +issue5a.c issue5b.d issue5c.d issue5d.d issue5e.e: + : 'Making ${.TARGET} out of nothing.' + +# XXX: As of 2020-10-20, the result is unexpected. +# XXX: .d.c is not a transformation rule but a regular target. +# XXX: .d is not a transformation rule but a regular target. +# XXX: .e.d is not a transformation but a regular target. +# XXX: .c.d is listed as "Files that are only sources". +# XXX: .d.e is listed as "Files that are only sources". +# XXX: The suffixes .c and .e both have the number 2. +# XXX: don't know how to make issue5a.d (even though .c.d is a transformation +# rule and issue5a.c can be readily made) +#.MAKEFLAGS: -dg1 +.MAKEFLAGS: -d0 diff --git a/unit-tests/suff-clear-regular.exp b/unit-tests/suff-clear-regular.exp new file mode 100644 index 000000000000..f3d73f6e4a2f --- /dev/null +++ b/unit-tests/suff-clear-regular.exp @@ -0,0 +1,5 @@ +make: don't know how to make .a (continuing) +make: don't know how to make .a.b (continuing) +make: don't know how to make .b.a (continuing) +`all' not remade because of errors. +exit status 0 diff --git a/unit-tests/suff-clear-regular.mk b/unit-tests/suff-clear-regular.mk new file mode 100644 index 000000000000..4f98aa374818 --- /dev/null +++ b/unit-tests/suff-clear-regular.mk @@ -0,0 +1,31 @@ +# $NetBSD: suff-clear-regular.mk,v 1.1 2020/10/20 20:36:53 rillig Exp $ +# +# https://gnats.netbsd.org/49086, issue 4: +# Suffix rules do not become regular rules when .SUFFIXES is cleared. + +all: .a .a.b .b.a + +.SUFFIXES: .a .b .c + +# At this point, .a and .b are known suffixes, therefore the following +# targets are interpreted as transformation rules. +.a .a.b .b.a: + : 'Making ${.TARGET} from ${.IMPSRC}.' + +# The empty .SUFFIXES discards all previous suffixes. +# This means the above rules should be turned into regular targets. +.SUFFIXES: + +# XXX: As of 2020-10-20, the result is unexpected. +# XXX: .a.b is still a transformation rule. +# XXX: .a belongs to "Files that are only sources". +# XXX: .a.b belongs to "Files that are only sources". +# XXX: .b.a belongs to "Files that are only sources". +# XXX: .a is listed in "Transformations". +# XXX: .a.b is listed in "Transformations". +# XXX: .b.a is listed in "Transformations". +# XXX: don't know how to make .a +# XXX: don't know how to make .a.b +# XXX: don't know how to make .b.a +# XXX: exit status 0 +#.MAKEFLAGS: -dg1 diff --git a/unit-tests/suff-clear-single.exp b/unit-tests/suff-clear-single.exp new file mode 100644 index 000000000000..f8abe6348b34 --- /dev/null +++ b/unit-tests/suff-clear-single.exp @@ -0,0 +1,3 @@ +make: don't know how to make issue3 (continuing) +`all' not remade because of errors. +exit status 0 diff --git a/unit-tests/suff-clear-single.mk b/unit-tests/suff-clear-single.mk new file mode 100644 index 000000000000..04558064f459 --- /dev/null +++ b/unit-tests/suff-clear-single.mk @@ -0,0 +1,19 @@ +# $NetBSD: suff-clear-single.mk,v 1.1 2020/10/20 20:36:53 rillig Exp $ +# +# https://gnats.netbsd.org/49086, issue 3: +# Single suffix rules remain active after .SUFFIXES is cleared. +# +# There's a rule for issue3.a, but .a is no longer a known suffix when +# targets are being made, so issue3 should not get made. + +all: issue3 + +.SUFFIXES: .a .b .c + +.a .a.b .b.a: + : 'Making ${.TARGET} from ${.IMPSRC}.' + +.SUFFIXES: + +issue3.a: + : 'There is a bug if you see this.' diff --git a/unit-tests/suff-lookup.exp b/unit-tests/suff-lookup.exp new file mode 100644 index 000000000000..6714e7975e18 --- /dev/null +++ b/unit-tests/suff-lookup.exp @@ -0,0 +1,58 @@ +defining transformation from `.ccc' to `.cc' +inserting ".ccc" (3) at end of list +inserting ".cc" (2) at end of list +transformation .ccc.cc complete +defining transformation from `.c' to `.ccc' +inserting ".c" (1) at end of list +inserting ".ccc" (3) at end of list +transformation .c.ccc complete +defining transformation from `.short' to `.c' +inserting ".short" (4) at end of list +inserting ".c" (1) at end of list +transformation .short.c complete +defining transformation from `.sho' to `.c' +inserting ".sho" (5) at end of list +inserting ".c" (1) at end of list +transformation .sho.c complete +defining transformation from `.dead-end' to `.short' +inserting ".dead-end" (6) at end of list +inserting ".short" (4) at end of list +transformation .dead-end.short complete +inserting ".ccc" (3) at end of list +inserting ".cc" (2) at end of list +inserting ".c" (1) at end of list +inserting ".ccc" (3) at end of list +inserting ".short" (4) at end of list +inserting ".c" (1) at end of list +inserting ".sho" (5) at end of list +inserting ".c" (1) at end of list +inserting ".dead-end" (6) at end of list +inserting ".short" (4) at end of list +Wildcard expanding "all"... +SuffFindDeps (all) + No known suffix on all. Using .NULL suffix +adding suffix rules +Wildcard expanding "suff-lookup.cc"...suffix is ".cc"... +SuffFindDeps (suff-lookup.cc) + trying suff-lookup.ccc...not there + trying suff-lookup.c...not there + trying suff-lookup.short...not there + trying suff-lookup.sho...got it + applying .sho -> .c to "suff-lookup.c" + applying .c -> .ccc to "suff-lookup.ccc" + applying .ccc -> .cc to "suff-lookup.cc" +suffix is ".ccc"... +suffix is ".c"... +suffix is ".sho"... +SuffFindDeps (suff-lookup.sho) +suffix is ".sho"... +: 'Making suff-lookup.sho out of nothing.' +: 'Making suff-lookup.c from suff-lookup.sho.' +: 'Making suff-lookup.ccc from suff-lookup.c.' +: 'Making suff-lookup.cc from suff-lookup.ccc.' +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/unit-tests/suff-lookup.mk b/unit-tests/suff-lookup.mk new file mode 100644 index 000000000000..f45dfbd92111 --- /dev/null +++ b/unit-tests/suff-lookup.mk @@ -0,0 +1,40 @@ +# $NetBSD: suff-lookup.mk,v 1.2 2020/10/24 03:18:22 rillig Exp $ +# +# Demonstrate name resolution for suffixes. +# +# See also: +# FindSuffByName + +.MAKEFLAGS: -ds + +all: suff-lookup.cc + +.SUFFIXES: .c .cc .ccc + +# Register '.short' before '.sho'. When searching for the transformation +# '.sho.c', the suffix '.short' must not be found even though it starts with +# the correct characters. +.SUFFIXES: .short .sho .dead-end + +# From long to short suffix. +.ccc.cc: + : 'Making ${.TARGET} from ${.IMPSRC}.' + +# From short to long suffix. +.c.ccc: + : 'Making ${.TARGET} from ${.IMPSRC}.' + +.short.c: + : 'Making ${.TARGET} from ${.IMPSRC}.' +.sho.c: + : 'Making ${.TARGET} from ${.IMPSRC}.' +.dead-end.short: + : 'Making ${.TARGET} from ${.IMPSRC}.' + +suff-lookup.sho: + : 'Making ${.TARGET} out of nothing.' + +# Deleting all suffixes and adding them again rebuilds all of the above +# transformation rules. +.SUFFIXES: +.SUFFIXES: .c .cc .ccc .short .sho .dead-end diff --git a/unit-tests/suff-main.exp b/unit-tests/suff-main.exp new file mode 100644 index 000000000000..0a51101cf0eb --- /dev/null +++ b/unit-tests/suff-main.exp @@ -0,0 +1,2 @@ +: Making next-main +exit status 0 diff --git a/unit-tests/suff-main.mk b/unit-tests/suff-main.mk new file mode 100644 index 000000000000..192a9450d248 --- /dev/null +++ b/unit-tests/suff-main.mk @@ -0,0 +1,22 @@ +# $NetBSD: suff-main.mk,v 1.1 2020/10/18 16:33:18 rillig Exp $ +# +# Demonstrate that an inference rule is considered the main target if its +# suffixes are not known at the point of declaration. + +.1.2: + : Making ${.TARGET} from ${.IMPSRC}. + +# At this point, the target '.1.2' is a normal target. +# Since it is the first target in the first dependency declaration, +# it becomes the main target. + +next-main: + : Making ${.TARGET} + +# At this point, 'next-main' is effectively ignored. + +# Declaring both '.1' and '.2' as suffixes turns the '.1.2' target into an +# inference rule (OP_TRANSFORM). As a side effect, this target is no longer +# a candidate for the main target. Therefore the next target is selected as +# the main target, which in this case is 'next-main'. +.SUFFIXES: .1 .2 diff --git a/unit-tests/suff-rebuild.exp b/unit-tests/suff-rebuild.exp new file mode 100644 index 000000000000..b5bb60c23477 --- /dev/null +++ b/unit-tests/suff-rebuild.exp @@ -0,0 +1,5 @@ +: from nothing to a +: from a to b +: from b to c +: from c to nothing +exit status 0 diff --git a/unit-tests/suff-rebuild.mk b/unit-tests/suff-rebuild.mk new file mode 100644 index 000000000000..d190eaf4c6e8 --- /dev/null +++ b/unit-tests/suff-rebuild.mk @@ -0,0 +1,33 @@ +# $NetBSD: suff-rebuild.mk,v 1.2 2020/10/18 16:12:39 rillig Exp $ +# +# Demonstrates what happens to transformation rules (called inference rules +# by POSIX) when all suffixes are deleted. + +all: suff-rebuild-example + +.SUFFIXES: + +.SUFFIXES: .a .b .c + +suff-rebuild-example.a: + : from nothing to a + +.a.b: + : from a to b +.b.c: + : from b to c +.c: + : from c to nothing + +# XXX: At a quick glance, the code in SuffScanTargets looks as if it were +# possible to delete the suffixes in the middle of the makefile, add back +# the suffixes from before, and have the transformation rules preserved. +# +# As of 2020-09-25, uncommenting the following line results in the error +# message "don't know how to make suff-rebuild-example" though. +# +#.SUFFIXES: + +# Add the suffixes back. It should not matter that the order of the suffixes +# is different from before. +.SUFFIXES: .c .b .a diff --git a/unit-tests/suff-transform-endless.exp b/unit-tests/suff-transform-endless.exp new file mode 100644 index 000000000000..5cd267306b92 --- /dev/null +++ b/unit-tests/suff-transform-endless.exp @@ -0,0 +1,4 @@ +make: "suff-transform-endless.mk" line 36: prevent endless loop + +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/suff-transform-endless.mk b/unit-tests/suff-transform-endless.mk new file mode 100644 index 000000000000..546e24f398ad --- /dev/null +++ b/unit-tests/suff-transform-endless.mk @@ -0,0 +1,36 @@ +# $NetBSD: suff-transform-endless.mk,v 1.1 2020/10/20 20:36:53 rillig Exp $ + +# https://gnats.netbsd.org/49086, issue 6: +# Transformation search can end up in an infinite loop. +# +# There is no file or target from which issue6.f could be made, so +# this should fail. The bug is that because rules .e.f, .d.e and .e.d +# exist, make would try to make .f from .e and then infinitely try +# to do .e from .d and vice versa. + +all: issue6.f + +.c.d .d.c .d .d.e .e.d: + : 'Making ${.TARGET} from ${.IMPSRC}.' + +.SUFFIXES: .c .d .e .f + +.e .e.f .f.e: + : 'Making ${.TARGET} out of nothing.' + +# XXX: As of 2020-10-20, the result is unexpected. +# XXX: .d.c is not a transformation rule. +# XXX: .d is not a transformation rule. +# XXX: .e.d is not a transformation rule. +# XXX: .c.d is listed as "Files that are only sources". +# XXX: .d.e is listed as "Files that are only sources". +# XXX: The suffixes .d and .f both have the number 3. +# XXX: .c.d is not listed as "Transformations". +# XXX: .d.c is not listed as "Transformations". +# XXX: .d is not listed as "Transformations". +# XXX: .d.e is not listed as "Transformations". +# XXX: .e.d is not listed as "Transformations". +# XXX: Found 'all' as '(not found)' +# XXX: trying all.e, all.e, all.f, all.e, all.e, repeatedly. +#.MAKEFLAGS: -dg1 +.error prevent endless loop diff --git a/unit-tests/suff-transform-expand.exp b/unit-tests/suff-transform-expand.exp new file mode 100644 index 000000000000..178e264769af --- /dev/null +++ b/unit-tests/suff-transform-expand.exp @@ -0,0 +1,5 @@ +: 'Making issue11.h out of nothing.' +make: don't know how to make .first (continuing) +: 'Making issue11.second out of nothing.' +`all' not remade because of errors. +exit status 0 diff --git a/unit-tests/suff-transform-expand.mk b/unit-tests/suff-transform-expand.mk new file mode 100644 index 000000000000..943dd5279539 --- /dev/null +++ b/unit-tests/suff-transform-expand.mk @@ -0,0 +1,25 @@ +# $NetBSD: suff-transform-expand.mk,v 1.1 2020/10/20 20:36:53 rillig Exp $ +# +# https://gnats.netbsd.org/49086, issue 11: +# Sources from transformation rules are expanded incorrectly. +# +# issue11.j should depend on issue11.i and issue11.second. +# issue11.i should depend on issue11.h and issue11.first. +# +# XXX: The dynamic sources are expanded before ${.PREFIX} and +# ${.TARGET} were available, so they expand to an empty string. + +all: issue11.j + +.SUFFIXES: .h .i .j + +.h.i: ${.PREFIX}.first + : 'Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC}.' + +.i.j: ${.PREFIX}.second + : 'Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC}.' + +issue11.h issue11.first issue11.second: + : 'Making ${.TARGET} out of nothing.' + +#.MAKEFLAGS: -dg1 diff --git a/unit-tests/suff-transform-select.exp b/unit-tests/suff-transform-select.exp new file mode 100644 index 000000000000..8470ba3853dc --- /dev/null +++ b/unit-tests/suff-transform-select.exp @@ -0,0 +1,4 @@ +make: "suff-transform-select.mk" line 28: prevent endless loop + +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/suff-transform-select.mk b/unit-tests/suff-transform-select.mk new file mode 100644 index 000000000000..d4ae37086e84 --- /dev/null +++ b/unit-tests/suff-transform-select.mk @@ -0,0 +1,28 @@ +# $NetBSD: suff-transform-select.mk,v 1.1 2020/10/20 20:36:53 rillig Exp $ +# +# https://gnats.netbsd.org/49086, issue 10: +# Explicit dependencies affect transformation rule selection. +# +# If issue10.e is wanted and both issue10.d and issue10.f are available, +# make should choose the .d.e rule, because .d is before .f in .SUFFIXES. +# The bug was that if issue10.d had an explicit dependency on issue10.f, +# it would choose .f.e instead. + +_!= rm -f issue10.* + +all: issue10.e + +.c.d .d.c .d .d.e .e.d: + : 'Making ${.TARGET} from ${.IMPSRC} (first set).' + +.SUFFIXES: .c .d .e .f .g + +.e .e.f .f.e: + : 'Making ${.TARGET} from ${.IMPSRC} (second set).' + +issue10.d issue10.f: + : 'Making ${.TARGET} out of nothing.' + +# XXX: see suff-bug-endless, which must be fixed first. +#.MAKEFLAGS: -dg1 +.error prevent endless loop diff --git a/unit-tests/suffixes.exp b/unit-tests/suffixes.exp deleted file mode 100644 index 2a46e1cfa8e8..000000000000 --- a/unit-tests/suffixes.exp +++ /dev/null @@ -1,35 +0,0 @@ -make: don't know how to make issue3 (continuing) -There should be no text after the colon: -touch .a -There should be no text after the colon: -touch .a.b -There should be no text after the colon: -touch .b.a -touch issue5a.c -first set -cp issue5a.c issue5a.d -touch issue5b.d -first set -cp issue5b.d issue5b.c -touch issue5c.d -first set -cp issue5c.d issue5c -touch issue5d.d -first set -cp issue5d.d issue5d.e -touch issue5e.e -first set -cp issue5e.e issue5e.d -make: don't know how to make issue6.f (continuing) -touch issue10.d -first set -cp issue10.d issue10.e -touch issue11.h -touch issue11.first -.ALLSRC: issue11.h issue11.first -cp issue11.h issue11.i -touch issue11.second -.ALLSRC: issue11.i issue11.second -cp issue11.i issue11.j -`all' not remade because of errors. -exit status 0 diff --git a/unit-tests/suffixes.mk b/unit-tests/suffixes.mk deleted file mode 100644 index 113484a590af..000000000000 --- a/unit-tests/suffixes.mk +++ /dev/null @@ -1,89 +0,0 @@ -# $NetBSD: suffixes.mk,v 1.3 2014/08/30 22:21:08 sjg Exp $ - -# Issues from PR 49086 - -# Issue 3: single suffix rules remain active after .SUFFIXES is cleared -# -# There's a rule for issue3.a, but .a is no longer a known suffix when -# targets are being made, so issue3 should not get made. -all: issue3 - -# Issue 4: suffix rules do not become regular rules when .SUFFIXES is cleared -# -# When the rules were encountered, .a and .b were known suffices, but later -# on they were forgotten. These should get created as regular targets. -all: .a .a.b .b.a - -# Issue 5: adding more suffixes does not make existing rules into suffix rules -# -# When the targets .c.d, .d.c, .d, .d.e, and .e.d were encountered, only .a, -# .b and .c were known suffixes, so all of them were regular rules. Later -# rest of the suffixes were made known, so they should all be suffix -# transformation rules. -all: issue5a.d issue5b.c issue5c issue5d.e issue5e.d - -# Issue 6: transformation search can end up in an infinite loop -# -# There is no file or target from which issue6.f could be made from so -# this should fail. The bug was that because rules .e.f, .d.e and .e.d -# exist, make would try to make .f from .e and then infinitely try -# to do .e from .d and vice versa. -all: issue6.f - -# Issue 10: explicit dependencies affect transformation rule selection -# -# If issue10.e is wanted and both issue10.d and issue10.f are available, -# make should choose the .d.e rule, because .d is before .f in .SUFFIXES. -# The bug was that if issue10.d had an explicit dependency on issue10.f, -# it would choose .f.e instead. -all: issue10.e - -# Issue 11: sources from transformation rules are expanded incorrectly -# -# issue11.j should depend on issue11.i and issue11.second and issue11.i -# should depend on issue11.h and issue11.first. The bug was that -# the dynamic sources were expanded before ${.PREFIX} and ${.TARGET} were -# available, so they would have expanded to a null string. -all: issue11.j - -# we need to clean for repeatable results -.BEGIN: clean -clean: - @rm -f issue* .[ab]* - -.SUFFIXES: .a .b .c - -.a .a.b .b.a: - @echo 'There should be no text after the colon: ${.IMPSRC}' - touch ${.TARGET} - -.c.d .d.c .d .d.e .e.d: - @echo 'first set' - cp ${.IMPSRC} ${.TARGET} - -.SUFFIXES: -.SUFFIXES: .c .d .e .f .g - -.e .e.f .f.e: - @echo 'second set' - cp ${.IMPSRC} ${.TARGET} - -issue3.a: - @echo 'There is a bug if you see this.' - touch ${.TARGET} - -issue5a.c issue5b.d issue5c.d issue5d.d issue5e.e issue10.d issue10.f: - touch ${.TARGET} - -.SUFFIXES: .h .i .j - -.h.i: ${.PREFIX}.first - @echo '.ALLSRC: ${.ALLSRC}' - cp ${.IMPSRC} ${.TARGET} - -.i.j: ${.PREFIX}.second - @echo '.ALLSRC: ${.ALLSRC}' - cp ${.IMPSRC} ${.TARGET} - -issue11.h issue11.first issue11.second: - touch ${.TARGET} diff --git a/unit-tests/sunshcmd.mk b/unit-tests/sunshcmd.mk index e3baf901e51d..40db66d17ee0 100644 --- a/unit-tests/sunshcmd.mk +++ b/unit-tests/sunshcmd.mk @@ -1,8 +1,10 @@ -BYECMD = echo bye -LATERCMD = echo later +# $NetBSD: sunshcmd.mk,v 1.3 2020/10/24 08:50:17 rillig Exp $ + +BYECMD= echo bye +LATERCMD= echo later TEST1 :sh = echo hello TEST2 :sh = ${BYECMD} -TEST3 = ${LATERCMD:sh} +TEST3= ${LATERCMD:sh} all: @echo "TEST1=${TEST1}" diff --git a/unit-tests/sysv.exp b/unit-tests/sysv.exp deleted file mode 100644 index 610f97c39e85..000000000000 --- a/unit-tests/sysv.exp +++ /dev/null @@ -1,15 +0,0 @@ -FOOBAR = -FOOBAR = foobar fubar -fun -fun -fun -In the Sun -acme -aam.d -sam.c -a%.c -asam.c.c -asam.c -a.c.c - -exit status 0 diff --git a/unit-tests/sysv.mk b/unit-tests/sysv.mk deleted file mode 100644 index 5c87579cc11b..000000000000 --- a/unit-tests/sysv.mk +++ /dev/null @@ -1,43 +0,0 @@ -# $Id: sysv.mk,v 1.9 2020/08/23 16:08:32 sjg Exp $ - -all: foo fun sam bla - -FOO ?= -FOOBAR = ${FOO:=bar} - -_this := ${.PARSEDIR}/${.PARSEFILE} - -B = /b -S = / -FUN = ${B}${S}fun -SUN = the Sun - -# we expect nothing when FOO is empty -foo: - @echo FOOBAR = ${FOOBAR} -.if empty(FOO) - @FOO="foo fu" ${.MAKE} -f ${_this} foo -.endif - -fun: - @echo ${FUN:T} - @echo ${FUN:${B}${S}fun=fun} - @echo ${FUN:${B}${S}%=%} - @echo ${In:L:%=% ${SUN}} - - -SAM=sam.c - -sam: - @echo ${SAM:s%.c=acme} - @echo ${SAM:s%.c=a%.d} - @echo ${SAM:s.c=a%.d} - @echo ${SAM:sam.c=a%.c} - @echo ${SAM:%=a%.c} - @echo ${SAM:%.c=a%.c} - @echo ${SAM:sam%=a%.c} - -BLA= - -bla: - @echo $(BLA:%=foo/%x) diff --git a/unit-tests/ternary.mk b/unit-tests/ternary.mk index 77f834981c6c..78f2f6c88e36 100644 --- a/unit-tests/ternary.mk +++ b/unit-tests/ternary.mk @@ -1,3 +1,4 @@ +# $NetBSD: ternary.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ all: @for x in "" A= A=42; do ${.MAKE} -f ${MAKEFILE} show $$x; done diff --git a/unit-tests/unexport-env.mk b/unit-tests/unexport-env.mk index bc5fb4914ddc..85eed7672146 100644 --- a/unit-tests/unexport-env.mk +++ b/unit-tests/unexport-env.mk @@ -1,14 +1,14 @@ -# $Id: unexport-env.mk,v 1.1.1.2 2020/07/28 16:57:18 sjg Exp $ +# $NetBSD: unexport-env.mk,v 1.4 2020/10/24 08:50:17 rillig Exp $ # pick up a bunch of exported vars FILTER_CMD= grep ^UT_ .include "export.mk" # an example of setting up a minimal environment. -PATH = /bin:/usr/bin:/sbin:/usr/sbin +PATH= /bin:/usr/bin:/sbin:/usr/sbin # now clobber the environment to just PATH and UT_TEST -UT_TEST = unexport-env +UT_TEST= unexport-env # this removes everything .unexport-env diff --git a/unit-tests/unexport.mk b/unit-tests/unexport.mk index 7d2e8f275173..4363aaac3eee 100644 --- a/unit-tests/unexport.mk +++ b/unit-tests/unexport.mk @@ -1,4 +1,4 @@ -# $Id: unexport.mk,v 1.1.1.3 2020/08/08 22:34:25 sjg Exp $ +# $NetBSD: unexport.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $ # pick up a bunch of exported vars FILTER_CMD= grep ^UT_ @@ -6,7 +6,7 @@ FILTER_CMD= grep ^UT_ .unexport UT_ZOO UT_FOO -UT_TEST = unexport +UT_TEST= unexport # Until 2020-08-08, Var_UnExport had special handling for '\n', that code # was not reachable though. At that point, backslash-newline has already diff --git a/unit-tests/var-class-local.exp b/unit-tests/var-class-local.exp index 39a9383953dd..f1595c6810fb 100644 --- a/unit-tests/var-class-local.exp +++ b/unit-tests/var-class-local.exp @@ -1 +1,2 @@ +: all overwritten exit status 0 diff --git a/unit-tests/var-class-local.mk b/unit-tests/var-class-local.mk index e75f08ba75a3..68696ea854af 100644 --- a/unit-tests/var-class-local.mk +++ b/unit-tests/var-class-local.mk @@ -1,8 +1,31 @@ -# $NetBSD: var-class-local.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-class-local.mk,v 1.4 2020/10/25 09:46:25 rillig Exp $ # # Tests for target-local variables, such as ${.TARGET} or $@. # TODO: Implementation +# Ensure that the name of the variable is exactly the given one. +# The variable "@" is an alias for ".TARGET", so the implementation might +# canonicalize these aliases at some point, and that might be surprising. +# This aliasing happens for single-character variable names like $@ or $< +# (see VarFind, CanonicalVarname), but not for braced or parenthesized +# expressions like ${@}, ${.TARGET} ${VAR:Mpattern} (see Var_Parse, +# ParseVarname). +.if ${@:L} != "@" +. error +.endif +.if ${.TARGET:L} != ".TARGET" +. error +.endif +.if ${@F:L} != "@F" +. error +.endif +.if ${@D:L} != "@D" +. error +.endif + all: - @:; + # The ::= modifier overwrites the .TARGET variable in the node + # 'all', not in the global scope. This can be seen with the -dv + # option, looking for "all:@ = overwritten". + : ${.TARGET} ${.TARGET::=overwritten}${.TARGET} diff --git a/unit-tests/var-op-append.exp b/unit-tests/var-op-append.exp index 39a9383953dd..d9cfe21bd84c 100644 --- a/unit-tests/var-op-append.exp +++ b/unit-tests/var-op-append.exp @@ -1 +1,7 @@ +Var_Parse: ${:U\$\$\$\$\$\$\$\$} with VARE_WANTRES +Applying ${:U...} to "" (VARE_WANTRES, none, VEF_UNDEF) +Result of ${:U\$\$\$\$\$\$\$\$} is "$$$$$$$$" (VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) +Global:VAR.$$$$ = dollars +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/unit-tests/var-op-append.mk b/unit-tests/var-op-append.mk index b75880f95838..009a469e31ef 100644 --- a/unit-tests/var-op-append.mk +++ b/unit-tests/var-op-append.mk @@ -1,9 +1,48 @@ -# $NetBSD: var-op-append.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-op-append.mk,v 1.7 2020/10/30 20:36:33 rillig Exp $ # # Tests for the += variable assignment operator, which appends to a variable, # creating it if necessary. -# TODO: Implementation +# 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 +C++= value +.if ${C+} != "value" || defined(C++) +. error +.endif + +# Try out how often the variable name is expanded when appending to a +# nonexistent variable. +# As of 2020-10-30, that's two times. +# XXX: That's one time too often. +# See Var_Append, the call to Var_Set. +.MAKEFLAGS: -dv +VAR.${:U\$\$\$\$\$\$\$\$}+= dollars +.MAKEFLAGS: -d0 +.if ${VAR.${:U\$\$\$\$}} != "dollars" +. error +.endif all: @:; diff --git a/unit-tests/var-op-assign.mk b/unit-tests/var-op-assign.mk index dadff6ed4b87..c988d4141b83 100644 --- a/unit-tests/var-op-assign.mk +++ b/unit-tests/var-op-assign.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-assign.mk,v 1.4 2020/08/25 16:20:32 rillig Exp $ +# $NetBSD: var-op-assign.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $ # # Tests for the = variable assignment operator, which overwrites an existing # variable or creates it. @@ -13,7 +13,7 @@ VAR= value # is discarded. Otherwise the value would start with a single tab. # .if ${VAR} != "value" -.error +. error .endif # Whitespace to the left of the assignment operator is ignored as well. @@ -26,12 +26,12 @@ VAR= value # The '$' needs to be escaped with another '$', otherwise it would refer to # another variable. # -VAR =new value and \# some $$ special characters # comment +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} +. error ${VAR} .endif # The variable value may contain references to other variables. @@ -71,11 +71,11 @@ VARNAME_PAREN= VAR(spaces in parentheses) VARNAME_BRACES= VAR{spaces in braces} .if ${${VARNAME_PAREN}} != "()" -.error +. error .endif .if ${${VARNAME_BRACES}} != "{}" -.error +. error .endif # In safe mode, parsing would stop immediately after the "VARIABLE NAME=" diff --git a/unit-tests/var-op-sunsh.exp b/unit-tests/var-op-sunsh.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/var-op-sunsh.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/var-op-sunsh.mk b/unit-tests/var-op-sunsh.mk new file mode 100644 index 000000000000..efef19bf1567 --- /dev/null +++ b/unit-tests/var-op-sunsh.mk @@ -0,0 +1,122 @@ +# $NetBSD: var-op-sunsh.mk,v 1.5 2020/10/04 08:32:52 rillig Exp $ +# +# Tests for the :sh= variable assignment operator, which runs its right-hand +# side through the shell. It is a seldom-used alternative to the != +# assignment operator, adopted from Sun make. + +.MAKEFLAGS: -dL # Enable sane error messages + +# This is the idiomatic form of the Sun shell assignment operator. +# The assignment operator is directly preceded by the ':sh'. +VAR:sh= echo colon-sh +.if ${VAR} != "colon-sh" +. error +.endif + +# It is also possible to have whitespace around the :sh assignment +# operator modifier. +VAR :sh = echo colon-sh-spaced +.if ${VAR} != "colon-sh-spaced" +. error +.endif + +# Until 2020-10-04, the ':sh' could even be followed by other characters. +# This was neither documented by NetBSD make nor by Solaris make and was +# an implementation error. +# +# Since 2020-10-04, this is a normal variable assignment using the '=' +# assignment operator. +VAR:shell= echo colon-shell +.if ${${:UVAR\:shell}} != "echo colon-shell" +. error +.endif + +# Several colons can syntactically appear in a variable name. +# Until 2020-10-04, the last of them was interpreted as the ':sh' +# assignment operator. +# +# Since 2020-10-04, the colons are part of the variable name. +VAR:shoe:shore= echo two-colons +.if ${${:UVAR\:shoe\:shore}} != "echo two-colons" +. error +.endif + +# Until 2020-10-04, the following expression was wrongly marked as +# a parse error. This was because the parser for variable assignments +# just looked for the previous ":sh", without taking any contextual +# information into account. +# +# There are two different syntactical elements that look exactly the same: +# The variable modifier ':sh' and the assignment operator modifier ':sh'. +# Intuitively this variable name contains the variable modifier, but until +# 2020-10-04, the parser regarded it as an assignment operator modifier, in +# Parse_DoVar. +VAR.${:Uecho 123:sh}= ok-123 +.if ${VAR.123} != "ok-123" +. error +.endif + +# Same pattern here. Until 2020-10-04, the ':sh' inside the nested expression +# was taken for the :sh assignment operator modifier, even though it was +# escaped by a backslash. +VAR.${:U echo\:shell}= ok-shell +.if ${VAR.${:U echo\:shell}} != "ok-shell" +. error +.endif + +# Until 2020-10-04, the word 'shift' was also affected since it starts with +# ':sh'. +VAR.key:shift= Shift +.if ${${:UVAR.key\:shift}} != "Shift" +. error +.endif + +# Just for fun: The code in Parse_IsVar allows for multiple appearances of +# the ':sh' assignment operator modifier. Let's see what happens ... +# +# Well, the end result is correct but the way until there is rather +# adventurous. This only works because the parser replaces each an every +# whitespace character that is not nested with '\0' (see Parse_DoVar). +# The variable name therefore ends before the first ':sh', and the last +# ':sh' turns the assignment operator into the shell command evaluation. +# Parse_DoVar completely trusts Parse_IsVar to properly verify the syntax. +# +# The ':sh' is the only word that may occur between the variable name and +# the assignment operator at nesting level 0. All other words would lead +# to a parse error since the left-hand side of an assignment must be +# exactly one word. +VAR :sh :sh :sh :sh= echo multiple +.if ${VAR} != "multiple" +. error +.endif + +# The word ':sh' is not the only thing that can occur after a variable name. +# Since the parser just counts braces and parentheses instead of properly +# expanding nested expressions, the token ' :sh' can be used to add arbitrary +# text between the variable name and the assignment operator, it just has to +# be enclosed in braces or parentheses. +VAR :sh(Put a comment here)= comment in parentheses +.if ${VAR} != "comment in parentheses" +. error +.endif + +# The unintended comment can include multiple levels of nested braces and +# parentheses, they don't even need to be balanced since they are only +# counted by Parse_IsVar and ignored by Parse_DoVar. +VAR :sh{Put}((((a}{comment}}}}{here}= comment in braces +.if ${VAR} != "comment in braces" +. error +.endif + +# Syntactically, the ':sh' modifier can be combined with the '+=' assignment +# operator. In such a case the ':sh' modifier is silently ignored. +# +# XXX: This combination should not be allowed at all. +VAR= one +VAR :sh += echo two +.if ${VAR} != "one echo two" +. error ${VAR} +.endif + +all: + @:; diff --git a/unit-tests/var-recursive.exp b/unit-tests/var-recursive.exp new file mode 100644 index 000000000000..9739d8bcca13 --- /dev/null +++ b/unit-tests/var-recursive.exp @@ -0,0 +1,12 @@ +make: "var-recursive.mk" line 20: still there +Variable DIRECT is recursive. + +make: stopped in unit-tests +Variable INDIRECT1 is recursive. + +make: stopped in unit-tests +make: "var-recursive.mk" line 35: ok +Variable V is recursive. + +make: stopped in unit-tests +exit status 0 diff --git a/unit-tests/var-recursive.mk b/unit-tests/var-recursive.mk new file mode 100644 index 000000000000..da1fb696d655 --- /dev/null +++ b/unit-tests/var-recursive.mk @@ -0,0 +1,49 @@ +# $NetBSD: var-recursive.mk,v 1.2 2020/10/31 13:45:00 rillig Exp $ +# +# Tests for variable expressions that refer to themselves and thus +# cannot be evaluated. + +TESTS= direct indirect conditional short + +# Since make exits immediately when it detects a recursive expression, +# the actual tests are run in sub-makes. +TEST?= # none +.if ${TEST} == "" +all: +.for test in ${TESTS} + @${.MAKE} -f ${MAKEFILE} TEST=${test} || : +.endfor + +.elif ${TEST} == direct + +DIRECT= ${DIRECT} # Defining a recursive variable is not yet an error. +. info still there # Therefore this line is printed. +. info ${DIRECT} # But expanding the variable is an error. + +.elif ${TEST} == indirect + +# The chain of variables that refer to each other may be long. +INDIRECT1= ${INDIRECT2} +INDIRECT2= ${INDIRECT1} +. info ${INDIRECT1} + +.elif ${TEST} == conditional + +# The variable refers to itself, but only in the branch of a condition that +# is never satisfied and is thus not evaluated. +CONDITIONAL= ${1:?ok:${CONDITIONAL}} +. info ${CONDITIONAL} + +.elif ${TEST} == short + +# Short variable names can be expanded using the short-hand $V notation, +# which takes a different code path in Var_Parse for parsing the variable +# name. Ensure that these are checked as well. +V= $V +. info $V + +.else +. error Unknown test "${TEST}" +.endif + +all: diff --git a/unit-tests/varcmd.mk b/unit-tests/varcmd.mk index 53a8263f3484..9ec4f4f9a21a 100644 --- a/unit-tests/varcmd.mk +++ b/unit-tests/varcmd.mk @@ -1,13 +1,13 @@ -# $Id: varcmd.mk,v 1.1.1.3 2017/12/08 03:37:54 sjg Exp $ +# $NetBSD: varcmd.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $ # # Test behaviour of recursive make and vars set on command line. -FU=fu -FOO?=foo +FU= fu +FOO?= foo .if !empty(.TARGETS) -TAG=${.TARGETS} +TAG= ${.TARGETS} .endif -TAG?=default +TAG?= default all: one @@ -43,11 +43,11 @@ VAR=Internal four: show @${.MAKE} -f ${MAKEFILE} five -M = x -V.y = is y -V.x = is x -V := ${V.$M} -K := ${V} +M= x +V.y= is y +V.x= is x +V:= ${V.$M} +K:= ${V} show-v: @echo '${TAG} v=${V} k=${K}' diff --git a/unit-tests/vardebug.exp b/unit-tests/vardebug.exp index 8a9eefad4b0c..474a46ee1b5c 100644 --- a/unit-tests/vardebug.exp +++ b/unit-tests/vardebug.exp @@ -1,80 +1,86 @@ -Global:RELEVANT = yes +Global:delete FROM_CMDLINE (not found) +Command:FROM_CMDLINE = +Global:.MAKEOVERRIDES = FROM_CMDLINE Global:VAR = added Global:VAR = overwritten Global:delete VAR Global:delete VAR (not found) Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (eflags = VARE_WANTRES, vflags = VAR_JUNK) -Result of ${:U} is "" (eflags = VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Applying ${:U} to "" (VARE_WANTRES, none, VEF_UNDEF) +Result of ${:U} is "" (VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Var_Set("${:U}", "empty name", ...) name expands to empty string - ignored Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (eflags = VARE_WANTRES, vflags = VAR_JUNK) -Result of ${:U} is "" (eflags = VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Applying ${:U} to "" (VARE_WANTRES, none, VEF_UNDEF) +Result of ${:U} is "" (VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Var_Append("${:U}", "empty name", ...) name expands to empty string - ignored Global:FROM_CMDLINE = overwritten ignored! Global:VAR = 1 Global:VAR = 1 2 Global:VAR = 1 2 3 Var_Parse: ${VAR:M[2]} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VAR:M...} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) +Applying ${VAR:M...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Pattern[VAR] for [1 2 3] is [[2]] ModifyWords: split "1 2 3" into 3 words VarMatch [1] [[2]] VarMatch [2] [[2]] VarMatch [3] [[2]] -Result of ${VAR:M[2]} is "2" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) +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" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) +Applying ${VAR:N...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Pattern[VAR] for [1 2 3] is [[2]] ModifyWords: split "1 2 3" into 3 words -Result of ${VAR:N[2]} is "1 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) +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" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) +Applying ${VAR:S...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) Modifier part: "2" Modifier part: "two" ModifyWords: split "1 2 3" into 3 words -Result of ${VAR:S,2,two,} is "1 two 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) +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" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) -QuoteMeta: [1\ 2\ 3] -Result of ${VAR:Q} is "1\ 2\ 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = 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: ${VAR:tu:tl:Q} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VAR:t...} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) -Result of ${VAR:tu} is "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) -Applying ${VAR:t...} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) -Result of ${VAR:tl} is "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) -Applying ${VAR:Q} to "1 2 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) -QuoteMeta: [1\ 2\ 3] -Result of ${VAR:Q} is "1\ 2\ 3" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = none) +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 "" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK) -Result of ${:Uvalue} is "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) +Result of ${:Uvalue} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Var_Parse: ${:UM*e}:Mvalu[e]} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK) -Result of ${:UM*e} is "M*e" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) +Result of ${:UM*e} is "M*e" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Indirect modifier "M*e" from "${:UM*e}" -Applying ${:M...} to "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Applying ${:M...} to "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Pattern[] for [value] is [*e] ModifyWords: split "value" into 1 words VarMatch [value] [*e] -Result of ${:M*e} is "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) -Applying ${:M...} to "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Result of ${:M*e} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) +Applying ${:M...} to "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Pattern[] for [value] is [valu[e]] ModifyWords: split "value" into 1 words VarMatch [value] [valu[e]] -Result of ${:Mvalu[e]} is "value" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Result of ${:Mvalu[e]} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Var_Parse: ${:UVAR} with VARE_WANTRES -Applying ${:U...} to "" (eflags = VARE_WANTRES, vflags = VAR_JUNK) -Result of ${:UVAR} is "VAR" (eflags = VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Applying ${:U...} to "" (VARE_WANTRES, none, VEF_UNDEF) +Result of ${:UVAR} is "VAR" (VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Global:delete VAR Var_Parse: ${:Uvariable:unknown} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK) -Result of ${:Uvariable} is "variable" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) -Applying ${:u...} to "variable" (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) +Result of ${:Uvariable} is "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) +Applying ${:u...} to "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) make: Unknown modifier 'u' -Result of ${:unknown} is error (eflags = VARE_UNDEFERR|VARE_WANTRES, vflags = VAR_JUNK|VAR_KEEP) +Result of ${:unknown} is error (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) make: "vardebug.mk" line 44: Malformed conditional (${:Uvariable:unknown}) Var_Parse: ${UNDEFINED} with VARE_UNDEFERR|VARE_WANTRES make: "vardebug.mk" line 53: Malformed conditional (${UNDEFINED}) -Global:RELEVANT = no +Global:delete .SHELL (not found) +Command:.SHELL = /bin/sh +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/unit-tests/vardebug.mk b/unit-tests/vardebug.mk index f61df98d50de..51c2f4b0839a 100644 --- a/unit-tests/vardebug.mk +++ b/unit-tests/vardebug.mk @@ -1,8 +1,8 @@ -# $NetBSD: vardebug.mk,v 1.3 2020/08/08 14:28:46 rillig Exp $ +# $NetBSD: vardebug.mk,v 1.6 2020/10/31 13:15:10 rillig Exp $ # # Demonstrates the debugging output for var.c. -RELEVANT= yes +.MAKEFLAGS: -dv FROM_CMDLINE= VAR= added # VarAdd VAR= overwritten # Var_Set @@ -19,12 +19,12 @@ VAR= 1 VAR+= 2 VAR+= 3 -.if ${VAR:M[2]} # VarMatch +.if ${VAR:M[2]} # ModifyWord_Match .endif -.if ${VAR:N[2]} # VarNoMatch (no debug output) +.if ${VAR:N[2]} # ModifyWord_NoMatch (no debug output) .endif -.if ${VAR:S,2,two,} # VarGetPattern +.if ${VAR:S,2,two,} # ParseModifierPart .endif .if ${VAR:Q} # VarQuote @@ -53,7 +53,12 @@ VAR+= 3 .if ${UNDEFINED} .endif -RELEVANT= no +# By default, .SHELL is not defined and thus can be set. As soon as it is +# accessed, it is initialized in the command line context (during VarFind), +# where it is set to read-only. Assigning to it is ignored. +.MAKEFLAGS: .SHELL=overwritten + +.MAKEFLAGS: -d0 all: @: diff --git a/unit-tests/varmisc.mk b/unit-tests/varmisc.mk index 4c58b2a5dc77..e2d53129a932 100644 --- a/unit-tests/varmisc.mk +++ b/unit-tests/varmisc.mk @@ -1,4 +1,5 @@ -# $Id: varmisc.mk,v 1.19 2020/08/31 16:28:10 sjg Exp $ +# $Id: varmisc.mk,v 1.20 2020/10/26 17:43:57 sjg Exp $ +# $NetBSD: varmisc.mk,v 1.26 2020/10/24 08:50:17 rillig Exp $ # # Miscellaneous variable tests. @@ -12,10 +13,10 @@ all: varerror-unclosed unmatched_var_paren: @echo ${foo::=foo-text} -True = ${echo true >&2:L:sh}TRUE -False= ${echo false >&2:L:sh}FALSE +True= ${echo true >&2:L:sh}TRUE +False= ${echo false >&2:L:sh}FALSE -VSET= is set +VSET= is set .undef UNDEF U_false: @@ -46,7 +47,7 @@ NQ_none: @echo do not evaluate or expand :? if discarding @echo ${VSET:U${1:L:?${True}:${False}}} -April1= 1459494000 +April1= 1459494000 # slightly contorted syntax to use utc via variable strftime: @@ -54,11 +55,11 @@ strftime: @echo date=${%Y%m%d:L:${gmtime=${April1}:L}} # big jumps to handle 3 digits per step -M_cmpv.units = 1 1000 1000000 -M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh +M_cmpv.units= 1 1000 1000000 +M_cmpv= S,., ,g:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh -Version = 123.456.789 -cmpv.only = target specific vars +Version= 123.456.789 +cmpv.only= target specific vars cmpv: @echo Version=${Version} == ${Version:${M_cmpv}} @@ -67,11 +68,11 @@ cmpv: # catch misshandling of nested vars in .for loop MAN= -MAN1= make.1 +MAN1= make.1 .for s in 1 2 -.if defined(MAN$s) && !empty(MAN$s) -MAN+= ${MAN$s} -.endif +. if defined(MAN$s) && !empty(MAN$s) +MAN+= ${MAN$s} +. endif .endfor manok: @@ -106,7 +107,7 @@ save-dollars: .undef APPENDED APPENDED+= value .if ${APPENDED} != "value" -.error "${APPENDED}" +. error "${APPENDED}" .endif # Appending to an empty variable adds a space between the old value @@ -114,7 +115,7 @@ APPENDED+= value APPENDED= # empty APPENDED+= value .if ${APPENDED} != " value" -.error "${APPENDED}" +. error "${APPENDED}" .endif # Appending to parameterized variables works as well. @@ -122,7 +123,7 @@ PARAM= param VAR.${PARAM}= 1 VAR.${PARAM}+= 2 .if ${VAR.param} != "1 2" -.error "${VAR.param}" +. error "${VAR.param}" .endif # The variable name can contain arbitrary characters. @@ -134,13 +135,13 @@ PARAM= + VAR.${PARAM}= 1 VAR.${PARAM}+= 2 .if ${VAR.+} != "1 2" -.error "${VAR.+}" +. error "${VAR.+}" .endif .for param in + ! ? VAR.${param}= ${param} .endfor .if ${VAR.+} != "+" || ${VAR.!} != "!" || ${VAR.?} != "?" -.error "${VAR.+}" "${VAR.!}" "${VAR.?}" +. error "${VAR.+}" "${VAR.!}" "${VAR.?}" .endif # Appending to a variable from the environment creates a copy of that variable @@ -199,8 +200,8 @@ UNCLOSED_INDIR_1= ${UNCLOSED_ORIG UNCLOSED_INDIR_2= ${UNCLOSED_INDIR_1} FLAGS= one two -FLAGS+= ${FLAGS.${.ALLSRC:M*.c:T:u}} -FLAGS.target2.c = three four +FLAGS+= ${FLAGS.${.ALLSRC:M*.c:T:u}} +FLAGS.target2.c= three four target1.c: target2.c: diff --git a/unit-tests/varmod-assign.exp b/unit-tests/varmod-assign.exp index 120c775d5f69..743ef2fb4082 100644 --- a/unit-tests/varmod-assign.exp +++ b/unit-tests/varmod-assign.exp @@ -11,7 +11,7 @@ mod-assign-nested: else2e2 mod-assign-nested: then3t3 mod-assign-nested: else4e4 make: Bad modifier `:' for -value} +mod-assign-empty: value} make: Bad modifier `:' for mod-assign-empty: overwritten} mod-assign-empty: VAR=overwritten diff --git a/unit-tests/varmod-assign.mk b/unit-tests/varmod-assign.mk index 82b7d947c484..773c348b19c8 100644 --- a/unit-tests/varmod-assign.mk +++ b/unit-tests/varmod-assign.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-assign.mk,v 1.6 2020/08/25 21:16:53 rillig Exp $ +# $NetBSD: varmod-assign.mk,v 1.8 2020/10/18 21:37:24 rillig Exp $ # # Tests for the obscure ::= variable modifiers, which perform variable # assignments during evaluation, just like the = operator in C. @@ -51,13 +51,25 @@ SINK3:= ${1:?${THEN3::=then3${IT3::=t3}}:${ELSE3::=else3${IE3::=e3}}}${THEN3}${E 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 as a "bad modifier". - @echo ${::=value} + # 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 variable's value to its name. - # Since the name is still "VAR", assigning to that variable works. + # 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: @@ -79,3 +91,17 @@ mod-assign-shell-error: # FIXME: the error message says: "previous" returned non-zero status @${SH_ERR::=previous} @${SH_ERR::!= echo word; false } echo err=${SH_ERR} + +# 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 diff --git a/unit-tests/varmod-defined.mk b/unit-tests/varmod-defined.mk index fa5bf5c2245c..a722ebf666e6 100644 --- a/unit-tests/varmod-defined.mk +++ b/unit-tests/varmod-defined.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-defined.mk,v 1.3 2020/08/25 21:58:08 rillig Exp $ +# $NetBSD: varmod-defined.mk,v 1.7 2020/10/24 08:46:08 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. @@ -10,7 +10,7 @@ DEF= defined # "defined". # .if ${DEF:Dvalue} != "value" -.error +. error .endif # Since UNDEF is not defined, the "value" is ignored. Instead of leaving the @@ -21,8 +21,69 @@ DEF= defined # 2020-08-25 it is "Malformed conditional". # .if ${UNDEF:Dvalue} != "" -.error +. error .endif +# The modifier text may contain plain text as well as expressions. +# +.if ${DEF:D<${DEF}>} != "<defined>" +. 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. + all: @:; diff --git a/unit-tests/varmod-edge.mk b/unit-tests/varmod-edge.mk index e6f7f91c95c7..a0b6d9342ef6 100644 --- a/unit-tests/varmod-edge.mk +++ b/unit-tests/varmod-edge.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-edge.mk,v 1.12 2020/08/08 13:29:09 rillig Exp $ +# $NetBSD: varmod-edge.mk,v 1.13 2020/10/24 08:46:08 rillig Exp $ # # Tests for edge cases in variable modifiers. # @@ -163,9 +163,9 @@ EXP.colons= # empty .for test in ${TESTS} . if ${MOD.${test}} == ${EXP.${test}} -.info ok ${test} +. info ok ${test} . else -.warning error in ${test}: expected "${EXP.${test}}", got "${MOD.${test}}" +. warning error in ${test}: expected "${EXP.${test}}", got "${MOD.${test}}" . endif .endfor diff --git a/unit-tests/varmod-exclam-shell.mk b/unit-tests/varmod-exclam-shell.mk index 2e811ddb4f5c..eaad8275805f 100644 --- a/unit-tests/varmod-exclam-shell.mk +++ b/unit-tests/varmod-exclam-shell.mk @@ -1,27 +1,27 @@ -# $NetBSD: varmod-exclam-shell.mk,v 1.2 2020/08/16 12:48:55 rillig Exp $ +# $NetBSD: varmod-exclam-shell.mk,v 1.3 2020/10/24 08:46:08 rillig Exp $ # # Tests for the :!cmd! variable modifier. .if ${:!echo hello | tr 'l' 'l'!} != "hello" -.warning unexpected +. warning unexpected .endif # The output is truncated at the first null byte. # Cmd_Exec returns only a string pointer without length information. .if ${:!echo hello | tr 'l' '\0'!} != "he" -.warning unexpected +. warning unexpected .endif .if ${:!echo!} != "" -.warning A newline at the end of the output must be stripped. +. warning A newline at the end of the output must be stripped. .endif .if ${:!echo;echo!} != " " -.warning Only a single newline at the end of the output is stripped. +. warning Only a single newline at the end of the output is stripped. .endif .if ${:!echo;echo;echo;echo!} != " " -.warning Other newlines in the output are converted to spaces. +. warning Other newlines in the output are converted to spaces. .endif all: diff --git a/unit-tests/varmod-gmtime.exp b/unit-tests/varmod-gmtime.exp index 373e8de1a271..06e6314c7bfb 100644 --- a/unit-tests/varmod-gmtime.exp +++ b/unit-tests/varmod-gmtime.exp @@ -2,8 +2,26 @@ mod-gmtime: %Y 2020 %Y -%Y +localtime == localtime mod-gmtime-indirect: -make: Unknown modifier '1' +make: Invalid time value: ${:U1593536400}} + +mtime=1593536400} +parse-errors: +make: Invalid time value: -1}. + +: -1 becomes mtime=-1}. +make: Invalid time value: 1}. + +: space 1 becomes mtime= 1}. +: 0 becomes ok. +: 1 becomes Thu Jan 1 00:00:01 1970. +: INT32_MAX becomes Tue Jan 19 03:14:07 2038. +: INT32_MAX + 1 becomes Tue Jan 19 03:14:08 2038. +make: Invalid time value: 10000000000000000000000000000000}. + +: overflow becomes mtime=10000000000000000000000000000000}. +make: Invalid time value: error}. +: letter becomes mtime=error}. exit status 0 diff --git a/unit-tests/varmod-gmtime.mk b/unit-tests/varmod-gmtime.mk index 0a05ad58d982..b404d1c2768e 100644 --- a/unit-tests/varmod-gmtime.mk +++ b/unit-tests/varmod-gmtime.mk @@ -1,17 +1,35 @@ -# $NetBSD: varmod-gmtime.mk,v 1.2 2020/08/16 12:48:55 rillig Exp $ +# $NetBSD: varmod-gmtime.mk,v 1.6 2020/10/31 20:30:06 rillig Exp $ # # Tests for the :gmtime variable modifier, which formats a timestamp -# using strftime(3). +# using strftime(3) in UTC. all: mod-gmtime all: mod-gmtime-indirect +all: parse-errors + +# Test for the default time format, %c. Since the time always varies, it's +# only possible to check for the general format here. The names of the +# month and weekday are always in English, independent from the locale. +# Example: Thu Oct 29 18:56:41 2020 +.if ${:U:gmtime:tW:M??? ??? ?? ??\:??\:?? ????} == "" +. error +.endif mod-gmtime: @echo $@: - @echo ${%Y:L:gmtim=1593536400} # modifier name too short - @echo ${%Y:L:gmtime=1593536400} # 2020-07-01T00:00:00Z - @echo ${%Y:L:gmtimer=1593536400} # modifier name too long - @echo ${%Y:L:gm=gm:M*} + + # modifier name too short + @echo ${%Y:L:gmtim=1593536400} + + # 2020-07-01T00:00:00Z + @echo ${%Y:L:gmtime=1593536400} + + # modifier name too long + @echo ${%Y:L:gmtimer=1593536400} + + # If the modifier name is not matched exactly, fall back to the + # :from=to modifier. + @echo ${gmtime:L:gm%=local%} == localtime mod-gmtime-indirect: @echo $@: @@ -31,5 +49,39 @@ mod-gmtime-indirect: # ParseModifierPart, this would work. @echo ${%Y:L:gmtime=${:U1593536400}} -all: - @:; +parse-errors: + @echo $@: + + # As of 2020-10-31, it is possible to pass negative time stamps + # to the :gmtime modifier, resulting in dates before 1970. + # Going back 50 years in the past is not a practical use case for + # make. + : -1 becomes ${:L:gmtime=-1}. + + # Spaces are allowed, not because it would make sense but just as + # a side-effect from using strtoul. + : space 1 becomes ${:L:gmtime= 1}. + + # 0 means now; to get consistent test results, the actual value has + # to be normalized. + : 0 becomes ${:L:gmtime=0:C,^... ... .. ..:..:.. 20..$,ok,W}. + + : 1 becomes ${:L:gmtime=1}. + + : INT32_MAX becomes ${:L:gmtime=2147483647}. + + # This may be different if time_t is still a 32-bit signed integer. + : INT32_MAX + 1 becomes ${:L:gmtime=2147483648}. + + # Integer overflow. + # Because this modifier is implemented using strtoul, the parsed + # time is ULONG_MAX, which gets converted to -1. This results + # in a time stamp of the second before 1970. + : overflow becomes ${:L:gmtime=10000000000000000000000000000000}. + + # As of 2020-10-31, there is no error handling while parsing the + # :gmtime modifier, thus no error message is printed. Parsing + # stops after the '=', and the remaining string is parsed for + # more variable modifiers. Because of the unknown modifier 'e', + # the whole variable value is discarded and thus not printed. + : letter becomes ${:L:gmtime=error}. diff --git a/unit-tests/varmod-hash.mk b/unit-tests/varmod-hash.mk index ef59268cb82c..5407e8299f9e 100644 --- a/unit-tests/varmod-hash.mk +++ b/unit-tests/varmod-hash.mk @@ -1,6 +1,60 @@ -# $NetBSD: varmod-hash.mk,v 1.3 2020/08/23 15:13:21 rillig Exp $ +# $NetBSD: varmod-hash.mk,v 1.5 2020/09/04 06:54:07 rillig Exp $ # -# Tests for the :hash variable modifier. +# Tests for the :hash variable modifier, which computes a 32-bit hash from +# the value of the expression. + +# Test vectors for generating certain hashes. Found by a brute force +# search over [a-z]{8}. +# +VECTORS+= 00000000 adjbuqnt +VECTORS+= 00000001 beiiyxdp +VECTORS+= 00000002 ajriwzqe +VECTORS+= 00000004 aimszzcb +VECTORS+= 00000008 afffvsgz +VECTORS+= 00000010 alkksbun +VECTORS+= 00000020 arqeianj +VECTORS+= 00000040 acgaltwv +VECTORS+= 00000080 addsjxec +VECTORS+= 00000100 acbozubm +VECTORS+= 00000200 acnbugtp +VECTORS+= 00000400 ajyfkpcl +VECTORS+= 00000800 akobyelz +VECTORS+= 00001000 aclmaggk +VECTORS+= 00002000 aauwlqiq +VECTORS+= 00004000 ankfvoqf +VECTORS+= 00008000 airtytts +VECTORS+= 00010000 bfwwrqfi +VECTORS+= 00020000 actwkzix +VECTORS+= 00040000 alsfbgvo +VECTORS+= 00080000 aioiauem +VECTORS+= 00100000 bxexhpji +VECTORS+= 00200000 awtxcwch +VECTORS+= 00400000 aoqpmqam +VECTORS+= 00800000 akgtvjhz +VECTORS+= 01000000 bcmsuvrm +VECTORS+= 02000000 aqnktorm +VECTORS+= 04000000 aweqylny +VECTORS+= 08000000 crvkuyze +VECTORS+= 10000000 alxiatjv +VECTORS+= 20000000 aezwuukx +VECTORS+= 40000000 abdpnifu +VECTORS+= 80000000 auusgoii +VECTORS+= ffffffff ahnvmfdw + +VECTORS+= b2af338b "" +VECTORS+= 3360ac65 a +VECTORS+= 7747f046 ab +VECTORS+= 9ca87054 abc +VECTORS+= 880fe816 abcd +VECTORS+= 208fcbd3 abcde +VECTORS+= d5d376eb abcdef +VECTORS+= de41416c abcdefghijklmnopqrstuvwxyz + +.for hash input in ${VECTORS} +. if ${input:S,^""$,,:hash} != ${hash} +. warning Expected ${hash} for ${input}, but was ${input:hash}. +. endif +.endfor all: @echo ${12345:L:has} # modifier name too short diff --git a/unit-tests/varmod-ifelse.exp b/unit-tests/varmod-ifelse.exp index 39a9383953dd..ac1527a11c9e 100644 --- a/unit-tests/varmod-ifelse.exp +++ b/unit-tests/varmod-ifelse.exp @@ -1 +1,8 @@ -exit status 0 +make: Bad conditional expression `variable expression == "literal"' in variable expression == "literal"?bad:bad +make: "varmod-ifelse.mk" line 27: Malformed conditional (${${:Uvariable expression} == "literal":?bad:bad}) +make: Bad conditional expression ` == ""' in == ""?bad-assign:bad-assign +make: Bad conditional expression ` == ""' in == ""?bad-cond:bad-cond +make: "varmod-ifelse.mk" line 44: Malformed conditional (${${UNDEF} == "":?bad-cond:bad-cond}) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/varmod-ifelse.mk b/unit-tests/varmod-ifelse.mk index e8e92b35d1c1..8bd67195282c 100644 --- a/unit-tests/varmod-ifelse.mk +++ b/unit-tests/varmod-ifelse.mk @@ -1,9 +1,61 @@ -# $NetBSD: varmod-ifelse.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varmod-ifelse.mk,v 1.5 2020/10/23 14:24:51 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 + all: @:; diff --git a/unit-tests/varmod-l-name-to-value.mk b/unit-tests/varmod-l-name-to-value.mk index b8a5877e1100..354622cf098b 100644 --- a/unit-tests/varmod-l-name-to-value.mk +++ b/unit-tests/varmod-l-name-to-value.mk @@ -1,21 +1,21 @@ -# $NetBSD: varmod-l-name-to-value.mk,v 1.3 2020/08/25 22:25:05 rillig Exp $ +# $NetBSD: varmod-l-name-to-value.mk,v 1.7 2020/10/24 08:46:08 rillig Exp $ # # Tests for the :L modifier, which returns the variable name as the new value. # The empty variable name leads to an empty string. .if ${:L} != "" -.error +. error .endif # The variable name is converted into an expression with the variable name # "VARNAME" and the value "VARNAME". .if ${VARNAME:L} != "VARNAME" -.error +. error .endif # The value of the expression can be modified afterwards. .if ${VARNAME:L:S,VAR,,} != "NAME" -.error +. error .endif # The name of the expression is still the same as before. Using the :L @@ -24,7 +24,20 @@ # Hmmm, this can be used as a double storage or a backup mechanism. # Probably unintended, but maybe useful. .if ${VARNAME:L:S,VAR,,:L} != "VARNAME" -.error +. error +.endif + +# Between 2020-09-22 (var.c 1.527) and 2020-09-30 (var.c 1.553), there was +# a bug in the evaluation of variable expressions. Indirect modifiers like +# the below :L did not update the definedness of the enclosing expression. +# This resulted in a wrong "Malformed conditional". +.if ${value:${:UL}} == "" +.endif + +# As of 2020-10-02, the :L modifier does not ensure that it is followed by +# a delimiter, that is, a ':' or endc. Neither does the :P modifier. +.if ${value:LLLLLLPL} != "value" +. error .endif all: diff --git a/unit-tests/varmod-localtime.exp b/unit-tests/varmod-localtime.exp index 69e4335be187..e89a03b40765 100644 --- a/unit-tests/varmod-localtime.exp +++ b/unit-tests/varmod-localtime.exp @@ -1,4 +1,27 @@ +mod-localtime %Y 2020 %Y +gmtime == gmtime +mod-localtime-indirect: +make: Invalid time value: ${:U1593536400}} + +ocaltime=1593536400} +parse-errors: +make: Invalid time value: -1}. + +: -1 becomes ocaltime=-1}. +make: Invalid time value: 1}. + +: space 1 becomes ocaltime= 1}. +: 0 becomes ok. +: 1 becomes Thu Jan 1 01:00:01 1970. +: INT32_MAX becomes Tue Jan 19 04:14:07 2038. +: INT32_MAX + 1 becomes Tue Jan 19 04:14:08 2038. +make: Invalid time value: 10000000000000000000000000000000}. + +: overflow becomes ocaltime=10000000000000000000000000000000}. +make: Invalid time value: error}. + +: letter becomes ocaltime=error}. exit status 0 diff --git a/unit-tests/varmod-localtime.mk b/unit-tests/varmod-localtime.mk index fa4fd4f9cfb1..f7358e309046 100644 --- a/unit-tests/varmod-localtime.mk +++ b/unit-tests/varmod-localtime.mk @@ -1,9 +1,92 @@ -# $NetBSD: varmod-localtime.mk,v 1.3 2020/08/23 15:13:21 rillig Exp $ +# $NetBSD: varmod-localtime.mk,v 1.5 2020/10/31 20:30:06 rillig Exp $ # -# Tests for the :localtime variable modifier, which returns the given time, -# formatted as a local timestamp. +# Tests for the :localtime variable modifier, which formats a timestamp +# using strftime(3) in local time. -all: - @echo ${%Y:L:localtim=1593536400} # modifier name too short - @echo ${%Y:L:localtime=1593536400} # 2020-07-01T00:00:00Z - @echo ${%Y:L:localtimer=1593536400} # modifier name too long +.if ${TZ} != "Europe/Berlin" # see unit-tests/Makefile +. error +.endif + +all: mod-localtime +all: mod-localtime-indirect +all: parse-errors + +# Test for the default time format, %c. Since the time always varies, it's +# only possible to check for the general format here. The names of the +# month and weekday are always in English, independent from the locale. +# Example: Thu Oct 29 18:56:41 2020 +.if ${:U:localtime:tW:M??? ??? ?? ??\:??\:?? ????} == "" +. error +.endif + +mod-localtime: + @echo $@ + + # modifier name too short + @echo ${%Y:L:localtim=1593536400} + + # 2020-07-01T00:00:00Z + @echo ${%Y:L:localtime=1593536400} + + # modifier name too long + @echo ${%Y:L:localtimer=1593536400} + + # If the modifier name is not matched exactly, fall back to the + # :from=to modifier. + @echo ${localtime:L:local%=gm%} == gmtime + +mod-localtime-indirect: + @echo $@: + + # As of 2020-08-16, it is not possible to pass the seconds via a + # variable expression. This is because parsing of the :localtime + # modifier stops at the '$' and returns to ApplyModifiers. + # + # There, a colon would be skipped but not a dollar. + # Parsing therefore continues at the '$' of the ${:U159...}, looking + # for an ordinary variable modifier. + # + # At this point, the ${:U} is expanded and interpreted as a variable + # modifier, which results in the error message "Unknown modifier '1'". + # + # If ApplyModifier_Localtime were to pass its argument through + # ParseModifierPart, this would work. + @echo ${%Y:L:localtime=${:U1593536400}} + +parse-errors: + @echo $@: + + # As of 2020-10-31, it is possible to pass negative time stamps + # to the :localtime modifier, resulting in dates before 1970. + # Going back 50 years in the past is not a practical use case for + # make. + : -1 becomes ${:L:localtime=-1}. + + # Spaces are allowed, not because it would make sense but just as + # a side-effect from using strtoul. + : space 1 becomes ${:L:localtime= 1}. + + # 0 means now; to get consistent test results, the actual value has + # to be normalized. + : 0 becomes ${:L:localtime=0:C,^... ... .. ..:..:.. 20..$,ok,W}. + + : 1 becomes ${:L:localtime=1}. + + : INT32_MAX becomes ${:L:localtime=2147483647}. + + # This may be different if time_t is still a 32-bit signed integer. + : INT32_MAX + 1 becomes ${:L:localtime=2147483648}. + + # Integer overflow. + # Because this modifier is implemented using strtoul, the parsed + # time is ULONG_MAX, which gets converted to -1. This results + # in a time stamp of the second before 1970 (in UTC) or 3599 seconds + # after New Year's Day in Europe/Berlin. + : overflow becomes ${:L:localtime=10000000000000000000000000000000}. + + # As of 2020-10-31, there is no error handling while parsing the + # :localtime modifier, thus no error message is printed. Parsing + # stops after the '=', and the remaining string is parsed for + # more variable modifiers. Because of the unknown modifier 'e', + # the whole variable value is discarded and thus not printed. + : letter becomes ${:L:localtime=error}. diff --git a/unit-tests/varmod-loop.exp b/unit-tests/varmod-loop.exp index ce93eb5bc0c8..497de68df82c 100644 --- a/unit-tests/varmod-loop.exp +++ b/unit-tests/varmod-loop.exp @@ -3,6 +3,7 @@ :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:() () (). diff --git a/unit-tests/varmod-loop.mk b/unit-tests/varmod-loop.mk index 561f4b95baa0..a3fbbfceae8d 100644 --- a/unit-tests/varmod-loop.mk +++ b/unit-tests/varmod-loop.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-loop.mk,v 1.2 2020/08/16 12:30:45 rillig Exp $ +# $NetBSD: varmod-loop.mk,v 1.5 2020/10/31 12:34:03 rillig Exp $ # # Tests for the :@var@...${var}...@ variable modifier. @@ -13,20 +13,29 @@ all: mod-loop-dollar # 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 + + # ":::" is a very creative variable name, unlikely in practice. # The expression ${\:\:\:} would not work since backslashes can only # be escaped in the modifiers, but not in the variable name. @echo :${:U1 2 3:@:::@x${${:U\:\:\:}}y@}: + # "@@" is another creative variable name. @echo :${:U1 2 3:@\@\@@x${@@}y@}: + # Even "@" works as a variable name since the variable is installed # in the "current" scope, which in this case is the one from the # target. @echo :$@: :${:U1 2 3:@\@@x${@}y@}: :$@: + # In extreme cases, even the backslash can be used as variable name. # It needs to be doubled though. @echo :${:U1 2 3:@\\@x${${:Ux:S,x,\\,}}y@}: + # The variable name can technically be empty, and in this situation + # the variable value cannot be accessed since the empty variable is + # protected to always return an empty string. + @echo empty: :${:U1 2 3:@@x${}y@}: + # The :@ modifier resolves the variables a little more often than expected. # In particular, it resolves _all_ variables from the context, and not only # the loop variable (in this case v). @@ -61,3 +70,33 @@ mod-loop-dollar: @echo $@:${:U4:@word@$$$${word}$$$$@:Q}: @echo $@:${:U5:@word@$$$$${word}$$$$$@:Q}: @echo $@:${:U6:@word@$$$$$${word}$$$$$$@:Q}: + +# It may happen that there are nested :@ modifiers that use the same name for +# for the loop variable. These modifiers influence each other. +# +# As of 2020-10-18, the :@ modifier is implemented by actually setting a +# variable in the context of the expression and deleting it again after the +# loop. This is different from the .for loops, which substitute the variable +# expression with ${:Uvalue}, leading to different unwanted side effects. +# +# To make the behavior more predictable, the :@ modifier should restore the +# loop variable to the value it had before the loop. This would result in +# the string "1a b c1 2a b c2 3a b c3", making the two loops independent. +.if ${:U1 2 3:@i@$i${:Ua b c:@i@$i@}${i:Uu}@} != "1a b cu 2a b cu 3a b cu" +. error +.endif + +# During the loop, the variable is actually defined and nonempty. +# If the loop were implemented in the same way as the .for loop, the variable +# would be neither defined nor nonempty since all expressions of the form +# ${var} would have been replaced with ${:Uword} before evaluating them. +.if defined(var) +. error +.endif +.if ${:Uword:@var@${defined(var):?def:undef} ${empty(var):?empty:nonempty}@} \ + != "def nonempty" +. error +.endif +.if defined(var) +. error +.endif diff --git a/unit-tests/varmod-match-escape.exp b/unit-tests/varmod-match-escape.exp index 1e4c030c5b42..023fd7f23dfb 100755 --- a/unit-tests/varmod-match-escape.exp +++ b/unit-tests/varmod-match-escape.exp @@ -1,3 +1,61 @@ +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, VEF_UNDEF) +Result of ${:U} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Pattern[SPECIALS] for [\: : \\ * \*] 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, VEF_UNDEF) +Result of ${:U} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) Pattern[SPECIALS] for [\: : \\ * \*] is [:] -exit status 0 +ModifyWords: split "\: : \\ * \*" into 5 words +VarMatch [\:] [:] +VarMatch [:] [:] +VarMatch [\\] [:] +VarMatch [*] [:] +VarMatch [\*] [:] +Result of ${SPECIALS:M\:${:U}} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, none) +lhs = ":", rhs = ":", op = != +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, VEF_UNDEF) +Result of ${:U} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) +Pattern[VALUES] for [: :: :\:] 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, VEF_UNDEF) +Result of ${:U\:} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) +Pattern[VALUES] for [: :: :\:] is [:\:] +ModifyWords: split ": :: :\:" into 3 words +VarMatch [:] [:\:] +VarMatch [::] [:\:] +VarMatch [:\:] [:\:] +Result of ${VALUES:M${:U\:}\:} is "::" (VARE_UNDEFERR|VARE_WANTRES, none, none) +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 +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/unit-tests/varmod-match-escape.mk b/unit-tests/varmod-match-escape.mk index 7913bb476ae7..5115d1b565df 100755 --- a/unit-tests/varmod-match-escape.mk +++ b/unit-tests/varmod-match-escape.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-match-escape.mk,v 1.1 2020/08/16 20:03:53 rillig Exp $ +# $NetBSD: varmod-match-escape.mk,v 1.5 2020/11/01 19:49:28 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 @@ -9,12 +9,64 @@ # 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= \: : \\ * \* -RELEVANT= yes .if ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} -.warning unexpected +. 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. +# 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 -RELEVANT= no all: @:; diff --git a/unit-tests/varmod-match.exp b/unit-tests/varmod-match.exp index 11777e086d4f..080e9e0f74de 100644 --- a/unit-tests/varmod-match.exp +++ b/unit-tests/varmod-match.exp @@ -1,5 +1,12 @@ -match-char-class: - uppercase numbers: One Two Three Four - all the others: five six seven - starts with non-s, ends with [ex]: One Three five +CondParser_Eval: ${NUMBERS:M[A-Z]*} != "One Two Three Four" +lhs = "One Two Three Four", rhs = "One Two Three Four", op = != +CondParser_Eval: ${NUMBERS:M[^A-Z]*} != "five six seven" +lhs = "five six seven", rhs = "five six seven", op = != +CondParser_Eval: ${NUMBERS:M[^s]*[ex]} != "One Three five" +lhs = "One Three five", rhs = "One Three five", op = != +CondParser_Eval: ${:U****************:M****************b} +CondParser_Eval: ${:Ua \$ sign:M*$$*} != "\$" +lhs = "$", rhs = "$", op = != +CondParser_Eval: ${:Ua \$ sign any-asterisk:M*\$*} != "any-asterisk" +lhs = "any-asterisk", rhs = "any-asterisk", op = != exit status 0 diff --git a/unit-tests/varmod-match.mk b/unit-tests/varmod-match.mk index 805426a0fda9..5e16a9cc8bf1 100644 --- a/unit-tests/varmod-match.mk +++ b/unit-tests/varmod-match.mk @@ -1,22 +1,55 @@ -# $NetBSD: varmod-match.mk,v 1.3 2020/08/16 20:03:53 rillig Exp $ +# $NetBSD: varmod-match.mk,v 1.5 2020/09/13 05:36:26 rillig Exp $ # # Tests for the :M variable modifier, which filters words that match the # given pattern. +# +# See ApplyModifier_Match and ModifyWord_Match for the implementation. -all: match-char-class -all: slow - +.MAKEFLAGS: -dc NUMBERS= One Two Three Four five six seven -match-char-class: - @echo '$@:' - @echo ' uppercase numbers: ${NUMBERS:M[A-Z]*}' - @echo ' all the others: ${NUMBERS:M[^A-Z]*}' - @echo ' starts with non-s, ends with [ex]: ${NUMBERS:M[^s]*[ex]}' +# Only keep words that start with an uppercase letter. +.if ${NUMBERS:M[A-Z]*} != "One Two Three Four" +. error +.endif +# Only keep words that start with a character other than an uppercase letter. +.if ${NUMBERS:M[^A-Z]*} != "five six seven" +. error +.endif + +# Only keep words that don't start with s and at the same time end with +# either of [ex]. +# +# This test case ensures that the negation from the first character class +# does not propagate to the second character class. +.if ${NUMBERS:M[^s]*[ex]} != "One Three five" +. error +.endif # Before 2020-06-13, this expression took quite a long time in Str_Match, # calling itself 601080390 times for 16 asterisks. -slow: - @: ${:U****************:M****************b} +.if ${:U****************:M****************b} +.endif + +# To match a dollar sign in a word, double it. +# +# This is different from the :S and :C variable modifiers, where a '$' +# has to be escaped as '\$'. +.if ${:Ua \$ sign:M*$$*} != "\$" +. error +.endif + +# In the :M modifier, '\$' does not escape a dollar. Instead it is +# interpreted as a backslash followed by whatever expression the +# '$' starts. +# +# This differs from the :S, :C and several other variable modifiers. +${:U*}= asterisk +.if ${:Ua \$ sign any-asterisk:M*\$*} != "any-asterisk" +. error +.endif + +all: + @:; diff --git a/unit-tests/varmod-order-reverse.mk b/unit-tests/varmod-order-reverse.mk index fa9d179560c8..1a6d2d766f76 100644 --- a/unit-tests/varmod-order-reverse.mk +++ b/unit-tests/varmod-order-reverse.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-order-reverse.mk,v 1.3 2020/08/16 20:13:10 rillig Exp $ +# $NetBSD: varmod-order-reverse.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for the :Or variable modifier, which returns the words, sorted in # descending order. @@ -6,7 +6,7 @@ NUMBERS= one two three four five six seven eight nine ten .if ${NUMBERS:Or} != "two three ten six seven one nine four five eight" -.error ${NUMBERS:Or} +. error ${NUMBERS:Or} .endif all: diff --git a/unit-tests/varmod-order-shuffle.mk b/unit-tests/varmod-order-shuffle.mk index b6eb6be4b1bc..4ad057e0a810 100644 --- a/unit-tests/varmod-order-shuffle.mk +++ b/unit-tests/varmod-order-shuffle.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-order-shuffle.mk,v 1.3 2020/08/16 20:43:01 rillig Exp $ +# $NetBSD: varmod-order-shuffle.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ # # Tests for the :Ox variable modifier, which returns the words of the # variable, shuffled. @@ -6,6 +6,8 @@ # As of 2020-08-16, make uses random(3) seeded by the current time in seconds. # This makes the random numbers completely predictable since there is no other # part of make that uses random numbers. +# +# Tags: probabilistic NUMBERS= one two three four five six seven eight nine ten @@ -19,20 +21,20 @@ NUMBERS= one two three four five six seven eight nine ten shuffled1:= ${NUMBERS:Ox} shuffled2:= ${NUMBERS:Ox} .if ${shuffled1} == ${shuffled2} -.error ${shuffled1} == ${shuffled2} +. error ${shuffled1} == ${shuffled2} .endif # Sorting the list before shuffling it has no effect. shuffled1:= ${NUMBERS:O:Ox} shuffled2:= ${NUMBERS:O:Ox} .if ${shuffled1} == ${shuffled2} -.error ${shuffled1} == ${shuffled2} +. error ${shuffled1} == ${shuffled2} .endif # Sorting after shuffling must produce the original numbers. sorted:= ${NUMBERS:Ox:O} .if ${sorted} != ${NUMBERS:O} -.error ${sorted} != ${NUMBERS:O} +. error ${sorted} != ${NUMBERS:O} .endif all: diff --git a/unit-tests/varmod-order.mk b/unit-tests/varmod-order.mk index b079bdc34217..675b6efec5e7 100644 --- a/unit-tests/varmod-order.mk +++ b/unit-tests/varmod-order.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-order.mk,v 1.4 2020/08/16 20:43:01 rillig Exp $ +# $NetBSD: varmod-order.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ # # Tests for the :O variable modifier, which returns the words, sorted in # ascending order. @@ -6,7 +6,7 @@ NUMBERS= one two three four five six seven eight nine ten .if ${NUMBERS:O} != "eight five four nine one seven six ten three two" -.error ${NUMBERS:O} +. error ${NUMBERS:O} .endif # Unknown modifier "OX" diff --git a/unit-tests/varmod-quote.mk b/unit-tests/varmod-quote.mk index adf736048e76..3ab86e6b2dd3 100644 --- a/unit-tests/varmod-quote.mk +++ b/unit-tests/varmod-quote.mk @@ -1,9 +1,21 @@ -# $NetBSD: varmod-quote.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varmod-quote.mk,v 1.3 2020/10/29 19:07:45 rillig Exp $ # # Tests for the :Q variable modifier, which quotes the variable value # to be used in a shell program. -# TODO: Implementation +# Any characters that might be interpreted by the shell are escaped. +# The set of escaped characters is the same, no matter which shell (sh, csh, +# ksh) is in effect. +.if ${:Ua b c:Q} != "a\\ b\\ c" +. error +.endif + +# The quote modifier only applies if the whole modifier name is "Q". +# There is no "Qshell" or "Qawk" or "Qregex" or even "Qhtml" variant. +# All strings except the plain "Q" are interpreted as SysV modifier. +.if ${:Ua.Qshell:Qshell=replaced} != "a.replaced" +. error +.endif all: @:; diff --git a/unit-tests/varmod-range.exp b/unit-tests/varmod-range.exp index bdbd68edeb45..eeeceb72b83f 100644 --- a/unit-tests/varmod-range.exp +++ b/unit-tests/varmod-range.exp @@ -1,8 +1,14 @@ -make: Unknown modifier 'r' +make: "varmod-range.mk" line 53: Invalid number: x}Rest" != "Rest" -1 2 3 +make: "varmod-range.mk" line 53: Malformed conditional ("${:U:range=x}Rest" != "Rest") +make: Unknown modifier 'x' +make: "varmod-range.mk" line 62: Malformed conditional ("${:U:range=0x0}Rest" != "Rest") make: Unknown modifier 'r' - +make: "varmod-range.mk" line 78: Malformed conditional ("${a b c:L:rang}Rest" != "Rest") make: Unknown modifier 'r' - -exit status 0 +make: "varmod-range.mk" line 85: Malformed conditional ("${a b c:L:rango}Rest" != "Rest") +make: Unknown modifier 'r' +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/unit-tests/varmod-range.mk b/unit-tests/varmod-range.mk index 9fd79cc97a81..d63525248e58 100644 --- a/unit-tests/varmod-range.mk +++ b/unit-tests/varmod-range.mk @@ -1,10 +1,98 @@ -# $NetBSD: varmod-range.mk,v 1.3 2020/08/23 15:13:21 rillig Exp $ +# $NetBSD: varmod-range.mk,v 1.7 2020/11/01 14:36:25 rillig Exp $ # # Tests for the :range variable modifier, which generates sequences # of integers from the given range. +# +# See also: +# modword.mk + +# The :range modifier generates a sequence of integers, one number per +# word of the variable expression's value. +.if ${a b c:L:range} != "1 2 3" +. error +.endif + +# To preserve spaces in a word, they can be enclosed in quotes, just like +# everywhere else. +.if ${:U first "the second word" third 4 :range} != "1 2 3 4" +. error +.endif + +# The :range modifier takes the number of words from the value of the +# variable expression. If that expression is undefined, the range is +# undefined as well. This should not come as a surprise. +.if "${:range}" != "" +. error +.endif + +# The :range modifier can be given a parameter, which makes the generated +# range independent from the value or the name of the variable expression. +# +# XXX: As of 2020-09-27, the :range=... modifier does not turn the undefined +# expression into a defined one. This looks like an oversight. +.if "${:range=5}" != "" +. error +.endif + +# Negative ranges don't make sense. +# As of 2020-11-01, they are accepted though, using up all available memory. +#.if "${:range=-1}" +#. error +#.else +#. error +#.endif + +# The :range modifier requires a number as parameter. +# +# Until 2020-11-01, the parser tried to read the 'x' as a number, failed and +# stopped there. It then tried to parse the next modifier at that point, +# which failed with the message "Unknown modifier". +# +# Since 2020-11-01, the parser issues a more precise "Invalid number" error +# instead. +.if "${:U:range=x}Rest" != "Rest" +. error +.else +. error +.endif + +# The upper limit of the range must always be given in decimal. +# This parse error stops at the 'x', trying to parse it as a variable +# modifier. +.if "${:U:range=0x0}Rest" != "Rest" +. error +.else +. error +.endif + +# As of 2020-11-01, numeric overflow is not detected. +# Since strtoul returns ULONG_MAX in such a case, it is interpreted as a +# very large number, consuming all available memory. +#.if "${:U:range=18446744073709551619}Rest" != "Rest" +#. error +#.else +#. error +#.endif + +# modifier name too short +.if "${a b c:L:rang}Rest" != "Rest" +. error +.else +. error +.endif + +# misspelled modifier name +.if "${a b c:L:rango}Rest" != "Rest" +. error +.else +. error +.endif + +# modifier name too long +.if "${a b c:L:ranger}Rest" != "Rest" +. error +.else +. error +.endif all: - @echo ${a b c:L:rang} # modifier name too short - @echo ${a b c:L:range} # ok - @echo ${a b c:L:rango} # misspelled - @echo ${a b c:L:ranger} # modifier name too long diff --git a/unit-tests/varmod-subst-regex.mk b/unit-tests/varmod-subst-regex.mk index fe2758e401ec..f558ae1134e8 100644 --- a/unit-tests/varmod-subst-regex.mk +++ b/unit-tests/varmod-subst-regex.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-subst-regex.mk,v 1.3 2020/08/28 17:15:04 rillig Exp $ +# $NetBSD: varmod-subst-regex.mk,v 1.5 2020/10/31 12:20:36 rillig Exp $ # # Tests for the :C,from,to, variable modifier. @@ -10,26 +10,26 @@ all: mod-regex-errors # 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 +. 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 +. 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 +. 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 +. error .endif # The first :C modifier has the 'W' modifier, which makes the whole @@ -39,7 +39,7 @@ all: mod-regex-errors # '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 +. error .endif # The regular expression does not match in the first word. @@ -48,19 +48,36 @@ all: mod-regex-errors # 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 result. +# "45" is doubled in the third word. .if ${:U1 23 456:C,..,\0\0,} != "1 2323 45456" -.error +. error .endif # The modifier '1' applies the replacement at most once, across the whole -# variable value, no matter whether it is a single big word or many small +# 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. +# 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 +. 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 diff --git a/unit-tests/varmod-subst.mk b/unit-tests/varmod-subst.mk index de8d54df8506..5fc657ea10ee 100644 --- a/unit-tests/varmod-subst.mk +++ b/unit-tests/varmod-subst.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-subst.mk,v 1.3 2020/08/19 06:10:06 rillig Exp $ +# $NetBSD: varmod-subst.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for the :S,from,to, variable modifier. @@ -9,56 +9,56 @@ all: mod-subst-dollar WORDS= sequences of letters .if ${WORDS:S,,,} != ${WORDS} -.warning The empty pattern matches something. +. 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. +. 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,\ +. 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. +. 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. +. 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. +. 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. +. 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. +. 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. +. 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. +. 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. +. 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. +. 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. +. 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. +. 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. +. 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. +. 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. +. warning The :S modifier matches a too long suffix anchored at both ends. .endif mod-subst: diff --git a/unit-tests/varmod-sysv.exp b/unit-tests/varmod-sysv.exp index d9049c889823..301519ecc747 100644 --- a/unit-tests/varmod-sysv.exp +++ b/unit-tests/varmod-sysv.exp @@ -1,8 +1,5 @@ -ax:Q b c d eb -bcd.e -& -anchor-dollar: value -anchor-dollar: valux -mismatch: file.cpp file.h -mismatch: renamed.c other.c -exit status 0 +make: Unfinished modifier for word203 ('=' missing) +make: "varmod-sysv.mk" line 210: Malformed conditional (${word203:L:from${:D=}to}) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/varmod-sysv.mk b/unit-tests/varmod-sysv.mk index c8410ed900d8..10643495fef5 100644 --- a/unit-tests/varmod-sysv.mk +++ b/unit-tests/varmod-sysv.mk @@ -1,61 +1,237 @@ -# $NetBSD: varmod-sysv.mk,v 1.3 2020/08/23 14:52:06 rillig Exp $ +# $NetBSD: varmod-sysv.mk,v 1.11 2020/11/01 22:28:52 rillig Exp $ # # Tests for the ${VAR:from=to} variable modifier, 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. -all: words ampersand anchor-dollar mismatch +# A typical use case for the :from=to modifier is conversion of filename +# extensions. +.if ${src.c:L:.c=.o} != "src.o" +. error +.endif -# The :Q looks like a modifier but isn't. -# It is part of the replacement string. -words: - @echo a${a b c d e:L:%a=x:Q}b +# 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. +.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 +# word. In this case, the "from" string is empty, therefore it always +# matches. The same effect can be achieved with the :S,$,teen, modifier. +.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. +# 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. +.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. +.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. +.if ${one two:L:${:Uone}=${:U1}} != "1 two" +. error +.endif + +# In the :from=to modifier, 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. +# 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 +# no word at all. +.if ${:L:=suffix} != "" +. 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 +# no word at all. +.if ${:L:%=suffix} != "" +. error +.endif # Before 2020-07-19, an ampersand could be used in the replacement part -# of a SysV substitution modifier. This was probably a copy-and-paste -# mistake since the SysV modifier code looked a lot like the code for the -# :S and :C modifiers. The ampersand is not mentioned in the manual page. -ampersand: - @echo ${:U${a.bcd.e:L:a.%=%}:Q} - @echo ${:U${a.bcd.e:L:a.%=&}:Q} +# of a SysV substitution modifier, and it was replaced with the whole match, +# just like in the :S modifier. +# +# 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. +# 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 interpreted as an anchor, which doesn't make sense -# since the anchor was discarded immediately. -anchor-dollar: - @echo $@: ${:U${value:L:e$=x}:Q} - @echo $@: ${:U${value:L:e=x}:Q} +# before the '=' was parsed (but not interpreted) as an anchor. +# Parsing something without then evaluating it accordingly doesn't make +# sense. +.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 +# interpreted) as 'anchor at the end'. Therefore the modifier was equivalent +# 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. -# The % placeholder can be anywhere in the string. -mismatch: - @echo $@: ${:Ufile.c file.h:%.c=%.cpp} - @echo $@: ${:Ufile.c other.c:file.%=renamed.%} - -# Trying to cover all possible variants of the SysV modifier. -LIST= one two -EXPR.1= ${LIST:o=X} -EXP.1= one twX -EXPR.2= ${LIST:o=} -EXP.2= one tw -EXPR.3= ${LIST:o=%} -EXP.3= one tw% -EXPR.4= ${LIST:%o=X} -EXP.4= one X -EXPR.5= ${LIST:o%=X} -EXP.5= X two -EXPR.6= ${LIST:o%e=X} -EXP.6= X two -EXPR.7= ${LIST:o%%e=X} # Only the first '%' is the wildcard. -EXP.7= one two # None of the words contains a literal '%'. -EXPR.8= ${LIST:%=%%} -EXP.8= one% two% -EXPR.9= ${LIST:%nes=%xxx} # lhs is longer than the word "one" -EXP.9= one two - -.for i in ${:U:range=9} -.if ${EXPR.$i} != ${EXP.$i} -.warning test case $i expected "${EXP.$i}", got "${EXPR.$i} -.endif -.endfor +.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 +# 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. +# As of 2020-11-01, this seemingly correct modifier leads to a parse error. +# XXX +.if ${word203: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 + +all: diff --git a/unit-tests/varmod-to-lower.mk b/unit-tests/varmod-to-lower.mk index 38a4add4cfe5..6ab4af740fae 100644 --- a/unit-tests/varmod-to-lower.mk +++ b/unit-tests/varmod-to-lower.mk @@ -1,18 +1,18 @@ -# $NetBSD: varmod-to-lower.mk,v 1.3 2020/08/28 17:21:02 rillig Exp $ +# $NetBSD: varmod-to-lower.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for the :tl variable modifier, which returns the words in the # variable value, converted to lowercase. .if ${:UUPPER:tl} != "upper" -.error +. error .endif .if ${:Ulower:tl} != "lower" -.error +. error .endif .if ${:UMixeD case.:tl} != "mixed case." -.error +. error .endif all: diff --git a/unit-tests/varmod-to-separator.exp b/unit-tests/varmod-to-separator.exp index 07dd5b5e7c5b..a3c323ac123a 100644 --- a/unit-tests/varmod-to-separator.exp +++ b/unit-tests/varmod-to-separator.exp @@ -1,9 +1,21 @@ +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: "varmod-to-separator.mk" line 128: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) +make: Bad modifier `:ts\8' for 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: "varmod-to-separator.mk" line 143: Malformed conditional (${1 2 3:L:ts\100L}) +make: Bad modifier `:ts\x40g' for 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: "varmod-to-separator.mk" line 104: Malformed conditional (${WORDS:tx} != "anything") -make: "varmod-to-separator.mk" line 108: Parsing continues here. +make: "varmod-to-separator.mk" line 158: Malformed conditional (${WORDS:tx} != "anything") make: Bad modifier `:t\X' for WORDS -make: "varmod-to-separator.mk" line 112: Malformed conditional (${WORDS:t\X} != "anything") -make: "varmod-to-separator.mk" line 115: Parsing continues here. +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/unit-tests/varmod-to-separator.mk b/unit-tests/varmod-to-separator.mk index 68e728c276dd..89aa3d978bee 100644 --- a/unit-tests/varmod-to-separator.mk +++ b/unit-tests/varmod-to-separator.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-to-separator.mk,v 1.3 2020/08/31 19:58:21 rillig Exp $ +# $NetBSD: varmod-to-separator.mk,v 1.6 2020/11/01 14:36:25 rillig Exp $ # # Tests for the :ts variable modifier, which joins the words of the variable # using an arbitrary character as word separator. @@ -95,24 +95,75 @@ WORDS= one two three four five six . warning The separator \012 is not interpreted in octal ASCII. .endif +# The octal number can have as many digits as it wants. +.if ${WORDS:[1..2]:ts\000000000000000000000000012:tu} != "ONE${.newline}TWO" +. warning The separator \012 cannot have many leading zeroes. +.endif + +# The value of the separator character must not be outside the value space +# for an unsigned character though. +# +# Since 2020-11-01, these out-of-bounds values are rejected. +.if ${WORDS:[1..3]:ts\400:tu} +. warning The separator \400 is accepted even though it is out of bounds. +.else +. warning The separator \400 is accepted even though it is out of bounds. +.endif + # The separator can be given as hexadecimal number. .if ${WORDS:[1..3]:ts\xa:tu} != "ONE${.newline}TWO${.newline}THREE" . warning The separator \xa is not interpreted in hexadecimal ASCII. .endif +# The hexadecimal number must be in the range of an unsigned char. +# +# Since 2020-11-01, these out-of-bounds values are rejected. +.if ${WORDS:[1..3]:ts\x100:tu} +. warning The separator \x100 is accepted even though it is out of bounds. +.else +. warning The separator \x100 is accepted even though it is out of bounds. +.endif + +# Negative numbers are not allowed for the separator character. +.if ${WORDS:[1..3]:ts\-300:tu} +. warning The separator \-300 is accepted even though it is negative. +.else +. warning The separator \-300 is accepted even though it is negative. +.endif + +# The character number is interpreted as octal number by default. +# The digit '8' is not an octal digit though. +.if ${1 2 3:L:ts\8:tu} +. warning The separator \8 is accepted even though it is not octal. +.else +. warning The separator \8 is accepted even though it is not octal. +.endif + +# Trailing characters after the octal character number are rejected. +.if ${1 2 3:L:ts\100L} +. warning The separator \100L is accepted even though it contains an 'L'. +.else +. warning The separator \100L is accepted even though it contains an 'L'. +.endif + +# Trailing characters after the hexadecimal character number are rejected. +.if ${1 2 3:L:ts\x40g} +. warning The separator \x40g is accepted even though it contains a 'g'. +.else +. warning The separator \x40g is accepted even though it contains a 'g'. +.endif + + # In the :t modifier, the :t must be followed by any of A, l, s, u. .if ${WORDS:tx} != "anything" . info This line is not reached because of the malformed condition. . info If this line were reached, it would be visible in the -dcpv log. .endif -.info Parsing continues here. # After the backslash, only n, t, an octal number, or x and a hexadecimal # number are allowed. .if ${WORDS:t\X} != "anything" . info This line is not reached. .endif -.info Parsing continues here. all: - @:; diff --git a/unit-tests/varmod-to-upper.mk b/unit-tests/varmod-to-upper.mk index 26c0b25e5142..1417a5b5d83a 100644 --- a/unit-tests/varmod-to-upper.mk +++ b/unit-tests/varmod-to-upper.mk @@ -1,18 +1,18 @@ -# $NetBSD: varmod-to-upper.mk,v 1.4 2020/08/28 17:21:02 rillig Exp $ +# $NetBSD: varmod-to-upper.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ # # Tests for the :tu variable modifier, which returns the words in the # variable value, converted to uppercase. .if ${:UUPPER:tu} != "UPPER" -.error +. error .endif .if ${:Ulower:tu} != "LOWER" -.error +. error .endif .if ${:UMixeD case.:tu} != "MIXED CASE." -.error +. error .endif # The :tu and :tl modifiers operate on the variable value as a single string, diff --git a/unit-tests/varmod-undefined.mk b/unit-tests/varmod-undefined.mk index 9ed35a8406fe..1beaa763716b 100644 --- a/unit-tests/varmod-undefined.mk +++ b/unit-tests/varmod-undefined.mk @@ -1,26 +1,28 @@ -# $NetBSD: varmod-undefined.mk,v 1.3 2020/08/23 20:49:33 rillig Exp $ +# $NetBSD: varmod-undefined.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $ # # Tests for the :U variable modifier, which returns the given string # if the variable is undefined. # -# The pattern ${:Uword} is heavily used when expanding .for loops. +# See also: +# varmod-defined.mk +# The pattern ${:Uword} is heavily used when expanding .for loops. +# # This is how an expanded .for loop looks like. # .for word in one # . if ${word} != one -# . error ${word} -# . endif -# .endfor - .if ${:Uone} != one +# . error ${word} . error ${:Uone} +# . endif .endif +# .endfor # The variable expressions in the text of the :U modifier may be arbitrarily # nested. .if ${:U${:Unested}${${${:Udeeply}}}} != nested -.error +. error .endif # The nested variable expressions may contain braces, and these braces don't @@ -30,7 +32,7 @@ # For more similar examples, see varmod-subst.mk, mod-subst-delimiter. .if ${:U${:Uvalue:S{a{X{}} != vXlue -.error +. error .endif # The escaping rules for the :U modifier (left-hand side) and condition @@ -46,9 +48,19 @@ # the .newline variable is for. # # Whitespace at the edges is preserved, on both sides of the comparison. - +# .if ${:U \: \} \$ \\ \a \b \n } != " : } \$ \\ \\a \\b \\n " -.error +. error +.endif + +# Even after the :U modifier has been applied, the expression still remembers +# that it originated from an undefined variable, and the :U modifier can +# be used to overwrite the value of the expression. +# +.if ${UNDEF:Uvalue:S,a,X,} != "vXlue" +. error +.elif ${UNDEF:Uvalue:S,a,X,:Uwas undefined} != "was undefined" +. error .endif all: diff --git a/unit-tests/varmod.exp b/unit-tests/varmod.exp index 39a9383953dd..e4257bb0b596 100644 --- a/unit-tests/varmod.exp +++ b/unit-tests/varmod.exp @@ -1 +1,6 @@ -exit status 0 +make: "varmod.mk" line 42: To escape a dollar, use \$, not $$, at "$$:L} != """ +make: "varmod.mk" line 42: Invalid variable name ':', at "$:L} != """ +make: "varmod.mk" line 47: Dollar followed by nothing +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/varmod.mk b/unit-tests/varmod.mk index 68bf165bf72b..0a2a4c52c29b 100644 --- a/unit-tests/varmod.mk +++ b/unit-tests/varmod.mk @@ -1,8 +1,51 @@ -# $NetBSD: varmod.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varmod.mk,v 1.3 2020/09/13 07:42:20 rillig Exp $ # # Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback. -# TODO: Implementation +DOLLAR1= $$ +DOLLAR2= ${:U\$} -all: - @:; +# To get a single '$' sign in the value of a variable expression, it has to +# be written as '$$' in a literal variable value. +# +# See Var_Parse, where it calls Var_Subst. +.if ${DOLLAR1} != "\$" +. error +.endif + +# Another way to get a single '$' sign is to use the :U modifier. In the +# argument of that modifier, a '$' is escaped using the backslash instead. +# +# See Var_Parse, where it calls Var_Subst. +.if ${DOLLAR2} != "\$" +. error +.endif + +# It is also possible to use the :U modifier directly in the expression. +# +# See Var_Parse, where it calls Var_Subst. +.if ${:U\$} != "\$" +. error +.endif + +# XXX: As of 2020-09-13, it is not possible to use '$$' in a variable name +# to mean a single '$'. This contradicts the manual page, which says that +# '$' can be escaped as '$$'. +.if ${$$:L} != "" +. error +.endif + +# In lint mode, make prints helpful error messages. +# For compatibility, make does not print these error messages in normal mode. +# Should it? +.MAKEFLAGS: -dL +.if ${$$:L} != "" +. error +.endif + +# A '$' followed by nothing is an error as well. +.if ${:Uword:@word@${word}$@} != "word" +. error +.endif + +all: # nothing diff --git a/unit-tests/varname-dot-curdir.mk b/unit-tests/varname-dot-curdir.mk index 3795d87b030f..e7af5c952844 100644 --- a/unit-tests/varname-dot-curdir.mk +++ b/unit-tests/varname-dot-curdir.mk @@ -1,8 +1,45 @@ -# $NetBSD: varname-dot-curdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-dot-curdir.mk,v 1.7 2020/10/08 19:09:08 rillig Exp $ # -# Tests for the special .CURDIR variable. +# Tests for the special .CURDIR variable, which is initially set to the +# canonical path of the current working directory, when make started. -# TODO: Implementation +# In all normal situations, the current directory exists, and its name can +# be resolved. If not, make fails at startup. +# +# It would be possible on some systems to remove the current directory, even +# while a process runs in it, but this is so unrealistic that it's no worth +# testing. +.if !exists(${.CURDIR}) +. error +.endif +.if !exists(${.CURDIR}/) +. error +.endif +.if !exists(${.CURDIR}/.) +. error +.endif +.if !exists(${.CURDIR}/..) +. error +.endif + +# Until 2020-10-04, assigning the result of a shell assignment to .CURDIR +# tried to add the shell command ("echo /") to the .PATH instead of the +# output of the shell command ("/"). Since "echo /" does not exist, the +# .PATH was left unmodified. See VarAssign_Eval. +# +# Since 2020-10-04, the output of the shell command is added to .PATH. +.CURDIR!= echo / +.if ${.PATH:M/} != "/" +. error +.endif + +# A normal assignment works fine, as does a substitution assignment. +# Appending to .CURDIR does not make sense, therefore it doesn't matter that +# this code path is buggy as well. +.CURDIR= / +.if ${.PATH:M/} != "/" +. error +.endif all: @:; diff --git a/unit-tests/varname-dot-includes.mk b/unit-tests/varname-dot-includes.mk index 33e8669c27be..0193e8eb6f43 100755 --- a/unit-tests/varname-dot-includes.mk +++ b/unit-tests/varname-dot-includes.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname-dot-includes.mk,v 1.1 2020/08/28 03:51:06 rillig Exp $ +# $NetBSD: varname-dot-includes.mk,v 1.2 2020/09/13 16:53:19 rillig Exp $ # # Tests for the special .INCLUDES variable, which is not documented in the # manual page. @@ -12,8 +12,8 @@ .INCLUDES: .h # The .INCLUDES variable is not yet available. -.if defined(${.INCLUDES:Q}) -.error +.if defined(.INCLUDES) +. error .endif all: diff --git a/unit-tests/varname-dot-libs.mk b/unit-tests/varname-dot-libs.mk index 80abeb50b48c..99d28be71e43 100755 --- a/unit-tests/varname-dot-libs.mk +++ b/unit-tests/varname-dot-libs.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname-dot-libs.mk,v 1.1 2020/08/28 03:51:06 rillig Exp $ +# $NetBSD: varname-dot-libs.mk,v 1.2 2020/09/13 16:53:19 rillig Exp $ # # Tests for the special .LIBS variable, which is not documented in the # manual page. @@ -12,8 +12,8 @@ .LIBS: .a # The .LIBS variable is not yet available. -.if defined(${.LIBS:Q}) -.error +.if defined(.LIBS) +. error .endif all: diff --git a/unit-tests/varname-dot-newline.mk b/unit-tests/varname-dot-newline.mk index fa9ac0759649..0565d244f298 100644 --- a/unit-tests/varname-dot-newline.mk +++ b/unit-tests/varname-dot-newline.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname-dot-newline.mk,v 1.3 2020/08/19 05:51:18 rillig Exp $ +# $NetBSD: varname-dot-newline.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # # Tests for the special .newline variable. # @@ -11,9 +11,9 @@ NEWLINE:= ${.newline} .newline= overwritten .if ${.newline} == ${NEWLINE} -.info The .newline variable cannot be overwritten. Good. +. info The .newline variable cannot be overwritten. Good. .else -.info The .newline variable can be overwritten. Just don't do that. +. info The .newline variable can be overwritten. Just don't do that. .endif # Restore the original value. diff --git a/unit-tests/varname-dot-parsedir.exp b/unit-tests/varname-dot-parsedir.exp index 39a9383953dd..c0bc56f41d6e 100644 --- a/unit-tests/varname-dot-parsedir.exp +++ b/unit-tests/varname-dot-parsedir.exp @@ -1 +1,5 @@ +make: "varname-dot-parsedir.mk" line 28: At this point, .PARSEDIR is undefined. +make: "<normalized>" line 34: The location can be faked in some cases. +make: "varname-dot-parsedir.mk" line 38: The location is no longer fake. +At run time, .PARSEDIR is undefined. exit status 0 diff --git a/unit-tests/varname-dot-parsedir.mk b/unit-tests/varname-dot-parsedir.mk index 6aeb5878a457..7c74419ddd10 100644 --- a/unit-tests/varname-dot-parsedir.mk +++ b/unit-tests/varname-dot-parsedir.mk @@ -1,8 +1,41 @@ -# $NetBSD: varname-dot-parsedir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-dot-parsedir.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $ # -# Tests for the special .PARSEDIR variable. +# Tests for the special .PARSEDIR variable, which contains the directory part +# of the file that is currently parsed. -# TODO: Implementation +# The .PARSEDIR may be absolute or relative, therefore there is not much that +# can be tested here. +.if !${.PARSEDIR:tA:M*/unit-tests} +. error +.endif + +# During parsing, it is possible to undefine .PARSEDIR. +# Not that anyone would ever want to do this, but there's code in parse.c, +# function PrintLocation, that explicitly handles this situation. +.if !defined(.PARSEDIR) +. error +.endif +.undef .PARSEDIR +.if defined(.PARSEDIR) +. error +.endif + +# The variable .PARSEDIR is indirectly used by the .info directive, +# via PrintLocation. +# +# The .rawout file contains the full path to the current directory. +# In the .out file, it is filtered out. +.info At this point, .PARSEDIR is undefined. + +# There is absolutely no point in faking the location of the file that is +# being parsed. Technically, it's possible though, but only if the file +# being parsed is a relative pathname. See PrintLocation for details. +.PARSEDIR= /fake-absolute-path +.info The location can be faked in some cases. + +# After including another file, .PARSEDIR is reset. +.include "/dev/null" +.info The location is no longer fake. all: - @:; + @echo At run time, .PARSEDIR is ${.PARSEDIR:Uundefined}. diff --git a/unit-tests/varname-dot-parsefile.exp b/unit-tests/varname-dot-parsefile.exp index 39a9383953dd..b61f01c01ab7 100644 --- a/unit-tests/varname-dot-parsefile.exp +++ b/unit-tests/varname-dot-parsefile.exp @@ -1 +1,5 @@ +make: "varname-dot-parsefile.mk" line 23: At this point, .PARSEFILE is undefined. +make: "<normalized>" line 29: The location can be faked in some cases. +make: "varname-dot-parsefile.mk" line 33: The location is no longer fake. +At run time, .PARSEFILE is undefined. exit status 0 diff --git a/unit-tests/varname-dot-parsefile.mk b/unit-tests/varname-dot-parsefile.mk index 9deff34a70d0..17b48a5f77ec 100644 --- a/unit-tests/varname-dot-parsefile.mk +++ b/unit-tests/varname-dot-parsefile.mk @@ -1,8 +1,36 @@ -# $NetBSD: varname-dot-parsefile.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-dot-parsefile.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $ # -# Tests for the special .PARSEFILE variable. +# Tests for the special .PARSEFILE variable, which contains the basename part +# of the file that is currently parsed. -# TODO: Implementation +.if ${.PARSEFILE} != "varname-dot-parsefile.mk" +. error +.endif + +# During parsing, it is possible to undefine .PARSEFILE. +# Not that anyone would ever want to do this, but there's code in parse.c, +# function PrintLocation, that explicitly handles this situation. +.if !defined(.PARSEFILE) +. error +.endif +.undef .PARSEFILE +.if defined(.PARSEFILE) +. error +.endif + +# The variable .PARSEFILE is indirectly used by the .info directive, +# via PrintLocation. +.info At this point, .PARSEFILE is undefined. + +# There is absolutely no point in faking the location of the file that is +# being parsed. Technically, it's possible though, but only if the file +# being parsed is a relative pathname. See PrintLocation for details. +.PARSEFILE= fake-parsefile +.info The location can be faked in some cases. + +# After including another file, .PARSEFILE is reset. +.include "/dev/null" +.info The location is no longer fake. all: - @:; + @echo At run time, .PARSEFILE is ${.PARSEFILE:Uundefined}. diff --git a/unit-tests/varname-dot-path.mk b/unit-tests/varname-dot-path.mk index 8f4e3a5dcfa4..85a6f264eb5a 100644 --- a/unit-tests/varname-dot-path.mk +++ b/unit-tests/varname-dot-path.mk @@ -1,8 +1,56 @@ -# $NetBSD: varname-dot-path.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-dot-path.mk,v 1.3 2020/10/02 18:46:54 rillig Exp $ # -# Tests for the special .PATH variable. +# Tests for the special .PATH variable, which TODO: describe the purpose. -# TODO: Implementation +_!= mkdir -p varname-dot-path.d + +# By default, .PATH consists of "." and .CURDIR. +# XXX: Why both? Shouldn't they have the same effect? +.if ${.PATH} != ". ${.CURDIR}" +. error ${.PATH} +.endif + +# The special target .PATH adds a directory to the path. +.PATH: / +.if ${.PATH} != ". ${.CURDIR} /" +. error ${.PATH} +.endif + +# Only existing directories are added to the path, the others are ignored. +.PATH: /nonexistent +.if ${.PATH} != ". ${.CURDIR} /" +. error ${.PATH} +.endif + +# Only directories are added to the path, not regular files. +.PATH: ${.PARSEDIR}/${.PARSEFILE} +.if ${.PATH} != ". ${.CURDIR} /" +. error ${.PATH} +.endif + +# Relative directories can be added as well. +# Each directory is only added once to the path. +.PATH: varname-dot-path.d / +.if ${.PATH} != ". ${.CURDIR} / varname-dot-path.d" +. error ${.PATH} +.endif + +# The pathnames are not normalized before being added to the path. +.PATH: ./. +.if ${.PATH} != ". ${.CURDIR} / varname-dot-path.d ./." +. error ${.PATH} +.endif + +# The two default entries can be placed at the back of the path, +# by adding the special entry ".DOTLAST" somewhere in the path. +# The entry .DOTLAST, if any, is listed in the path, always at the +# very beginning, to make this magic less surprising. +.PATH: .DOTLAST +.if ${.PATH} != ".DOTLAST / varname-dot-path.d ./. . ${.CURDIR}" +. error ${.PATH} +.endif + +_!= rmdir varname-dot-path.d all: @:; diff --git a/unit-tests/varname-dot-shell.exp b/unit-tests/varname-dot-shell.exp index af4baaf44c0b..704d4863d4e9 100755 --- a/unit-tests/varname-dot-shell.exp +++ b/unit-tests/varname-dot-shell.exp @@ -1,19 +1,32 @@ -ParseReadLine (8): 'ORIG_SHELL:= ${.SHELL}' +ParseReadLine (10): 'ORIG_SHELL:= ${.SHELL}' +Global:ORIG_SHELL = Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_ASSIGN Global:delete .SHELL (not found) Command:.SHELL = (details omitted) -ParseReadLine (10): '.SHELL= overwritten' +Global:ORIG_SHELL = (details omitted) +ParseReadLine (12): '.SHELL= overwritten' Global:.SHELL = overwritten +CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES -ParseReadLine (18): '.undef .SHELL' +Var_Parse: ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES +lhs = "(details omitted)", rhs = "(details omitted)", op = != +ParseReadLine (19): '.MAKEFLAGS: .SHELL+=appended' +ParseDoDependency(.MAKEFLAGS: .SHELL+=appended) +Ignoring append to .SHELL since it is read-only +CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} +Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES +Var_Parse: ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES +lhs = "(details omitted)", rhs = "(details omitted)", op = != +ParseReadLine (27): '.undef .SHELL' Global:delete .SHELL -ParseReadLine (19): '.SHELL= newly overwritten' +ParseReadLine (28): '.SHELL= newly overwritten' Global:.SHELL = newly overwritten +CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES -ParseReadLine (24): 'all:' -ParseReadLine (25): ' @echo ${.SHELL:M*}' -Var_Parse: ${.SHELL:M*} with VARE_WANTRES -Applying ${.SHELL:M...} to "(details omitted)" (eflags = VARE_WANTRES, vflags = VAR_READONLY) -Pattern[.SHELL] for [(details omitted)] is [*] -Result of ${.SHELL:M*} is "(details omitted)" (eflags = VARE_WANTRES, vflags = VAR_READONLY) +Var_Parse: ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES +lhs = "(details omitted)", rhs = "(details omitted)", op = != +ParseReadLine (33): '.MAKEFLAGS: -d0' +ParseDoDependency(.MAKEFLAGS: -d0) +Global:.MAKEFLAGS = -r -k -d cpv -d +Global:.MAKEFLAGS = -r -k -d cpv -d 0 exit status 0 diff --git a/unit-tests/varname-dot-shell.mk b/unit-tests/varname-dot-shell.mk index 4a2c52b39cbc..2e67b12aefff 100755 --- a/unit-tests/varname-dot-shell.mk +++ b/unit-tests/varname-dot-shell.mk @@ -1,15 +1,24 @@ -# $NetBSD: varname-dot-shell.mk,v 1.2 2020/08/23 09:28:52 rillig Exp $ +# $NetBSD: varname-dot-shell.mk,v 1.6 2020/10/30 16:09:56 rillig Exp $ # # Tests for the special .SHELL variable, which contains the shell used for # running the commands. # # This variable is read-only. +.MAKEFLAGS: -dcpv + ORIG_SHELL:= ${.SHELL} .SHELL= overwritten .if ${.SHELL} != ${ORIG_SHELL} -.error +. error +.endif + +# Trying to append to the variable. +# Since 2020-10-30 this is prevented. +.MAKEFLAGS: .SHELL+=appended +.if ${.SHELL} != ${ORIG_SHELL} +. error .endif # Trying to delete the variable. @@ -18,8 +27,10 @@ ORIG_SHELL:= ${.SHELL} .undef .SHELL .SHELL= newly overwritten .if ${.SHELL} != ${ORIG_SHELL} -.error +. error .endif +.MAKEFLAGS: -d0 + all: - @echo ${.SHELL:M*} + @:; diff --git a/unit-tests/varname-empty.exp b/unit-tests/varname-empty.exp index 77cb7c517e9d..ba465cc3eff2 100644 --- a/unit-tests/varname-empty.exp +++ b/unit-tests/varname-empty.exp @@ -6,6 +6,8 @@ Var_Set("", "appended", ...) name expands to empty string - ignored Var_Set("", "", ...) name expands to empty string - ignored Var_Set("", "subst", ...) name expands to empty string - ignored Var_Set("", "shell-output", ...) name expands to empty string - ignored +Var_Set("${:U}", "assigned indirectly", ...) name expands to empty string - ignored +Var_Set("", "assigned", ...) name expands to empty string - ignored out: fallback out: 1 2 3 exit status 0 diff --git a/unit-tests/varname-empty.mk b/unit-tests/varname-empty.mk index b4ce05c3017b..fd9aa05b6057 100755 --- a/unit-tests/varname-empty.mk +++ b/unit-tests/varname-empty.mk @@ -1,18 +1,54 @@ -# $NetBSD: varname-empty.mk,v 1.5 2020/08/22 21:22:24 rillig Exp $ +# $NetBSD: varname-empty.mk,v 1.7 2020/10/23 17:53:01 rillig Exp $ # # Tests for the special variable with the empty name. # -# The variable "" is not supposed to be assigned any value. -# This is because it is heavily used in the .for loop expansion, -# as well as to generate arbitrary strings, as in ${:Ufallback}. +# 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. +# 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 + +# 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 +# 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 diff --git a/unit-tests/varname-make_print_var_on_error-jobs.exp b/unit-tests/varname-make_print_var_on_error-jobs.exp new file mode 100644 index 000000000000..057c09dcbcf6 --- /dev/null +++ b/unit-tests/varname-make_print_var_on_error-jobs.exp @@ -0,0 +1,7 @@ +fail +*** [all] Error code 1 + +make: stopped in unit-tests +.ERROR_TARGET='all' +.ERROR_CMD='@: command before @echo fail; false @: command after, with variable expressions expanded' +exit status 1 diff --git a/unit-tests/varname-make_print_var_on_error-jobs.mk b/unit-tests/varname-make_print_var_on_error-jobs.mk new file mode 100644 index 000000000000..7e611d092f23 --- /dev/null +++ b/unit-tests/varname-make_print_var_on_error-jobs.mk @@ -0,0 +1,21 @@ +# $NetBSD: varname-make_print_var_on_error-jobs.mk,v 1.1 2020/10/23 06:18:23 rillig Exp $ +# +# Tests for the special MAKE_PRINT_VAR_ON_ERROR variable, which prints the +# values of selected variables on error. +# +# The variable .ERROR_CMD contains all commands of the target, with variable +# expressions expanded, just as they were printed to the shell command file. +# +# The commands in .ERROR_CMD are space-separated. Since each command usually +# contains spaces as well, this value is only intended as a first hint to what +# happened. For more details, use the debug options -de, -dj, -dl, -dn, -dx. + +# As of 2020-10-23, .ERROR_CMD only works in parallel mode. +.MAKEFLAGS: -j1 + +MAKE_PRINT_VAR_ON_ERROR= .ERROR_TARGET .ERROR_CMD + +all: + @: command before + @echo fail; false + @: command after${:U, with variable expressions expanded} diff --git a/unit-tests/varname-make_print_var_on_error.exp b/unit-tests/varname-make_print_var_on_error.exp index 39a9383953dd..e2f6a429a9fc 100644 --- a/unit-tests/varname-make_print_var_on_error.exp +++ b/unit-tests/varname-make_print_var_on_error.exp @@ -1 +1,8 @@ -exit status 0 +fail +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +.ERROR_TARGET='all' +.ERROR_CMD='' +exit status 1 diff --git a/unit-tests/varname-make_print_var_on_error.mk b/unit-tests/varname-make_print_var_on_error.mk index 757d55780be1..9ea78cb2cb4a 100644 --- a/unit-tests/varname-make_print_var_on_error.mk +++ b/unit-tests/varname-make_print_var_on_error.mk @@ -1,8 +1,16 @@ -# $NetBSD: varname-make_print_var_on_error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-make_print_var_on_error.mk,v 1.4 2020/10/23 06:18:23 rillig Exp $ # -# Tests for the special .MAKE.PRINT_VAR_ON_ERROR variable. +# Tests for the special MAKE_PRINT_VAR_ON_ERROR variable, which prints the +# values of selected variables on error. -# TODO: Implementation +# XXX: As of 2020-10-23, the .ERROR_CMD variable is pointless in compat mode +# since at the point where it is filled in PrintOnError, the first command in +# gn->commands has been set to NULL already. This leaves .ERROR_CMD an empty +# list. + +MAKE_PRINT_VAR_ON_ERROR= .ERROR_TARGET .ERROR_CMD all: - @:; + @: command before + @echo fail; false + @: command after diff --git a/unit-tests/varname-makefile.exp b/unit-tests/varname-makefile.exp new file mode 100755 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/varname-makefile.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/varname-makefile.mk b/unit-tests/varname-makefile.mk new file mode 100755 index 000000000000..785fe9301df2 --- /dev/null +++ b/unit-tests/varname-makefile.mk @@ -0,0 +1,44 @@ +# $NetBSD: varname-makefile.mk,v 1.2 2020/09/05 06:25:38 rillig Exp $ +# +# Tests for the special MAKEFILE variable, which contains the current +# makefile from the -f command line option. +# +# When there are multiple -f options, the variable MAKEFILE is set +# again for each of these makefiles, before the file is parsed. +# Including a file via .include does not influence the MAKEFILE +# variable though. + +.if ${MAKEFILE:T} != "varname-makefile.mk" +. error +.endif + +# This variable lives in the "Internal" namespace. +# TODO: Why does it do that, and what consequences does this have? + +# Deleting the variable does not work since this variable does not live in +# the "Global" namespace but in "Internal", which is kind of a child +# namespace. +# +.undef MAKEFILE +.if ${MAKEFILE:T} != "varname-makefile.mk" +. error +.endif + +# Overwriting this variable is possible since the "Internal" namespace +# serves as a fallback for the "Global" namespace (see VarFind). +# +MAKEFILE= overwritten +.if ${MAKEFILE:T} != "overwritten" +. error +.endif + +# When the overwritten value is deleted, the fallback value becomes +# visible again. +# +.undef MAKEFILE +.if ${MAKEFILE:T} != "varname-makefile.mk" +. error +.endif + +all: + @:; diff --git a/unit-tests/varname.mk b/unit-tests/varname.mk index 9dd965083f3f..e9fbc89873ca 100644 --- a/unit-tests/varname.mk +++ b/unit-tests/varname.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname.mk,v 1.4 2020/10/18 08:47:54 rillig Exp $ # # Tests for special variables, such as .MAKE or .PARSEDIR. diff --git a/unit-tests/varparse-dynamic.mk b/unit-tests/varparse-dynamic.mk index 724cca3a5035..228eb17475b0 100644 --- a/unit-tests/varparse-dynamic.mk +++ b/unit-tests/varparse-dynamic.mk @@ -1,4 +1,4 @@ -# $NetBSD: varparse-dynamic.mk,v 1.1 2020/07/26 22:15:36 rillig Exp $ +# $NetBSD: varparse-dynamic.mk,v 1.2 2020/09/13 21:00:34 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. @@ -10,5 +10,16 @@ .if ${.TARGXX} # 2 characters difference, must be defined .endif +# When a dynamic variable (such as .TARGET) is evaluated in the global +# context, 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. +.if ${.TARGET:S,^,,} != "\${.TARGET:S,^,,}" +. error +.endif + all: @: diff --git a/unit-tests/varparse-mod.exp b/unit-tests/varparse-mod.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/varparse-mod.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/varparse-mod.mk b/unit-tests/varparse-mod.mk new file mode 100644 index 000000000000..0b4cbf6ca40a --- /dev/null +++ b/unit-tests/varparse-mod.mk @@ -0,0 +1,61 @@ +# $NetBSD: varparse-mod.mk,v 1.1 2020/10/02 20:34:59 rillig Exp $ + +# Tests for parsing variable expressions with modifiers. + +# As of 2020-10-02, the below condition does not result in a parse error. +# The condition contains two separate mistakes. The first mistake is that +# the :!cmd! modifier is missing the closing '!'. The second mistake is that +# there is a stray '}' at the end of the whole condition. +# +# As of 2020-10-02, the actual parse result of this condition is a single +# variable expression with 2 modifiers. The first modifier is +# ":!echo "\$VAR"} !". Afterwards, the parser optionally skips a ':' (at the +# bottom of ApplyModifiers) and continues with the next modifier, in this case +# "= "value"", which is interpreted as a SysV substitution modifier with an +# empty left-hand side, thereby appending the string " "value"" to each word +# of the expression. +# +# As of 2020-10-02, some modifiers ensure that they are followed by either a +# ':' or the closing brace or parenthesis of the expression. The modifiers +# that don't ensure this are (in order of appearance in ApplyModifier): +# :@var@replacement@ +# :_ +# :L +# :P +# :!cmd! +# :gmtime=... +# :localtime=... +# :M (because '}' and ')' are treated the same) +# :N (because '}' and ')' are treated the same) +# :S +# :C +# :range=... +# On the other hand, these modifiers ensure that they are followed by a +# delimiter: +# :D +# :U +# :[...] +# :gmtime (if not followed by '=') +# :hash (if not followed by '=') +# :localtime (if not followed by '=') +# :t +# :q +# :Q +# :T +# :H +# :E +# :R +# :range (if not followed by '=') +# :O +# :u +# :sh +# These modifiers don't care since they reach until the closing character +# of the expression, which is either ')' or '}': +# ::= (as well as the other assignment modifiers) +# :? +# +.if ${:!echo "\$VAR"} != "value"} +.endif + +all: + @: diff --git a/unit-tests/varparse-undef-partial.exp b/unit-tests/varparse-undef-partial.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/varparse-undef-partial.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/varparse-undef-partial.mk b/unit-tests/varparse-undef-partial.mk new file mode 100644 index 000000000000..4851b6d9d567 --- /dev/null +++ b/unit-tests/varparse-undef-partial.mk @@ -0,0 +1,64 @@ +# $NetBSD: varparse-undef-partial.mk,v 1.2 2020/09/27 09:53:41 rillig Exp $ + +# When an undefined variable is expanded in a ':=' assignment, only the +# initial '$' of the variable expression is skipped by the parser, while +# the remaining expression is evaluated. In edge cases this can lead to +# a completely different interpretation of the partially expanded text. + +LIST= ${DEF} ${UNDEF} ${VAR.${PARAM}} end +DEF= defined +PARAM= :Q + +# The expression ${VAR.{PARAM}} refers to the variable named "VAR.:Q", +# with the ":Q" being part of the name. This variable is not defined, +# therefore the initial '$' of that whole expression is skipped by the +# parser (see Var_Subst, the Buf_AddByte in the else branch) and the rest +# of the expression is expanded as usual. +# +# The resulting variable expression is ${VAR.:Q}, which means that the +# interpretation of the ":Q" has changed from being part of the variable +# name to being a variable modifier. This is a classical code injection. +EVAL:= ${LIST} +.if ${EVAL} != "defined end" +. error ${EVAL} +.endif + +# Define the possible outcomes, to see which of them gets expanded. +VAR.= var-dot without parameter +${:UVAR.\:Q}= var-dot with parameter :Q + +# At this point, the variable "VAR." is defined, therefore the expression +# ${VAR.:Q} is expanded as usual. +.if ${EVAL} != "defined var-dot\\ without\\ parameter end" +. error ${EVAL} +.endif + +# In contrast to the previous line, evaluating the original LIST again now +# produces a different result since the ":Q" has already been inserted +# literally into the expression. The variable named "VAR.:Q" is defined, +# therefore it is resolved as usual. The ":Q" is interpreted as part of the +# variable name, as would be expected from reading the variable expression. +EVAL:= ${LIST} +.if ${EVAL} != "defined var-dot with parameter :Q end" +. error ${EVAL} +.endif + +# It's difficult to decide what the best behavior is in this situation. +# Should the whole expression be skipped for now, or should the inner +# subexpressions be expanded already? +# +# Example 1: +# CFLAGS:= ${CFLAGS:N-W*} ${COPTS.${COMPILER}} +# +# The variable COMPILER typically contains an identifier and the variable is +# not modified later. In this practical case, it does not matter whether the +# expression is expanded early, or whether the whole ${COPTS.${COMPILER}} is +# expanded as soon as the variable COPTS.${COMPILER} becomes defined. The +# expression ${COMPILER} would be expanded several times, but in this simple +# scenario there would not be any side effects. +# +# TODO: Add a practical example where early/lazy expansion actually makes a +# difference. + +all: + @: diff --git a/unit-tests/varshell.exp b/unit-tests/varshell.exp index dae2c6b9dff5..54df3527ed1c 100644 --- a/unit-tests/varshell.exp +++ b/unit-tests/varshell.exp @@ -1,6 +1,6 @@ -make: "varshell.mk" line 5: warning: "/bin/no/such/command 2> /dev/null" returned non-zero status -make: "varshell.mk" line 8: warning: "false" returned non-zero status -make: "varshell.mk" line 9: warning: "echo "output before the error"; false" returned non-zero status +make: "varshell.mk" line 6: warning: "/bin/no/such/command 2> /dev/null" returned non-zero status +make: "varshell.mk" line 9: warning: "false" returned non-zero status +make: "varshell.mk" line 10: warning: "echo "output before the error"; false" returned non-zero status EXEC_FAILED='' TERMINATED_BY_SIGNAL='' ERROR_NO_OUTPUT='' diff --git a/unit-tests/varshell.mk b/unit-tests/varshell.mk index 9c8baacc6474..113c265cce3c 100644 --- a/unit-tests/varshell.mk +++ b/unit-tests/varshell.mk @@ -1,17 +1,18 @@ -# $Id: varshell.mk,v 1.4 2015/04/20 03:16:39 sjg Exp $ +# $Id: varshell.mk,v 1.6 2020/10/26 17:55:23 sjg Exp $ +# $NetBSD: varshell.mk,v 1.4 2020/10/24 08:50:17 rillig Exp $ # # Test VAR != shell command -EXEC_FAILED != /bin/no/such/command 2> /dev/null +EXEC_FAILED!= /bin/no/such/command 2> /dev/null # SunOS cannot handle this one -#TERMINATED_BY_SIGNAL != kill -14 $$$$ -ERROR_NO_OUTPUT != false -ERROR_WITH_OUTPUT != echo "output before the error"; false -NO_ERROR_NO_OUTPUT != true -NO_ERROR_WITH_OUTPUT != echo "this is good" +#TERMINATED_BY_SIGNAL!= kill -14 $$$$ +ERROR_NO_OUTPUT!= false +ERROR_WITH_OUTPUT!= echo "output before the error"; false +NO_ERROR_NO_OUTPUT!= true +NO_ERROR_WITH_OUTPUT!= echo "this is good" -allvars= EXEC_FAILED TERMINATED_BY_SIGNAL ERROR_NO_OUTPUT ERROR_WITH_OUTPUT \ - NO_ERROR_NO_OUTPUT NO_ERROR_WITH_OUTPUT +allvars= EXEC_FAILED TERMINATED_BY_SIGNAL ERROR_NO_OUTPUT ERROR_WITH_OUTPUT \ + NO_ERROR_NO_OUTPUT NO_ERROR_WITH_OUTPUT all: .for v in ${allvars} @@ -1,29 +1,21 @@ -/* $NetBSD: util.c,v 1.58 2020/08/01 14:47:49 rillig Exp $ */ +/* $NetBSD: util.c,v 1.64 2020/10/06 21:51:33 rillig Exp $ */ /* * Missing stuff from OS's * - * $Id: util.c,v 1.36 2020/08/01 23:08:14 sjg Exp $ + * $Id: util.c,v 1.39 2020/10/10 19:42:02 sjg Exp $ */ -#if defined(__MINT__) || defined(__linux__) -#include <signal.h> -#endif - -#include "make.h" - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: util.c,v 1.58 2020/08/01 14:47:49 rillig Exp $"; -#else -#ifndef lint -__RCSID("$NetBSD: util.c,v 1.58 2020/08/01 14:47:49 rillig Exp $"); -#endif -#endif +#include <sys/param.h> #include <errno.h> #include <time.h> #include <signal.h> -#if !defined(HAVE_STRERROR) +#include "make.h" + +MAKE_RCSID("$NetBSD: util.c,v 1.64 2020/10/06 21:51:33 rillig Exp $"); + +#if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR) extern int errno, sys_nerr; extern char *sys_errlist[]; @@ -350,8 +342,8 @@ getcwd(path, sz) #endif /* force posix signals */ -void (* -bmake_signal(int s, void (*a)(int)))(int) +SignalProc +bmake_signal(int s, SignalProc a) { struct sigaction sa, osa; @@ -495,6 +487,7 @@ strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) buf += s; len -= s; } + return buf - b; } #endif @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.484 2020/09/02 06:25:48 rillig Exp $ */ +/* $NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,84 +68,82 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: var.c,v 1.484 2020/09/02 06:25:48 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: var.c,v 1.484 2020/09/02 06:25:48 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * var.c -- - * Variable-handling functions +/* + * 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_Set Set the value of a variable in the given - * context. The variable is created if it doesn't - * yet exist. + * Var_Init Initialize this module. + * + * Var_End Clean up the module. * - * Var_Append Append more characters to an existing variable - * in the given context. The variable needn't - * exist already -- it will be created if it doesn't. - * A space is placed between the old value and the - * new one. + * Var_Set Set the value of the variable, creating it if + * necessary. * - * Var_Exists See if a variable exists. + * Var_Append Append more characters to the variable, creating it if + * necessary. A space is placed between the old value and + * the new one. * - * Var_Value Return the unexpanded value of a variable in a - * context or NULL if the variable is undefined. + * Var_Exists See if a variable exists. * - * Var_Subst Substitute either a single variable or all - * variables in a string, using the given context. + * Var_Value Return the unexpanded value of a variable, or NULL if + * the variable is undefined. * - * Var_Parse Parse a variable expansion from a string and - * return the result and the number of characters - * consumed. + * Var_Subst Substitute all variable expressions in a string. * - * Var_Delete Delete a variable in a context. + * Var_Parse Parse a variable expression such as ${VAR:Mpattern}. * - * Var_Init Initialize this module. + * Var_Delete Delete a variable. + * + * Var_ExportVars Export some or even all variables to the environment + * of this process and its child processes. + * + * Var_Export Export the variable to the environment of this process + * and its child processes. + * + * Var_UnExport Don't export the variable anymore. * * Debugging: - * Var_Dump Print out all variables defined in the given - * context. + * Var_Stats Print out hashing statistics if in -dh mode. + * + * Var_Dump Print out all variables defined in the given context. * * XXX: There's a lot of duplication in these functions. */ -#include <sys/stat.h> -#include <sys/types.h> +#include <sys/stat.h> +#include <sys/types.h> #ifndef NO_REGEX -#include <regex.h> +#include <regex.h> #endif -#include <time.h> -#include "make.h" +#include "make.h" +#include <errno.h> #ifdef HAVE_INTTYPES_H -#include <inttypes.h> +#include <inttypes.h> #elif defined(HAVE_STDINT_H) -#include <stdint.h> +#include <stdint.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> #endif +#include <time.h> -#include "enum.h" -#include "dir.h" -#include "job.h" -#include "metachar.h" +#include "dir.h" +#include "job.h" +#include "metachar.h" -#define VAR_DEBUG_IF(cond, fmt, ...) \ - if (!(DEBUG(VAR) && (cond))) \ - (void) 0; \ - else \ - fprintf(debug_file, fmt, __VA_ARGS__) +/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $"); -#define VAR_DEBUG(fmt, ...) VAR_DEBUG_IF(TRUE, fmt, __VA_ARGS__) +#define VAR_DEBUG1(fmt, arg1) DEBUG1(VAR, fmt, arg1) +#define VAR_DEBUG2(fmt, arg1, arg2) DEBUG2(VAR, fmt, arg1, arg2) +#define VAR_DEBUG3(fmt, arg1, arg2, arg3) DEBUG3(VAR, fmt, arg1, arg2, arg3) +#define VAR_DEBUG4(fmt, arg1, arg2, arg3, arg4) DEBUG4(VAR, fmt, arg1, arg2, arg3, arg4) ENUM_FLAGS_RTTI_3(VarEvalFlags, VARE_UNDEFERR, VARE_WANTRES, VARE_ASSIGN); @@ -156,109 +154,126 @@ ENUM_FLAGS_RTTI_3(VarEvalFlags, */ char **savedEnv = NULL; -/* - * This is a harmless return value for Var_Parse that can be used by Var_Subst - * to determine if there was an error in parsing -- easier than returning - * a flag, as things outside this module don't give a hoot. - */ +/* 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[] = ""; -/* - * Similar to var_Error, but returned when the 'VARE_UNDEFERR' flag for - * Var_Parse is not set. - * - * Why not just use a constant? Well, GCC likes to condense identical string - * instances... - */ -static char varNoError[] = ""; +/* Special return value for Var_Parse, indicating an undefined variable in + * a case where VARE_UNDEFERR is not set. This undefined variable is + * typically a dynamic variable such as ${.TARGET}, whose expansion needs to + * be deferred until it is defined in an actual target. */ +static char varUndefined[] = ""; + +/* Special return value for Var_Parse, just to avoid allocating empty strings. + * In contrast to var_Error and varUndefined, this is not an error marker but + * just an ordinary successful return value. */ +static char emptyString[] = ""; /* - * Traditionally we consume $$ during := like any other expansion. - * Other make's do not. + * Traditionally this make consumed $$ during := like any other expansion. + * Other make's do not, and this make follows straight since 2016-01-09. + * * This knob allows controlling the behavior. * FALSE to consume $$ during := assignment. * TRUE to preserve $$ during := assignment. */ -#define SAVE_DOLLARS ".MAKE.SAVE_DOLLARS" +#define MAKE_SAVE_DOLLARS ".MAKE.SAVE_DOLLARS" static Boolean save_dollars = FALSE; /* * Internally, variables are contained in four different contexts. * 1) the environment. They cannot be changed. If an environment - * variable is appended to, the result is placed in the global - * context. - * 2) the global context. Variables set in the Makefile are located in - * the global context. + * variable is appended to, the result is placed in the global + * context. + * 2) the global context. Variables set in the makefiles are located + * here. * 3) the command-line context. All variables set on the command line - * are placed in this context. They are UNALTERABLE once placed here. + * are placed in this context. * 4) the local context. Each target has associated with it a context * list. On this list are located the structures describing such * local variables as $(@) and $(*) * The four contexts are searched in the reverse order from which they are - * listed (but see checkEnvFirst). + * listed (but see opts.checkEnvFirst). */ GNode *VAR_INTERNAL; /* variables from make itself */ GNode *VAR_GLOBAL; /* variables from the makefile */ -GNode *VAR_CMD; /* variables defined on the command-line */ +GNode *VAR_CMDLINE; /* variables defined on the command-line */ -typedef enum { - FIND_CMD = 0x01, /* look in VAR_CMD when searching */ - FIND_GLOBAL = 0x02, /* look in VAR_GLOBAL as well */ - FIND_ENV = 0x04 /* look in the environment also */ -} VarFindFlags; +typedef enum VarFlags { -typedef enum { /* 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 a junk variable that should be destroyed when done with - * it. Used by Var_Parse for undefined, modified variables. */ - VAR_JUNK = 0x04, - /* Variable is VAR_JUNK, but we found a use for it in some modifier and - * the value is therefore valid. */ - VAR_KEEP = 0x08, + /* 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 command line. */ + + /* The variable came from the command line. */ VAR_FROM_CMD = 0x40, + + /* The variable value cannot be changed anymore, and the variable cannot + * be deleted. Any attempts to do so are ignored. */ VAR_READONLY = 0x80 } VarFlags; -ENUM_FLAGS_RTTI_8(VarFlags, - VAR_IN_USE, VAR_FROM_ENV, VAR_JUNK, VAR_KEEP, +ENUM_FLAGS_RTTI_6(VarFlags, + VAR_IN_USE, VAR_FROM_ENV, VAR_EXPORTED, VAR_REEXPORT, VAR_FROM_CMD, VAR_READONLY); +/* Variables are defined using one of the VAR=value assignments. Their + * value can be queried by expressions such as $V, ${VAR}, or with modifiers + * such as ${VAR:S,from,to,g:Q}. + * + * There are 3 kinds of variables: context variables, environment variables, + * undefined variables. + * + * Context variables are stored in a GNode.context. The only way to undefine + * a context variable is using the .undef directive. In particular, it must + * not be possible to undefine a variable during the evaluation of an + * expression, or Var.name might point nowhere. + * + * Environment variables are temporary. They are returned by VarFind, and + * after using them, they must be freed using VarFreeEnv. + * + * Undefined variables occur during evaluation of variable expressions such + * as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers. + */ typedef struct Var { - char *name; /* the variable's name; it is allocated for - * environment variables and aliased to the - * Hash_Entry name for all other variables, - * and thus must not be modified */ + /* The name of the variable, once set, doesn't change anymore. + * For context variables, it aliases the corresponding HashEntry name. + * For environment and undefined variables, it is allocated. */ + const char *name; + void *name_freeIt; + Buffer val; /* its value */ - VarFlags flags; /* miscellaneous status flags */ + VarFlags flags; /* miscellaneous status flags */ } Var; /* * Exporting vars is expensive so skip it if we can */ -typedef enum { +typedef enum VarExportedMode { VAR_EXPORTED_NONE, - VAR_EXPORTED_YES, + VAR_EXPORTED_SOME, VAR_EXPORTED_ALL } VarExportedMode; static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE; -typedef enum { +typedef enum VarExportFlags { /* * We pass this to Var_Export when doing the initial export * or after updating an exported var. @@ -271,48 +286,30 @@ typedef enum { } VarExportFlags; /* Flags for pattern matching in the :S and :C modifiers */ -typedef enum { - VARP_SUB_GLOBAL = 0x01, /* Apply substitution globally */ - VARP_SUB_ONE = 0x02, /* Apply substitution to one word */ - VARP_ANCHOR_START = 0x04, /* Match at start of word */ - VARP_ANCHOR_END = 0x08 /* Match at end of word */ +typedef enum VarPatternFlags { + VARP_SUB_GLOBAL = 0x01, /* Replace as often as possible ('g') */ + VARP_SUB_ONE = 0x02, /* Replace only once ('1') */ + VARP_ANCHOR_START = 0x04, /* Match at start of word ('^') */ + VARP_ANCHOR_END = 0x08 /* Match at end of word ('$') */ } VarPatternFlags; -#define BROPEN '{' -#define BRCLOSE '}' -#define PROPEN '(' -#define PRCLOSE ')' - -/*- - *----------------------------------------------------------------------- - * VarFind -- - * Find the given variable in the given context and any other contexts - * indicated. - * - * Input: - * name name to find - * ctxt context in which to find it - * flags FIND_GLOBAL look in VAR_GLOBAL as well - * FIND_CMD look in VAR_CMD as well - * FIND_ENV look in the environment as well - * - * Results: - * A pointer to the structure describing the desired variable or - * NULL if the variable does not exist. - *----------------------------------------------------------------------- - */ static Var * -VarFind(const char *name, GNode *ctxt, VarFindFlags flags) +VarNew(const char *name, void *name_freeIt, const char *value, VarFlags flags) { - Hash_Entry *var; + size_t value_len = strlen(value); + Var *var = bmake_malloc(sizeof *var); + var->name = name; + var->name_freeIt = name_freeIt; + Buf_Init(&var->val, value_len + 1); + Buf_AddBytes(&var->val, value, value_len); + var->flags = flags; + return var; +} - /* - * If the variable name begins with a '.', it could very well be one of - * the local ones. We check the name against all the local variables - * and substitute the short version in for 'name' if it matches one of - * them. - */ - if (*name == '.' && isupper((unsigned char)name[1])) { +static const char * +CanonicalVarname(const char *name) +{ + if (*name == '.' && ch_isupper(name[1])) { switch (name[1]) { case 'A': if (strcmp(name, ".ALLSRC") == 0) @@ -337,7 +334,7 @@ VarFind(const char *name, GNode *ctxt, VarFindFlags flags) name = PREFIX; break; case 'S': - if (strcmp(name, ".SHELL") == 0 ) { + if (strcmp(name, ".SHELL") == 0) { if (!shellPath) Shell_Init(); } @@ -349,87 +346,100 @@ VarFind(const char *name, GNode *ctxt, VarFindFlags flags) } } -#ifdef notyet - /* for compatibility with gmake */ - if (name[0] == '^' && name[1] == '\0') - name = ALLSRC; -#endif + /* GNU make has an additional alias $^ == ${.ALLSRC}. */ + + return name; +} + +static Var * +GNode_FindVar(GNode *ctxt, const char *varname, unsigned int hash) +{ + return HashTable_FindValueHash(&ctxt->context, varname, hash); +} + +/* Find the variable in the context, and maybe in other contexts as well. + * + * Input: + * name name to find, is not expanded any further + * ctxt context in which to look first + * elsewhere TRUE to look in other contexts as well + * + * Results: + * The found variable, or NULL if the variable does not exist. + * If the variable is an environment variable, it must be freed using + * VarFreeEnv after use. + */ +static Var * +VarFind(const char *name, GNode *ctxt, Boolean elsewhere) +{ + Var *var; + unsigned int nameHash; /* - * First look for the variable in the given context. If it's not there, - * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order, - * depending on the FIND_* flags in 'flags' + * 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. */ - var = Hash_FindEntry(&ctxt->context, name); + name = CanonicalVarname(name); + nameHash = Hash_Hash(name); - if (var == NULL && (flags & FIND_CMD) && ctxt != VAR_CMD) - var = Hash_FindEntry(&VAR_CMD->context, name); + /* First look for the variable in the given context. */ + var = GNode_FindVar(ctxt, name, nameHash); + if (!elsewhere) + return var; - if (!checkEnvFirst && var == NULL && (flags & FIND_GLOBAL) && - ctxt != VAR_GLOBAL) - { - var = Hash_FindEntry(&VAR_GLOBAL->context, name); + /* The variable was not found in the given context. Now look for it in + * the other contexts as well. */ + if (var == NULL && ctxt != VAR_CMDLINE) + var = GNode_FindVar(VAR_CMDLINE, name, nameHash); + + if (!opts.checkEnvFirst && var == NULL && ctxt != VAR_GLOBAL) { + var = GNode_FindVar(VAR_GLOBAL, name, nameHash); if (var == NULL && ctxt != VAR_INTERNAL) { /* VAR_INTERNAL is subordinate to VAR_GLOBAL */ - var = Hash_FindEntry(&VAR_INTERNAL->context, name); + var = GNode_FindVar(VAR_INTERNAL, name, nameHash); } } - if (var == NULL && (flags & FIND_ENV)) { + if (var == NULL) { char *env; if ((env = getenv(name)) != NULL) { - Var *v = bmake_malloc(sizeof(Var)); - size_t len; - v->name = bmake_strdup(name); - - len = strlen(env); - Buf_Init(&v->val, len + 1); - Buf_AddBytes(&v->val, env, len); - - v->flags = VAR_FROM_ENV; - return v; + char *varname = bmake_strdup(name); + return VarNew(varname, varname, env, VAR_FROM_ENV); } - if (checkEnvFirst && (flags & FIND_GLOBAL) && ctxt != VAR_GLOBAL) { - var = Hash_FindEntry(&VAR_GLOBAL->context, name); + if (opts.checkEnvFirst && ctxt != VAR_GLOBAL) { + var = GNode_FindVar(VAR_GLOBAL, name, nameHash); if (var == NULL && ctxt != VAR_INTERNAL) - var = Hash_FindEntry(&VAR_INTERNAL->context, name); - if (var == NULL) - return NULL; - else - return (Var *)Hash_GetValue(var); + var = GNode_FindVar(VAR_INTERNAL, name, nameHash); + return var; } return NULL; } - if (var == NULL) - return NULL; - else - return (Var *)Hash_GetValue(var); + return var; } -/*- - *----------------------------------------------------------------------- - * VarFreeEnv -- - * If the variable is an environment variable, free it +/* If the variable is an environment variable, free it. * * Input: * v the variable - * destroy true if the value buffer should be destroyed. + * 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 destroy) +VarFreeEnv(Var *v, Boolean freeValue) { if (!(v->flags & VAR_FROM_ENV)) return FALSE; - free(v->name); - Buf_Destroy(&v->val, destroy); + + free(v->name_freeIt); + Buf_Destroy(&v->val, freeValue); free(v); return TRUE; } @@ -439,69 +449,53 @@ VarFreeEnv(Var *v, Boolean destroy) static void VarAdd(const char *name, const char *val, GNode *ctxt, VarSet_Flags flags) { - Var *v = bmake_malloc(sizeof(Var)); - size_t len = strlen(val); - Hash_Entry *he; - - Buf_Init(&v->val, len + 1); - Buf_AddBytes(&v->val, val, len); - - v->flags = 0; - if (flags & VAR_SET_READONLY) - v->flags |= VAR_READONLY; - - he = Hash_CreateEntry(&ctxt->context, name, NULL); - Hash_SetValue(he, v); - v->name = he->name; - VAR_DEBUG_IF(!(ctxt->flags & INTERNAL), - "%s:%s = %s\n", ctxt->name, name, val); + HashEntry *he = HashTable_CreateEntry(&ctxt->context, name, NULL); + Var *v = VarNew(he->key /* aliased */, NULL, val, + flags & VAR_SET_READONLY ? VAR_READONLY : 0); + HashEntry_Set(he, v); + if (!(ctxt->flags & INTERNAL)) { + VAR_DEBUG3("%s:%s = %s\n", ctxt->name, name, val); + } } -/* Remove a variable from a context, freeing the Var structure as well. */ +/* Remove a variable from a context, freeing all related memory as well. + * The variable name is expanded once. */ void Var_Delete(const char *name, GNode *ctxt) { char *name_freeIt = NULL; - Hash_Entry *he; + HashEntry *he; - if (strchr(name, '$') != NULL) - name = name_freeIt = Var_Subst(name, VAR_GLOBAL, VARE_WANTRES); - he = Hash_FindEntry(&ctxt->context, name); - VAR_DEBUG("%s:delete %s%s\n", - ctxt->name, name, he != NULL ? "" : " (not found)"); + if (strchr(name, '$') != NULL) { + (void)Var_Subst(name, VAR_GLOBAL, VARE_WANTRES, &name_freeIt); + /* TODO: handle errors */ + name = name_freeIt; + } + he = HashTable_FindEntry(&ctxt->context, name); + VAR_DEBUG3("%s:delete %s%s\n", + ctxt->name, name, he != NULL ? "" : " (not found)"); free(name_freeIt); if (he != NULL) { - Var *v = (Var *)Hash_GetValue(he); + Var *v = HashEntry_Get(he); if (v->flags & VAR_EXPORTED) unsetenv(v->name); if (strcmp(v->name, MAKE_EXPORTED) == 0) var_exportedVars = VAR_EXPORTED_NONE; - if (v->name != he->name) - free(v->name); - Hash_DeleteEntry(&ctxt->context, he); + assert(v->name_freeIt == NULL); + HashTable_DeleteEntry(&ctxt->context, he); Buf_Destroy(&v->val, TRUE); free(v); } } - -/* - * Export a single variable. - * We ignore make internal variables (those which start with '.'). - * Also we jump through some hoops to avoid calling setenv - * more than necessary since it can leak. - * We only manipulate flags of vars if 'parent' is set. - */ static Boolean -Var_Export1(const char *name, VarExportFlags flags) +MayExport(const char *name) { - VarExportFlags parent = flags & VAR_EXPORT_PARENT; - Var *v; - char *val; - if (name[0] == '.') return FALSE; /* skip internals */ + if (name[0] == '-') + return FALSE; /* skip misnamed variables */ if (name[1] == '\0') { /* * A single char. @@ -517,6 +511,25 @@ Var_Export1(const char *name, VarExportFlags flags) return FALSE; } } + return TRUE; +} + +/* + * Export a single variable. + * We ignore make internal variables (those which start with '.'). + * Also we jump through some hoops to avoid calling setenv + * more than necessary since it can leak. + * We only manipulate flags of vars if 'parent' is set. + */ +static Boolean +Var_Export1(const char *name, VarExportFlags flags) +{ + VarExportFlags parent = flags & VAR_EXPORT_PARENT; + Var *v; + char *val; + + if (!MayExport(name)) + return FALSE; v = VarFind(name, VAR_GLOBAL, 0); if (v == NULL) @@ -531,9 +544,9 @@ Var_Export1(const char *name, VarExportFlags flags) if (parent) { /* - * Flag this as something we need to re-export. + * Flag the variable as something we need to re-export. * No point actually exporting it now though, - * the child can do it at the last minute. + * the child process can do it at the last minute. */ v->flags |= VAR_EXPORTED | VAR_REEXPORT; return TRUE; @@ -546,8 +559,10 @@ Var_Export1(const char *name, VarExportFlags flags) return FALSE; } + /* XXX: name is injected without escaping it */ expr = str_concat3("${", name, "}"); - val = Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES); + (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &val); + /* TODO: handle errors */ setenv(name, val, 1); free(val); free(expr); @@ -557,6 +572,7 @@ Var_Export1(const char *name, VarExportFlags flags) if (parent || !(v->flags & VAR_EXPORTED)) setenv(name, val, 1); } + /* * This is so Var_Set knows to call Var_Export again... */ @@ -566,15 +582,8 @@ Var_Export1(const char *name, VarExportFlags flags) return TRUE; } -static void -Var_ExportVars_callback(void *entry, void *unused MAKE_ATTR_UNUSED) -{ - Var *var = entry; - Var_Export1(var->name, 0); -} - /* - * This gets called from our children. + * This gets called from our child processes. */ void Var_ExportVars(void) @@ -595,14 +604,21 @@ Var_ExportVars(void) return; if (var_exportedVars == VAR_EXPORTED_ALL) { - /* Ouch! This is crazy... */ - Hash_ForEach(&VAR_GLOBAL->context, Var_ExportVars_callback, NULL); + HashIter hi; + + /* Ouch! Exporting all variables at once is crazy... */ + HashIter_Init(&hi, &VAR_GLOBAL->context); + while (HashIter_Next(&hi) != NULL) { + Var *var = hi.entry->value; + Var_Export1(var->name, 0); + } return; } - val = Var_Subst("${" MAKE_EXPORTED ":O:u}", VAR_GLOBAL, VARE_WANTRES); + (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", VAR_GLOBAL, VARE_WANTRES, &val); + /* TODO: handle errors */ if (*val) { - Words words = Str_Words(val, FALSE); + Words words = Str_Words(val, FALSE); size_t i; for (i = 0; i < words.len; i++) @@ -631,26 +647,27 @@ Var_Export(const char *str, Boolean isExport) return; } - flags = 0; - if (strncmp(str, "-env", 4) == 0) { + if (isExport && strncmp(str, "-env", 4) == 0) { str += 4; - } else if (strncmp(str, "-literal", 8) == 0) { + flags = 0; + } else if (isExport && strncmp(str, "-literal", 8) == 0) { str += 8; - flags |= VAR_EXPORT_LITERAL; + flags = VAR_EXPORT_LITERAL; } else { - flags |= VAR_EXPORT_PARENT; + flags = VAR_EXPORT_PARENT; } - val = Var_Subst(str, VAR_GLOBAL, VARE_WANTRES); + (void)Var_Subst(str, VAR_GLOBAL, VARE_WANTRES, &val); + /* TODO: handle errors */ if (val[0] != '\0') { - Words words = Str_Words(val, FALSE); + Words words = Str_Words(val, FALSE); size_t i; for (i = 0; i < words.len; i++) { const char *name = words.words[i]; if (Var_Export1(name, flags)) { - if (var_exportedVars != VAR_EXPORTED_ALL) - var_exportedVars = VAR_EXPORTED_YES; + if (var_exportedVars == VAR_EXPORTED_NONE) + var_exportedVars = VAR_EXPORTED_SOME; if (isExport && (flags & VAR_EXPORT_PARENT)) { Var_Append(MAKE_EXPORTED, name, VAR_GLOBAL); } @@ -704,16 +721,17 @@ Var_UnExport(const char *str) if (cp && *cp) setenv(MAKE_LEVEL_ENV, cp, 1); } else { - for (; isspace((unsigned char)*str); str++) - continue; + cpp_skip_whitespace(&str); if (str[0] != '\0') varnames = str; } if (varnames == NULL) { /* Using .MAKE.EXPORTED */ - varnames = varnames_freeIt = Var_Subst("${" MAKE_EXPORTED ":O:u}", - VAR_GLOBAL, VARE_WANTRES); + (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", VAR_GLOBAL, VARE_WANTRES, + &varnames_freeIt); + /* TODO: handle errors */ + varnames = varnames_freeIt; } { @@ -725,11 +743,11 @@ Var_UnExport(const char *str) const char *varname = words.words[i]; v = VarFind(varname, VAR_GLOBAL, 0); if (v == NULL) { - VAR_DEBUG("Not unexporting \"%s\" (not found)\n", varname); + VAR_DEBUG1("Not unexporting \"%s\" (not found)\n", varname); continue; } - VAR_DEBUG("Unexporting \"%s\"\n", varname); + VAR_DEBUG1("Unexporting \"%s\"\n", varname); if (!unexport_env && (v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT)) unsetenv(v->name); @@ -742,8 +760,11 @@ Var_UnExport(const char *str) * just delete .MAKE.EXPORTED below. */ if (varnames == str) { + /* XXX: v->name is injected without escaping it */ char *expr = str_concat3("${" MAKE_EXPORTED ":N", v->name, "}"); - char *cp = Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES); + char *cp; + (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &cp); + /* TODO: handle errors */ Var_Set(MAKE_EXPORTED, cp, VAR_GLOBAL); free(cp); free(expr); @@ -768,55 +789,58 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt, assert(val != NULL); - /* - * We only look for a variable in the given context since anything set - * here will override anything in a lower context, so there's not much - * point in searching them all just to save a bit of memory... - */ - if (strchr(name, '$') != NULL) - name = name_freeIt = Var_Subst(name, ctxt, VARE_WANTRES); + if (strchr(name, '$') != NULL) { + (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt); + /* TODO: handle errors */ + name = name_freeIt; + } if (name[0] == '\0') { - VAR_DEBUG("Var_Set(\"%s\", \"%s\", ...) " - "name expands to empty string - ignored\n", - unexpanded_name, val); + VAR_DEBUG2("Var_Set(\"%s\", \"%s\", ...) " + "name expands to empty string - ignored\n", + unexpanded_name, val); free(name_freeIt); return; } if (ctxt == VAR_GLOBAL) { - v = VarFind(name, VAR_CMD, 0); + v = VarFind(name, VAR_CMDLINE, 0); if (v != NULL) { if (v->flags & VAR_FROM_CMD) { - VAR_DEBUG("%s:%s = %s ignored!\n", ctxt->name, name, val); + VAR_DEBUG3("%s:%s = %s ignored!\n", ctxt->name, name, val); goto out; } VarFreeEnv(v, TRUE); } } + /* + * We only look for a variable in the given context since anything set + * here will override anything in a lower context, so there's not much + * point in searching them all just to save a bit of memory... + */ v = VarFind(name, ctxt, 0); if (v == NULL) { - if (ctxt == VAR_CMD && !(flags & VAR_NO_EXPORT)) { + if (ctxt == VAR_CMDLINE && !(flags & VAR_NO_EXPORT)) { /* * This var would normally prevent the same name being added * to VAR_GLOBAL, so delete it from there if needed. * Otherwise -V name may show the wrong value. */ + /* XXX: name is expanded for the second time */ Var_Delete(name, VAR_GLOBAL); } VarAdd(name, val, ctxt, flags); } else { if ((v->flags & VAR_READONLY) && !(flags & VAR_SET_READONLY)) { - VAR_DEBUG("%s:%s = %s ignored (read-only)\n", - ctxt->name, name, val); + VAR_DEBUG3("%s:%s = %s ignored (read-only)\n", + ctxt->name, name, val); goto out; - } + } Buf_Empty(&v->val); - if (val) - Buf_AddStr(&v->val, val); + Buf_AddStr(&v->val, val); - VAR_DEBUG("%s:%s = %s\n", ctxt->name, name, val); + VAR_DEBUG3("%s:%s = %s\n", ctxt->name, name, val); if (v->flags & VAR_EXPORTED) { Var_Export1(name, VAR_EXPORT_PARENT); } @@ -826,25 +850,23 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt, * to the environment (as per POSIX standard) * Other than internals. */ - if (ctxt == VAR_CMD && !(flags & VAR_NO_EXPORT) && name[0] != '.') { - if (v == NULL) { - /* we just added it */ - v = VarFind(name, ctxt, 0); - } - if (v != NULL) - v->flags |= VAR_FROM_CMD; + if (ctxt == VAR_CMDLINE && !(flags & VAR_NO_EXPORT) && name[0] != '.') { + if (v == NULL) + v = VarFind(name, ctxt, 0); /* we just added it */ + v->flags |= VAR_FROM_CMD; + /* * If requested, don't export these in the environment * individually. We still put them in MAKEOVERRIDES so * that the command-line settings continue to override * Makefile settings. */ - if (!varNoExportEnv) - setenv(name, val ? val : "", 1); + if (!opts.varNoExportEnv) + setenv(name, val, 1); Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL); } - if (name[0] == '.' && strcmp(name, SAVE_DOLLARS) == 0) + if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0) save_dollars = s2Boolean(val, save_dollars); out: @@ -862,19 +884,19 @@ out: * Otherwise the new value overwrites and replaces the old value. * * Input: - * name name of variable to set + * name name of the variable to set, is expanded once * val value to give to the variable * ctxt context in which to set it * * Notes: * The variable is searched for only in its context before being * created in that context. I.e. if the context is VAR_GLOBAL, - * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only - * VAR_CMD->context is searched. This is done to avoid the literally - * thousands of unnecessary strcmp's that used to be done to + * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMDLINE, + * only VAR_CMDLINE->context is searched. This is done to avoid the + * literally thousands of unnecessary strcmp's that used to be done to * set, say, $(@) or $(<). * If the context is VAR_GLOBAL though, we check if the variable - * was set in VAR_CMD from the command line and skip it if so. + * was set in VAR_CMDLINE from the command line and skip it if so. *----------------------------------------------------------------------- */ void @@ -893,7 +915,7 @@ Var_Set(const char *name, const char *val, GNode *ctxt) * are concatenated, with a space in between. * * Input: - * name name of variable to modify + * name name of the variable to modify, is expanded once * val string to append to it * ctxt context in which this should occur * @@ -916,29 +938,34 @@ Var_Append(const char *name, const char *val, GNode *ctxt) if (strchr(name, '$') != NULL) { const char *unexpanded_name = name; - name = name_freeIt = Var_Subst(name, ctxt, VARE_WANTRES); + (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt); + /* TODO: handle errors */ + name = name_freeIt; if (name[0] == '\0') { - VAR_DEBUG("Var_Append(\"%s\", \"%s\", ...) " - "name expands to empty string - ignored\n", - unexpanded_name, val); + VAR_DEBUG2("Var_Append(\"%s\", \"%s\", ...) " + "name expands to empty string - ignored\n", + unexpanded_name, val); free(name_freeIt); return; } } - v = VarFind(name, ctxt, ctxt == VAR_GLOBAL ? (FIND_CMD | FIND_ENV) : 0); + v = VarFind(name, ctxt, ctxt == VAR_GLOBAL); if (v == NULL) { + /* XXX: name is expanded for the second time */ Var_Set(name, val, ctxt); - } else if (ctxt == VAR_CMD || !(v->flags & VAR_FROM_CMD)) { + } else if (v->flags & VAR_READONLY) { + VAR_DEBUG1("Ignoring append to %s since it is read-only\n", name); + } else if (ctxt == VAR_CMDLINE || !(v->flags & VAR_FROM_CMD)) { Buf_AddByte(&v->val, ' '); Buf_AddStr(&v->val, val); - VAR_DEBUG("%s:%s = %s\n", ctxt->name, name, - Buf_GetAll(&v->val, NULL)); + VAR_DEBUG3("%s:%s = %s\n", + ctxt->name, name, Buf_GetAll(&v->val, NULL)); if (v->flags & VAR_FROM_ENV) { - Hash_Entry *h; + HashEntry *h; /* * If the original variable came from the environment, we @@ -947,8 +974,8 @@ Var_Append(const char *name, const char *val, GNode *ctxt) * export other variables...) */ v->flags &= ~(unsigned)VAR_FROM_ENV; - h = Hash_CreateEntry(&ctxt->context, name, NULL); - Hash_SetValue(h, v); + h = HashTable_CreateEntry(&ctxt->context, name, NULL); + HashEntry_Set(h, v); } } free(name_freeIt); @@ -958,7 +985,7 @@ Var_Append(const char *name, const char *val, GNode *ctxt) * fallback contexts. * * Input: - * name Variable to find + * name Variable to find, is expanded once * ctxt Context in which to start search */ Boolean @@ -967,10 +994,13 @@ Var_Exists(const char *name, GNode *ctxt) char *name_freeIt = NULL; Var *v; - if (strchr(name, '$') != NULL) - name = name_freeIt = Var_Subst(name, ctxt, VARE_WANTRES); + if (strchr(name, '$') != NULL) { + (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt); + /* TODO: handle errors */ + name = name_freeIt; + } - v = VarFind(name, ctxt, FIND_CMD | FIND_GLOBAL | FIND_ENV); + v = VarFind(name, ctxt, TRUE); free(name_freeIt); if (v == NULL) return FALSE; @@ -986,34 +1016,43 @@ Var_Exists(const char *name, GNode *ctxt) * context, or the usual contexts. * * Input: - * name name to find + * name name to find, is not expanded any further * ctxt context in which to search for it * * Results: * The value if the variable exists, NULL if it doesn't. - * If the returned value is not NULL, the caller must free *freeIt - * as soon as the returned value is no longer needed. + * If the returned value is not NULL, the caller must free + * out_freeIt when the returned value is no longer needed. *----------------------------------------------------------------------- */ const char * -Var_Value(const char *name, GNode *ctxt, char **freeIt) +Var_Value(const char *name, GNode *ctxt, void **out_freeIt) { - Var *v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); - char *p; + Var *v = VarFind(name, ctxt, TRUE); + char *value; - *freeIt = NULL; + *out_freeIt = NULL; if (v == NULL) return NULL; - p = Buf_GetAll(&v->val, NULL); + value = Buf_GetAll(&v->val, NULL); if (VarFreeEnv(v, FALSE)) - *freeIt = p; - return p; + *out_freeIt = value; + return value; +} + +/* Return the unexpanded variable value from this node, without trying to look + * up the variable in any other context. */ +const char * +Var_ValueDirect(const char *name, GNode *ctxt) +{ + Var *v = VarFind(name, ctxt, FALSE); + return v != NULL ? Buf_GetAll(&v->val, NULL) : NULL; } -/* SepBuf is a string being built from "words", interleaved with separators. */ -typedef struct { +/* SepBuf is a string being built from words, interleaved with separators. */ +typedef struct SepBuf { Buffer buf; Boolean needSep; char sep; /* usually ' ', but see the :ts modifier */ @@ -1064,9 +1103,12 @@ SepBuf_Destroy(SepBuf *buf, Boolean free_buf) } -/* This callback for ModifyWords gets a single word from an expression and - * typically adds a modification of this word to the buffer. It may also do - * nothing or add several words. */ +/* This callback for ModifyWords gets a single word from a variable expression + * and typically adds a modification of this word to the buffer. It may also + * do nothing or add several words. + * + * For example, in ${:Ua b c:M*2}, the callback is called 3 times, once for + * each word of "a b c". */ typedef void (*ModifyWordsCallback)(const char *word, SepBuf *buf, void *data); @@ -1118,7 +1160,7 @@ static void ModifyWord_Match(const char *word, SepBuf *buf, void *data) { const char *pattern = data; - VAR_DEBUG("VarMatch [%s] [%s]\n", word, pattern); + VAR_DEBUG2("VarMatch [%s] [%s]\n", word, pattern); if (Str_Match(word, pattern)) SepBuf_AddStr(buf, word); } @@ -1134,10 +1176,7 @@ ModifyWord_NoMatch(const char *word, SepBuf *buf, void *data) } #ifdef SYSVVARSUB -/*- - *----------------------------------------------------------------------- - * Str_SYSVMatch -- - * Check word against pattern for a match (% is wild), +/* Check word against pattern for a match (% is a wildcard). * * Input: * word Word to examine @@ -1145,13 +1184,12 @@ ModifyWord_NoMatch(const char *word, SepBuf *buf, void *data) * * Results: * Returns the start of the match, or NULL. - * *match_len returns the length of the match, if any. - * *hasPercent returns whether the pattern contains a percent. - *----------------------------------------------------------------------- + * out_match_len returns the length of the match, if any. + * out_hasPercent returns whether the pattern contains a percent. */ static const char * -Str_SYSVMatch(const char *word, const char *pattern, size_t *match_len, - Boolean *hasPercent) +SysVMatch(const char *word, const char *pattern, + size_t *out_match_len, Boolean *out_hasPercent) { const char *p = pattern; const char *w = word; @@ -1160,15 +1198,10 @@ Str_SYSVMatch(const char *word, const char *pattern, size_t *match_len, size_t p_len; const char *w_tail; - *hasPercent = FALSE; - if (*p == '\0') { /* ${VAR:=suffix} */ - *match_len = strlen(w); /* Null pattern is the whole string */ - return w; - } - + *out_hasPercent = FALSE; percent = strchr(p, '%'); if (percent != NULL) { /* ${VAR:...%...=...} */ - *hasPercent = TRUE; + *out_hasPercent = TRUE; if (*w == '\0') return NULL; /* empty word does not match pattern */ @@ -1181,7 +1214,7 @@ Str_SYSVMatch(const char *word, const char *pattern, size_t *match_len, p++; /* Skip the percent */ if (*p == '\0') { /* No more pattern, return the rest of the string */ - *match_len = strlen(w); + *out_match_len = strlen(w); return w; } } @@ -1196,28 +1229,28 @@ Str_SYSVMatch(const char *word, const char *pattern, size_t *match_len, if (memcmp(p, w_tail, p_len) != 0) return NULL; - *match_len = (size_t)(w_tail - w); + *out_match_len = (size_t)(w_tail - w); return w; } -typedef struct { +struct ModifyWord_SYSVSubstArgs { GNode *ctx; const char *lhs; const char *rhs; -} ModifyWord_SYSVSubstArgs; +}; /* Callback for ModifyWords to implement the :%.from=%.to modifier. */ static void ModifyWord_SYSVSubst(const char *word, SepBuf *buf, void *data) { - const ModifyWord_SYSVSubstArgs *args = data; + const struct ModifyWord_SYSVSubstArgs *args = data; char *rhs_expanded; const char *rhs; const char *percent; size_t match_len; Boolean lhsPercent; - const char *match = Str_SYSVMatch(word, args->lhs, &match_len, &lhsPercent); + const char *match = SysVMatch(word, args->lhs, &match_len, &lhsPercent); if (match == NULL) { SepBuf_AddStr(buf, word); return; @@ -1226,7 +1259,8 @@ ModifyWord_SYSVSubst(const char *word, SepBuf *buf, void *data) /* Append rhs to the buffer, substituting the first '%' with the * match, but only if the lhs had a '%' as well. */ - rhs_expanded = Var_Subst(args->rhs, args->ctx, VARE_WANTRES); + (void)Var_Subst(args->rhs, args->ctx, VARE_WANTRES, &rhs_expanded); + /* TODO: handle errors */ rhs = rhs_expanded; percent = strchr(rhs, '%'); @@ -1247,14 +1281,14 @@ ModifyWord_SYSVSubst(const char *word, SepBuf *buf, void *data) #endif -typedef struct { +struct ModifyWord_SubstArgs { const char *lhs; size_t lhsLen; const char *rhs; size_t rhsLen; VarPatternFlags pflags; Boolean matched; -} ModifyWord_SubstArgs; +}; /* Callback for ModifyWords to implement the :S,from,to, modifier. * Perform a string substitution on the given word. */ @@ -1262,7 +1296,7 @@ static void ModifyWord_Subst(const char *word, SepBuf *buf, void *data) { size_t wordLen = strlen(word); - ModifyWord_SubstArgs *args = data; + struct ModifyWord_SubstArgs *args = data; const char *match; if ((args->pflags & VARP_SUB_ONE) && args->matched) @@ -1273,19 +1307,13 @@ ModifyWord_Subst(const char *word, SepBuf *buf, void *data) memcmp(word, args->lhs, args->lhsLen) != 0) goto nosub; - if (args->pflags & VARP_ANCHOR_END) { - if (wordLen != args->lhsLen) - goto nosub; + if ((args->pflags & VARP_ANCHOR_END) && wordLen != args->lhsLen) + goto nosub; - /* :S,^whole$,replacement, */ - SepBuf_AddBytes(buf, args->rhs, args->rhsLen); - args->matched = TRUE; - } else { - /* :S,^prefix,replacement, */ - SepBuf_AddBytes(buf, args->rhs, args->rhsLen); - SepBuf_AddBytes(buf, word + args->lhsLen, wordLen - args->lhsLen); - args->matched = TRUE; - } + /* :S,^prefix,replacement, or :S,^whole$,replacement, */ + SepBuf_AddBytes(buf, args->rhs, args->rhsLen); + SepBuf_AddBytes(buf, word + args->lhsLen, wordLen - args->lhsLen); + args->matched = TRUE; return; } @@ -1306,8 +1334,11 @@ ModifyWord_Subst(const char *word, SepBuf *buf, void *data) return; } + if (args->lhs[0] == '\0') + goto nosub; + /* unanchored case, may match more than once */ - while ((match = Str_FindSubstring(word, args->lhs)) != NULL) { + while ((match = strstr(word, args->lhs)) != NULL) { SepBuf_AddBytesBetween(buf, word, match); SepBuf_AddBytes(buf, args->rhs, args->rhsLen); args->matched = TRUE; @@ -1332,20 +1363,20 @@ VarREError(int reerr, regex_t *pat, const char *str) free(errbuf); } -typedef struct { - regex_t re; - size_t nsub; - char *replace; +struct ModifyWord_SubstRegexArgs { + regex_t re; + size_t nsub; + char *replace; VarPatternFlags pflags; - Boolean matched; -} ModifyWord_SubstRegexArgs; + Boolean matched; +}; /* Callback for ModifyWords to implement the :C/from/to/ modifier. * Perform a regex substitution on the given word. */ static void ModifyWord_SubstRegex(const char *word, SepBuf *buf, void *data) { - ModifyWord_SubstRegexArgs *args = data; + struct ModifyWord_SubstRegexArgs *args = data; int xrv; const char *wp = word; char *rp; @@ -1375,7 +1406,7 @@ tryagain: continue; } - if (*rp != '\\' || !isdigit((unsigned char)rp[1])) { + if (*rp != '\\' || !ch_isdigit(rp[1])) { SepBuf_AddBytes(buf, rp, 1); continue; } @@ -1386,7 +1417,7 @@ tryagain: if (n >= args->nsub) { Error("No subexpression \\%zu", n); - } else if (m[n].rm_so == -1 && m[n].rm_eo == -1) { + } else if (m[n].rm_so == -1) { Error("No match for subexpression \\%zu", n); } else { SepBuf_AddBytesBetween(buf, wp + m[n].rm_so, @@ -1411,7 +1442,7 @@ tryagain: break; default: VarREError(xrv, &args->re, "Unexpected regex error"); - /* fall through */ + /* FALLTHROUGH */ case REG_NOMATCH: nosub: SepBuf_AddStr(buf, wp); @@ -1421,18 +1452,18 @@ tryagain: #endif -typedef struct { +struct ModifyWord_LoopArgs { GNode *ctx; char *tvar; /* name of temporary variable */ char *str; /* string to expand */ VarEvalFlags eflags; -} ModifyWord_LoopArgs; +}; /* Callback for ModifyWords to implement the :@var@...@ modifier of ODE make. */ static void ModifyWord_Loop(const char *word, SepBuf *buf, void *data) { - const ModifyWord_LoopArgs *args; + const struct ModifyWord_LoopArgs *args; char *s; if (word[0] == '\0') @@ -1440,31 +1471,28 @@ ModifyWord_Loop(const char *word, SepBuf *buf, void *data) args = data; Var_Set_with_flags(args->tvar, word, args->ctx, VAR_NO_EXPORT); - s = Var_Subst(args->str, args->ctx, args->eflags); + (void)Var_Subst(args->str, args->ctx, args->eflags, &s); + /* TODO: handle errors */ - VAR_DEBUG("ModifyWord_Loop: in \"%s\", replace \"%s\" with \"%s\" " - "to \"%s\"\n", - word, args->tvar, args->str, s); + VAR_DEBUG4("ModifyWord_Loop: " + "in \"%s\", replace \"%s\" with \"%s\" to \"%s\"\n", + word, args->tvar, args->str, s); - if (s[0] == '\n' || (buf->buf.count > 0 && - buf->buf.buffer[buf->buf.count - 1] == '\n')) + if (s[0] == '\n' || Buf_EndsWith(&buf->buf, '\n')) buf->needSep = FALSE; SepBuf_AddStr(buf, s); free(s); } -/*- - * Implements the :[first..last] modifier. - * This is a special case of ModifyWords since we want to be able - * to scan the list backwards if first > last. - */ +/* The :[first..last] modifier selects words from the expression. + * It can also reverse the words. */ static char * VarSelectWords(char sep, Boolean oneBigWord, const char *str, int first, int last) { Words words; - int start, end, step; + int len, start, end, step; int i; SepBuf buf; @@ -1486,21 +1514,22 @@ VarSelectWords(char sep, Boolean oneBigWord, const char *str, int first, * If first or last are negative, convert them to the positive equivalents * (-1 gets converted to ac, -2 gets converted to (ac - 1), etc.). */ + len = (int)words.len; if (first < 0) - first += (int)words.len + 1; + first += len + 1; if (last < 0) - last += (int)words.len + 1; + last += len + 1; /* * We avoid scanning more of the list than we need to. */ if (first > last) { - start = MIN((int)words.len, first) - 1; - end = MAX(0, last - 1); + start = (first > len ? len : first) - 1; + end = last < 1 ? 0 : last - 1; step = -1; } else { - start = MAX(0, first - 1); - end = MIN((int)words.len, last); + start = first < 1 ? 0 : first - 1; + end = last > len ? len : last; step = 1; } @@ -1530,9 +1559,7 @@ ModifyWord_Realpath(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) SepBuf_AddStr(buf, word); } -/*- - *----------------------------------------------------------------------- - * Modify each of the words of the passed string using the given function. +/* Modify each of the words of the passed string using the given function. * * Input: * str String whose words should be modified @@ -1544,8 +1571,9 @@ ModifyWord_Realpath(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) *----------------------------------------------------------------------- */ static char * -ModifyWords(GNode *ctx, char sep, Boolean oneBigWord, const char *str, - ModifyWordsCallback modifyWord, void *modifyWord_args) +ModifyWords(const char *str, + ModifyWordsCallback modifyWord, void *modifyWord_args, + Boolean oneBigWord, char sep) { SepBuf result; Words words; @@ -1561,11 +1589,11 @@ ModifyWords(GNode *ctx, char sep, Boolean oneBigWord, const char *str, words = Str_Words(str, FALSE); - VAR_DEBUG("ModifyWords: split \"%s\" into %zu words\n", str, words.len); + VAR_DEBUG2("ModifyWords: split \"%s\" into %zu words\n", str, words.len); for (i = 0; i < words.len; i++) { modifyWord(words.words[i], &result, modifyWord_args); - if (result.buf.count > 0) + if (Buf_Len(&result.buf) > 0) SepBuf_Sep(&result); } @@ -1612,145 +1640,11 @@ VarUniq(const char *str) } -/*- - * Parse a part of a modifier such as the "from" and "to" in :S/from/to/ - * or the "var" or "replacement" in :@var@replacement+${var}@, up to and - * including the next unescaped delimiter. The delimiter, as well as the - * backslash or the dollar, can be escaped with a backslash. - * - * Return the parsed (and possibly expanded) string, or NULL if no delimiter - * was found. On successful return, the parsing position pp points right - * after the delimiter. The delimiter is not included in the returned - * value though. - */ -static char * -ParseModifierPart( - const char **pp, /* The parsing position, updated upon return */ - int delim, /* Parsing stops at this delimiter */ - VarEvalFlags eflags, /* Flags for evaluating nested variables; - * if VARE_WANTRES is not set, the text is - * only parsed */ - GNode *ctxt, /* For looking up nested variables */ - size_t *out_length, /* Optionally stores the length of the returned - * string, just to save another strlen call. */ - VarPatternFlags *out_pflags,/* For the first part of the :S modifier, - * sets the VARP_ANCHOR_END flag if the last - * character of the pattern is a $. */ - ModifyWord_SubstArgs *subst /* For the second part of the :S modifier, - * allow ampersands to be escaped and replace - * unescaped ampersands with subst->lhs. */ -) { - Buffer buf; - const char *p; - char *rstr; - - Buf_Init(&buf, 0); - - /* - * Skim through until the matching delimiter is found; - * pick up variable substitutions on the way. Also allow - * backslashes to quote the delimiter, $, and \, but don't - * touch other backslashes. - */ - p = *pp; - while (*p != '\0' && *p != delim) { - const char *varstart; - - Boolean is_escaped = p[0] == '\\' && ( - p[1] == delim || p[1] == '\\' || p[1] == '$' || - (p[1] == '&' && subst != NULL)); - if (is_escaped) { - Buf_AddByte(&buf, p[1]); - p += 2; - continue; - } - - if (*p != '$') { /* Unescaped, simple text */ - if (subst != NULL && *p == '&') - Buf_AddBytes(&buf, subst->lhs, subst->lhsLen); - else - Buf_AddByte(&buf, *p); - p++; - continue; - } - - if (p[1] == delim) { /* Unescaped $ at end of pattern */ - if (out_pflags != NULL) - *out_pflags |= VARP_ANCHOR_END; - else - Buf_AddByte(&buf, *p); - p++; - continue; - } - - if (eflags & VARE_WANTRES) { /* Nested variable, evaluated */ - const char *cp2; - int len; - void *freeIt; - VarEvalFlags nested_eflags = eflags & ~(unsigned)VARE_ASSIGN; - - cp2 = Var_Parse(p, ctxt, nested_eflags, &len, &freeIt); - Buf_AddStr(&buf, cp2); - free(freeIt); - p += len; - continue; - } - - /* XXX: This whole block is very similar to Var_Parse without - * VARE_WANTRES. There may be subtle edge cases though that are - * not yet covered in the unit tests and that are parsed differently, - * depending on whether they are evaluated or not. - * - * This subtle difference is not documented in the manual page, - * neither is the difference between parsing :D and :M documented. - * No code should ever depend on these details, but who knows. */ - - varstart = p; /* Nested variable, only parsed */ - if (p[1] == PROPEN || p[1] == BROPEN) { - /* - * Find the end of this variable reference - * and suck it in without further ado. - * It will be interpreted later. - */ - int have = p[1]; - int want = have == PROPEN ? PRCLOSE : BRCLOSE; - int depth = 1; - - for (p += 2; *p != '\0' && depth > 0; p++) { - if (p[-1] != '\\') { - if (*p == have) - depth++; - if (*p == want) - depth--; - } - } - Buf_AddBytesBetween(&buf, varstart, p); - } else { - Buf_AddByte(&buf, *varstart); - p++; - } - } - - if (*p != delim) { - *pp = p; - return NULL; - } - - *pp = ++p; - if (out_length != NULL) - *out_length = Buf_Size(&buf); - - rstr = Buf_Destroy(&buf, FALSE); - VAR_DEBUG("Modifier part: \"%s\"\n", rstr); - return rstr; -} - /* 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) { - char *res; Buffer buf; Buf_Init(&buf, 0); @@ -1762,16 +1656,14 @@ VarQuote(const char *str, Boolean quoteDollar) Buf_AddStr(&buf, newline); continue; } - if (isspace((unsigned char)*str) || ismeta((unsigned char)*str)) + if (ch_isspace(*str) || is_shell_metachar((unsigned char)*str)) Buf_AddByte(&buf, '\\'); Buf_AddByte(&buf, *str); if (quoteDollar && *str == '$') Buf_AddStr(&buf, "\\$"); } - res = Buf_Destroy(&buf, FALSE); - VAR_DEBUG("QuoteMeta: [%s]\n", res); - return res; + return Buf_Destroy(&buf, FALSE); } /* Compute the 32-bit hash of the given string, using the MurmurHash3 @@ -1869,7 +1761,7 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim) * st->endc. * * If parsing fails because of a missing delimiter (as in the :S, :C or :@ - * modifiers), set st->missing_delim and return AMR_CLEANUP. + * 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 @@ -1905,13 +1797,26 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim) * * Housekeeping * - * Some modifiers such as :D and :U turn undefined variables into useful - * variables (VAR_JUNK, VAR_KEEP). + * Some modifiers such as :D and :U turn undefined expressions into defined + * expressions (see VEF_UNDEF, VEF_DEF). * * Some modifiers need to free some memory. */ -typedef struct { +typedef enum VarExprFlags { + /* The variable expression is based on an undefined variable. */ + VEF_UNDEF = 0x01, + /* The variable expression started as an undefined expression, but one + * of the modifiers (such as :D or :U) has turned the expression from + * undefined to defined. */ + VEF_DEF = 0x02 +} VarExprFlags; + +ENUM_FLAGS_RTTI_2(VarExprFlags, + VEF_UNDEF, VEF_DEF); + + +typedef struct ApplyModifiersState { const char startc; /* '\0' or '{' or '(' */ const char endc; /* '\0' or '}' or ')' */ Var * const v; @@ -1922,24 +1827,175 @@ typedef struct { * before applying the modifier, never NULL */ char *newVal; /* The new value of the expression, * after applying the modifier, never NULL */ - char missing_delim; /* For error reporting */ - char sep; /* Word separator in expansions * (see the :ts modifier) */ Boolean oneBigWord; /* TRUE if some modifiers that otherwise split * the variable value into words, like :S and * :C, treat the variable value as a single big * word, possibly containing spaces. */ + VarExprFlags exprFlags; } ApplyModifiersState; -typedef enum { +static void +ApplyModifiersState_Define(ApplyModifiersState *st) +{ + if (st->exprFlags & VEF_UNDEF) + st->exprFlags |= VEF_DEF; +} + +typedef enum ApplyModifierResult { AMR_OK, /* Continue parsing */ AMR_UNKNOWN, /* Not a match, try other modifiers as well */ AMR_BAD, /* Error out with "Bad modifier" message */ - AMR_CLEANUP /* Error out, with "Unfinished modifier" - * if st->missing_delim is set. */ + AMR_CLEANUP /* Error out without error message */ } ApplyModifierResult; +/* Allow backslashes to escape the delimiter, $, and \, but don't touch other + * backslashes. */ +static Boolean +IsEscapedModifierPart(const char *p, char delim, + struct ModifyWord_SubstArgs *subst) +{ + if (p[0] != '\\') + return FALSE; + if (p[1] == delim || p[1] == '\\' || p[1] == '$') + return TRUE; + return p[1] == '&' && subst != NULL; +} + +/* + * Parse a part of a modifier such as the "from" and "to" in :S/from/to/ or + * the "var" or "replacement ${var}" in :@var@replacement ${var}@, up to and + * including the next unescaped delimiter. The delimiter, as well as the + * backslash or the dollar, can be escaped with a backslash. + * + * Return the parsed (and possibly expanded) string, or NULL if no delimiter + * was found. On successful return, the parsing position pp points right + * after the delimiter. The delimiter is not included in the returned + * value though. + */ +static VarParseResult +ParseModifierPart( + const char **pp, /* The parsing position, updated upon return */ + char delim, /* Parsing stops at this delimiter */ + VarEvalFlags eflags, /* Flags for evaluating nested variables; + * if VARE_WANTRES is not set, the text is + * only parsed */ + ApplyModifiersState *st, + char **out_part, + size_t *out_length, /* Optionally stores the length of the returned + * string, just to save another strlen call. */ + VarPatternFlags *out_pflags,/* For the first part of the :S modifier, + * sets the VARP_ANCHOR_END flag if the last + * character of the pattern is a $. */ + struct ModifyWord_SubstArgs *subst + /* For the second part of the :S modifier, + * allow ampersands to be escaped and replace + * unescaped ampersands with subst->lhs. */ +) { + Buffer buf; + const char *p; + + Buf_Init(&buf, 0); + + /* + * Skim through until the matching delimiter is found; pick up variable + * expressions on the way. + */ + p = *pp; + while (*p != '\0' && *p != delim) { + const char *varstart; + + if (IsEscapedModifierPart(p, delim, subst)) { + Buf_AddByte(&buf, p[1]); + p += 2; + continue; + } + + if (*p != '$') { /* Unescaped, simple text */ + if (subst != NULL && *p == '&') + Buf_AddBytes(&buf, subst->lhs, subst->lhsLen); + else + Buf_AddByte(&buf, *p); + p++; + continue; + } + + if (p[1] == delim) { /* Unescaped $ at end of pattern */ + if (out_pflags != NULL) + *out_pflags |= VARP_ANCHOR_END; + else + Buf_AddByte(&buf, *p); + p++; + continue; + } + + if (eflags & VARE_WANTRES) { /* Nested variable, evaluated */ + const char *nested_p = p; + const char *nested_val; + void *nested_val_freeIt; + VarEvalFlags nested_eflags = eflags & ~(unsigned)VARE_ASSIGN; + + (void)Var_Parse(&nested_p, st->ctxt, nested_eflags, + &nested_val, &nested_val_freeIt); + /* TODO: handle errors */ + Buf_AddStr(&buf, nested_val); + free(nested_val_freeIt); + p += nested_p - p; + continue; + } + + /* XXX: This whole block is very similar to Var_Parse without + * VARE_WANTRES. There may be subtle edge cases though that are + * not yet covered in the unit tests and that are parsed differently, + * depending on whether they are evaluated or not. + * + * This subtle difference is not documented in the manual page, + * neither is the difference between parsing :D and :M documented. + * No code should ever depend on these details, but who knows. */ + + varstart = p; /* Nested variable, only parsed */ + if (p[1] == '(' || p[1] == '{') { + /* + * Find the end of this variable reference + * and suck it in without further ado. + * It will be interpreted later. + */ + char startc = p[1]; + int endc = startc == '(' ? ')' : '}'; + int depth = 1; + + for (p += 2; *p != '\0' && depth > 0; p++) { + if (p[-1] != '\\') { + if (*p == startc) + depth++; + if (*p == endc) + depth--; + } + } + Buf_AddBytesBetween(&buf, varstart, p); + } else { + Buf_AddByte(&buf, *varstart); + p++; + } + } + + if (*p != delim) { + *pp = p; + Error("Unfinished modifier for %s ('%c' missing)", st->v->name, delim); + *out_part = NULL; + return VPR_PARSE_MSG; + } + + *pp = ++p; + if (out_length != NULL) + *out_length = Buf_Len(&buf); + + *out_part = Buf_Destroy(&buf, FALSE); + VAR_DEBUG1("Modifier part: \"%s\"\n", *out_part); + return VPR_OK; +} + /* Test whether mod starts with modname, followed by a delimiter. */ static Boolean ModMatch(const char *mod, const char *modname, char endc) @@ -1958,25 +2014,82 @@ ModMatchEq(const char *mod, const char *modname, char endc) (mod[n] == endc || mod[n] == ':' || mod[n] == '='); } +static Boolean +TryParseIntBase0(const char **pp, int *out_num) +{ + char *end; + long n; + + errno = 0; + n = strtol(*pp, &end, 0); + if ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE) + return FALSE; + if (n < INT_MIN || n > INT_MAX) + return FALSE; + + *pp = end; + *out_num = (int)n; + return TRUE; +} + +static Boolean +TryParseSize(const char **pp, size_t *out_num) +{ + char *end; + unsigned long n; + + if (!ch_isdigit(**pp)) + return FALSE; + + errno = 0; + n = strtoul(*pp, &end, 10); + if (n == ULONG_MAX && errno == ERANGE) + return FALSE; + if (n > SIZE_MAX) + return FALSE; + + *pp = end; + *out_num = (size_t)n; + return TRUE; +} + +static Boolean +TryParseChar(const char **pp, int base, char *out_ch) +{ + char *end; + unsigned long n; + + if (!ch_isalnum(**pp)) + return FALSE; + + errno = 0; + n = strtoul(*pp, &end, base); + if (n == ULONG_MAX && errno == ERANGE) + return FALSE; + if (n > UCHAR_MAX) + return FALSE; + + *pp = end; + *out_ch = (char)n; + return TRUE; +} + /* :@var@...${var}...@ */ static ApplyModifierResult ApplyModifier_Loop(const char **pp, ApplyModifiersState *st) { - ModifyWord_LoopArgs args; - char delim; + struct ModifyWord_LoopArgs args; char prev_sep; VarEvalFlags eflags = st->eflags & ~(unsigned)VARE_WANTRES; + VarParseResult res; args.ctx = st->ctxt; (*pp)++; /* Skip the first '@' */ - delim = '@'; - args.tvar = ParseModifierPart(pp, delim, eflags, - st->ctxt, NULL, NULL, NULL); - if (args.tvar == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, '@', eflags, st, + &args.tvar, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } if (DEBUG(LINT) && strchr(args.tvar, '$') != NULL) { Parse_Error(PARSE_FATAL, "In the :@ modifier of \"%s\", the variable name \"%s\" " @@ -1985,18 +2098,16 @@ ApplyModifier_Loop(const char **pp, ApplyModifiersState *st) return AMR_CLEANUP; } - args.str = ParseModifierPart(pp, delim, eflags, - st->ctxt, NULL, NULL, NULL); - if (args.str == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, '@', eflags, st, + &args.str, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } args.eflags = st->eflags & (VARE_UNDEFERR | VARE_WANTRES); prev_sep = st->sep; st->sep = ' '; /* XXX: should be st->sep for consistency */ - st->newVal = ModifyWords(st->ctxt, st->sep, st->oneBigWord, st->val, - ModifyWord_Loop, &args); + st->newVal = ModifyWords(st->val, ModifyWord_Loop, &args, + st->oneBigWord, st->sep); st->sep = prev_sep; Var_Delete(args.tvar, st->ctxt); free(args.tvar); @@ -2013,7 +2124,7 @@ ApplyModifier_Defined(const char **pp, ApplyModifiersState *st) VarEvalFlags eflags = st->eflags & ~(unsigned)VARE_WANTRES; if (st->eflags & VARE_WANTRES) { - if ((**pp == 'D') == !(st->v->flags & VAR_JUNK)) + if ((**pp == 'D') == !(st->exprFlags & VEF_UNDEF)) eflags |= VARE_WANTRES; } @@ -2021,7 +2132,7 @@ ApplyModifier_Defined(const char **pp, ApplyModifiersState *st) p = *pp + 1; while (*p != st->endc && *p != ':' && *p != '\0') { - /* Escaped delimiter or other special character */ + /* Escaped delimiter or other special character */ if (*p == '\\') { char c = p[1]; if (c == st->endc || c == ':' || c == '$' || c == '\\') { @@ -2033,14 +2144,14 @@ ApplyModifier_Defined(const char **pp, ApplyModifiersState *st) /* Nested variable expression */ if (*p == '$') { - const char *cp2; - int len; - void *freeIt; - - cp2 = Var_Parse(p, st->ctxt, eflags, &len, &freeIt); - Buf_AddStr(&buf, cp2); - free(freeIt); - p += len; + const char *nested_val; + void *nested_val_freeIt; + + (void)Var_Parse(&p, st->ctxt, eflags, + &nested_val, &nested_val_freeIt); + /* TODO: handle errors */ + Buf_AddStr(&buf, nested_val); + free(nested_val_freeIt); continue; } @@ -2050,8 +2161,8 @@ ApplyModifier_Defined(const char **pp, ApplyModifiersState *st) } *pp = p; - if (st->v->flags & VAR_JUNK) - st->v->flags |= VAR_KEEP; + ApplyModifiersState_Define(st); + if (eflags & VARE_WANTRES) { st->newVal = Buf_Destroy(&buf, FALSE); } else { @@ -2061,6 +2172,35 @@ ApplyModifier_Defined(const char **pp, ApplyModifiersState *st) return AMR_OK; } +/* :L */ +static ApplyModifierResult +ApplyModifier_Literal(const char **pp, ApplyModifiersState *st) +{ + ApplyModifiersState_Define(st); + st->newVal = bmake_strdup(st->v->name); + (*pp)++; + return AMR_OK; +} + +static Boolean +TryParseTime(const char **pp, time_t *out_time) +{ + char *end; + unsigned long n; + + if (!ch_isdigit(**pp)) + return FALSE; + + errno = 0; + n = strtoul(*pp, &end, 10); + if (n == ULONG_MAX && errno == ERANGE) + return FALSE; + + *pp = end; + *out_time = (time_t)n; /* ignore possible truncation for now */ + return TRUE; +} + /* :gmtime */ static ApplyModifierResult ApplyModifier_Gmtime(const char **pp, ApplyModifiersState *st) @@ -2072,9 +2212,12 @@ ApplyModifier_Gmtime(const char **pp, ApplyModifiersState *st) return AMR_UNKNOWN; if (mod[6] == '=') { - char *ep; - utc = (time_t)strtoul(mod + 7, &ep, 10); - *pp = ep; + const char *arg = mod + 7; + if (!TryParseTime(&arg, &utc)) { + Parse_Error(PARSE_FATAL, "Invalid time value: %s\n", mod + 7); + return AMR_CLEANUP; + } + *pp = arg; } else { utc = 0; *pp = mod + 6; @@ -2084,7 +2227,7 @@ ApplyModifier_Gmtime(const char **pp, ApplyModifiersState *st) } /* :localtime */ -static Boolean +static ApplyModifierResult ApplyModifier_Localtime(const char **pp, ApplyModifiersState *st) { time_t utc; @@ -2094,9 +2237,12 @@ ApplyModifier_Localtime(const char **pp, ApplyModifiersState *st) return AMR_UNKNOWN; if (mod[9] == '=') { - char *ep; - utc = (time_t)strtoul(mod + 10, &ep, 10); - *pp = ep; + const char *arg = mod + 10; + if (!TryParseTime(&arg, &utc)) { + Parse_Error(PARSE_FATAL, "Invalid time value: %s\n", mod + 10); + return AMR_CLEANUP; + } + *pp = arg; } else { utc = 0; *pp = mod + 9; @@ -2124,16 +2270,15 @@ ApplyModifier_Path(const char **pp, ApplyModifiersState *st) GNode *gn; char *path; - if (st->v->flags & VAR_JUNK) - st->v->flags |= VAR_KEEP; + ApplyModifiersState_Define(st); - gn = Targ_FindNode(st->v->name, TARG_NOCREATE); + gn = Targ_FindNode(st->v->name); if (gn == NULL || gn->type & OP_NOPATH) { path = NULL; - } else if (gn->path) { + } else if (gn->path != NULL) { path = bmake_strdup(gn->path); } else { - Lst searchPath = Suff_FindPath(gn); + SearchPath *searchPath = Suff_FindPath(gn); path = Dir_FindFile(st->v->name, searchPath); } if (path == NULL) @@ -2146,33 +2291,29 @@ ApplyModifier_Path(const char **pp, ApplyModifiersState *st) /* :!cmd! */ static ApplyModifierResult -ApplyModifier_Exclam(const char **pp, ApplyModifiersState *st) +ApplyModifier_ShellCommand(const char **pp, ApplyModifiersState *st) { - char delim; char *cmd; const char *errfmt; + VarParseResult res; (*pp)++; - delim = '!'; - cmd = ParseModifierPart(pp, delim, st->eflags, st->ctxt, - NULL, NULL, NULL); - if (cmd == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, '!', st->eflags, st, + &cmd, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } errfmt = NULL; if (st->eflags & VARE_WANTRES) st->newVal = Cmd_Exec(cmd, &errfmt); else - st->newVal = varNoError; + st->newVal = emptyString; free(cmd); if (errfmt != NULL) Error(errfmt, st->val); /* XXX: why still return AMR_OK? */ - if (st->v->flags & VAR_JUNK) - st->v->flags |= VAR_KEEP; + ApplyModifiersState_Define(st); return AMR_OK; } @@ -2190,18 +2331,21 @@ ApplyModifier_Range(const char **pp, ApplyModifiersState *st) return AMR_UNKNOWN; if (mod[5] == '=') { - char *ep; - n = (size_t)strtoul(mod + 6, &ep, 10); - *pp = ep; + const char *p = mod + 6; + if (!TryParseSize(&p, &n)) { + Parse_Error(PARSE_FATAL, "Invalid number: %s\n", mod + 6); + return AMR_CLEANUP; + } + *pp = p; } else { n = 0; *pp = mod + 5; } if (n == 0) { - Words words = Str_Words(st->val, FALSE); - n = words.len; - Words_Free(words); + Words words = Str_Words(st->val, FALSE); + n = words.len; + Words_Free(words); } Buf_Init(&buf, 0); @@ -2279,15 +2423,16 @@ ApplyModifier_Match(const char **pp, ApplyModifiersState *st) if (needSubst) { /* pattern contains embedded '$', so use Var_Subst to expand it. */ char *old_pattern = pattern; - pattern = Var_Subst(pattern, st->ctxt, st->eflags); + (void)Var_Subst(pattern, st->ctxt, st->eflags, &pattern); + /* TODO: handle errors */ free(old_pattern); } - VAR_DEBUG("Pattern[%s] for [%s] is [%s]\n", st->v->name, st->val, pattern); + VAR_DEBUG3("Pattern[%s] for [%s] is [%s]\n", st->v->name, st->val, pattern); callback = mod[0] == 'M' ? ModifyWord_Match : ModifyWord_NoMatch; - st->newVal = ModifyWords(st->ctxt, st->sep, st->oneBigWord, st->val, - callback, pattern); + st->newVal = ModifyWords(st->val, callback, pattern, + st->oneBigWord, st->sep); free(pattern); return AMR_OK; } @@ -2296,9 +2441,10 @@ ApplyModifier_Match(const char **pp, ApplyModifiersState *st) static ApplyModifierResult ApplyModifier_Subst(const char **pp, ApplyModifiersState *st) { - ModifyWord_SubstArgs args; + struct ModifyWord_SubstArgs args; char *lhs, *rhs; Boolean oneBigWord; + VarParseResult res; char delim = (*pp)[1]; if (delim == '\0') { @@ -2321,20 +2467,16 @@ ApplyModifier_Subst(const char **pp, ApplyModifiersState *st) (*pp)++; } - lhs = ParseModifierPart(pp, delim, st->eflags, st->ctxt, - &args.lhsLen, &args.pflags, NULL); - if (lhs == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, delim, st->eflags, st, + &lhs, &args.lhsLen, &args.pflags, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } args.lhs = lhs; - rhs = ParseModifierPart(pp, delim, st->eflags, st->ctxt, - &args.rhsLen, NULL, &args); - if (rhs == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, delim, st->eflags, st, + &rhs, &args.rhsLen, NULL, &args); + if (res != VPR_OK) return AMR_CLEANUP; - } args.rhs = rhs; oneBigWord = st->oneBigWord; @@ -2353,8 +2495,8 @@ ApplyModifier_Subst(const char **pp, ApplyModifiersState *st) break; } - st->newVal = ModifyWords(st->ctxt, st->sep, oneBigWord, st->val, - ModifyWord_Subst, &args); + st->newVal = ModifyWords(st->val, ModifyWord_Subst, &args, + oneBigWord, st->sep); free(lhs); free(rhs); @@ -2368,9 +2510,10 @@ static ApplyModifierResult ApplyModifier_Regex(const char **pp, ApplyModifiersState *st) { char *re; - ModifyWord_SubstRegexArgs args; + struct ModifyWord_SubstRegexArgs args; Boolean oneBigWord; int error; + VarParseResult res; char delim = (*pp)[1]; if (delim == '\0') { @@ -2381,17 +2524,15 @@ ApplyModifier_Regex(const char **pp, ApplyModifiersState *st) *pp += 2; - re = ParseModifierPart(pp, delim, st->eflags, st->ctxt, NULL, NULL, NULL); - if (re == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, delim, st->eflags, st, + &re, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } - args.replace = ParseModifierPart(pp, delim, st->eflags, st->ctxt, - NULL, NULL, NULL); + res = ParseModifierPart(pp, delim, st->eflags, st, + &args.replace, NULL, NULL, NULL); if (args.replace == NULL) { free(re); - st->missing_delim = delim; return AMR_CLEANUP; } @@ -2424,14 +2565,26 @@ ApplyModifier_Regex(const char **pp, ApplyModifiersState *st) args.nsub = args.re.re_nsub + 1; if (args.nsub > 10) args.nsub = 10; - st->newVal = ModifyWords(st->ctxt, st->sep, oneBigWord, st->val, - ModifyWord_SubstRegex, &args); + st->newVal = ModifyWords(st->val, ModifyWord_SubstRegex, &args, + oneBigWord, st->sep); regfree(&args.re); free(args.replace); return AMR_OK; } #endif +/* :Q, :q */ +static ApplyModifierResult +ApplyModifier_Quote(const char **pp, ApplyModifiersState *st) +{ + if ((*pp)[1] == st->endc || (*pp)[1] == ':') { + st->newVal = VarQuote(st->val, **pp == 'q'); + (*pp)++; + return AMR_OK; + } else + return AMR_UNKNOWN; +} + static void ModifyWord_Copy(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) { @@ -2442,9 +2595,7 @@ ModifyWord_Copy(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) static ApplyModifierResult ApplyModifier_ToSep(const char **pp, ApplyModifiersState *st) { - /* XXX: pp points to the 's', for historic reasons only. - * Changing this will influence the error messages. */ - const char *sep = *pp + 1; + const char *sep = *pp + 2; /* ":ts<any><endc>" or ":ts<any>:" */ if (sep[0] != st->endc && (sep[1] == st->endc || sep[1] == ':')) { @@ -2461,8 +2612,10 @@ ApplyModifier_ToSep(const char **pp, ApplyModifiersState *st) } /* ":ts<unrecognised><unrecognised>". */ - if (sep[0] != '\\') + if (sep[0] != '\\') { + (*pp)++; /* just for backwards compatibility */ return AMR_BAD; + } /* ":ts\n" */ if (sep[1] == 'n') { @@ -2480,25 +2633,32 @@ ApplyModifier_ToSep(const char **pp, ApplyModifiersState *st) /* ":ts\x40" or ":ts\100" */ { - const char *numStart = sep + 1; + const char *p = sep + 1; int base = 8; /* assume octal */ - char *end; if (sep[1] == 'x') { base = 16; - numStart++; - } else if (!isdigit((unsigned char)sep[1])) + p++; + } else if (!ch_isdigit(sep[1])) { + (*pp)++; /* just for backwards compatibility */ return AMR_BAD; /* ":ts<backslash><unrecognised>". */ + } - st->sep = (char)strtoul(numStart, &end, base); - if (*end != ':' && *end != st->endc) + if (!TryParseChar(&p, base, &st->sep)) { + Parse_Error(PARSE_FATAL, "Invalid character number: %s\n", p); + return AMR_CLEANUP; + } + if (*p != ':' && *p != st->endc) { + (*pp)++; /* just for backwards compatibility */ return AMR_BAD; - *pp = end; + } + + *pp = p; } ok: - st->newVal = ModifyWords(st->ctxt, st->sep, st->oneBigWord, st->val, - ModifyWord_Copy, NULL); + st->newVal = ModifyWords(st->val, ModifyWord_Copy, NULL, + st->oneBigWord, st->sep); return AMR_OK; } @@ -2509,45 +2669,48 @@ ApplyModifier_To(const char **pp, ApplyModifiersState *st) const char *mod = *pp; assert(mod[0] == 't'); - *pp = mod + 1; /* make sure it is set */ - if (mod[1] == st->endc || mod[1] == ':' || mod[1] == '\0') + if (mod[1] == st->endc || mod[1] == ':' || mod[1] == '\0') { + *pp = mod + 1; return AMR_BAD; /* Found ":t<endc>" or ":t:". */ + } if (mod[1] == 's') return ApplyModifier_ToSep(pp, st); - if (mod[2] != st->endc && mod[2] != ':') + if (mod[2] != st->endc && mod[2] != ':') { + *pp = mod + 1; return AMR_BAD; /* Found ":t<unrecognised><unrecognised>". */ + } /* Check for two-character options: ":tu", ":tl" */ if (mod[1] == 'A') { /* absolute path */ - st->newVal = ModifyWords(st->ctxt, st->sep, st->oneBigWord, st->val, - ModifyWord_Realpath, NULL); + st->newVal = ModifyWords(st->val, ModifyWord_Realpath, NULL, + st->oneBigWord, st->sep); *pp = mod + 2; return AMR_OK; } - if (mod[1] == 'u') { + if (mod[1] == 'u') { /* :tu */ size_t i; size_t len = strlen(st->val); st->newVal = bmake_malloc(len + 1); for (i = 0; i < len + 1; i++) - st->newVal[i] = (char)toupper((unsigned char)st->val[i]); + st->newVal[i] = ch_toupper(st->val[i]); *pp = mod + 2; return AMR_OK; } - if (mod[1] == 'l') { + if (mod[1] == 'l') { /* :tl */ size_t i; size_t len = strlen(st->val); st->newVal = bmake_malloc(len + 1); for (i = 0; i < len + 1; i++) - st->newVal[i] = (char)tolower((unsigned char)st->val[i]); + st->newVal[i] = ch_tolower(st->val[i]); *pp = mod + 2; return AMR_OK; } - if (mod[1] == 'W' || mod[1] == 'w') { + if (mod[1] == 'W' || mod[1] == 'w') { /* :tW, :tw */ st->oneBigWord = mod[1] == 'W'; st->newVal = st->val; *pp = mod + 2; @@ -2555,26 +2718,24 @@ ApplyModifier_To(const char **pp, ApplyModifiersState *st) } /* Found ":t<unrecognised>:" or ":t<unrecognised><endc>". */ + *pp = mod + 1; return AMR_BAD; } -/* :[#], :[1], etc. */ +/* :[#], :[1], :[-1..1], etc. */ static ApplyModifierResult ApplyModifier_Words(const char **pp, ApplyModifiersState *st) { - char delim; char *estr; - char *ep; int first, last; + VarParseResult res; + const char *p; (*pp)++; /* skip the '[' */ - delim = ']'; /* look for closing ']' */ - estr = ParseModifierPart(pp, delim, st->eflags, st->ctxt, - NULL, NULL, NULL); - if (estr == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, ']', st->eflags, st, + &estr, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } /* now *pp points just after the closing ']' */ if (**pp != ':' && **pp != st->endc) @@ -2592,7 +2753,7 @@ ApplyModifier_Words(const char **pp, ApplyModifiersState *st) Words words = Str_Words(st->val, FALSE); size_t ac = words.len; Words_Free(words); - + Buf_Init(&buf, 4); /* 3 digits + '\0' is usually enough */ Buf_AddInt(&buf, (int)ac); st->newVal = Buf_Destroy(&buf, FALSE); @@ -2618,24 +2779,23 @@ ApplyModifier_Words(const char **pp, ApplyModifiersState *st) * We expect estr to contain a single integer for :[N], or two integers * separated by ".." for :[start..end]. */ - first = (int)strtol(estr, &ep, 0); - if (ep == estr) /* Found junk instead of a number */ - goto bad_modifier; + p = estr; + if (!TryParseIntBase0(&p, &first)) + goto bad_modifier; /* Found junk instead of a number */ - if (ep[0] == '\0') { /* Found only one integer in :[N] */ + if (p[0] == '\0') { /* Found only one integer in :[N] */ last = first; - } else if (ep[0] == '.' && ep[1] == '.' && ep[2] != '\0') { + } else if (p[0] == '.' && p[1] == '.' && p[2] != '\0') { /* Expecting another integer after ".." */ - ep += 2; - last = (int)strtol(ep, &ep, 0); - if (ep[0] != '\0') /* Found junk after ".." */ - goto bad_modifier; + p += 2; + if (!TryParseIntBase0(&p, &last) || *p != '\0') + goto bad_modifier; /* Found junk after ".." */ } else goto bad_modifier; /* Found junk instead of ".." */ /* - * Now seldata is properly filled in, but we still have to check for 0 as - * a special case. + * 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]" */ @@ -2648,7 +2808,7 @@ ApplyModifier_Words(const char **pp, ApplyModifiersState *st) if (first == 0 || last == 0) goto bad_modifier; - /* Normal case: select the words described by seldata. */ + /* Normal case: select the words described by first and last. */ st->newVal = VarSelectWords(st->sep, st->oneBigWord, st->val, first, last); ok: @@ -2721,8 +2881,8 @@ ApplyModifier_Order(const char **pp, ApplyModifiersState *st) static ApplyModifierResult ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) { - char delim; char *then_expr, *else_expr; + VarParseResult res; Boolean value = FALSE; VarEvalFlags then_eflags = st->eflags & ~(unsigned)VARE_WANTRES; @@ -2730,7 +2890,7 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) int cond_rc = COND_PARSE; /* anything other than COND_INVALID */ if (st->eflags & VARE_WANTRES) { - cond_rc = Cond_EvalExpression(NULL, st->v->name, &value, 0, FALSE); + cond_rc = Cond_EvalCondition(st->v->name, &value); if (cond_rc != COND_INVALID && value) then_eflags |= VARE_WANTRES; if (cond_rc != COND_INVALID && !value) @@ -2738,21 +2898,15 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) } (*pp)++; /* skip past the '?' */ - delim = ':'; - then_expr = ParseModifierPart(pp, delim, then_eflags, st->ctxt, - NULL, NULL, NULL); - if (then_expr == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, ':', then_eflags, st, + &then_expr, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } - delim = st->endc; /* BRCLOSE or PRCLOSE */ - else_expr = ParseModifierPart(pp, delim, else_eflags, st->ctxt, - NULL, NULL, NULL); - if (else_expr == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, st->endc, else_eflags, st, + &else_expr, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } (*pp)--; if (cond_rc == COND_INVALID) { @@ -2768,8 +2922,7 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) st->newVal = else_expr; free(then_expr); } - if (st->v->flags & VAR_JUNK) - st->v->flags |= VAR_KEEP; + ApplyModifiersState_Define(st); return AMR_OK; } @@ -2783,7 +2936,7 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) * * foo: .USE * .for i in ${.TARGET} ${.TARGET:R}.gz - * @: ${t::=$i} + * @: ${t::=$i} * @echo blah ${t:T} * .endfor * @@ -2798,32 +2951,27 @@ static ApplyModifierResult ApplyModifier_Assign(const char **pp, ApplyModifiersState *st) { GNode *v_ctxt; - char *sv_name; char delim; char *val; + VarParseResult res; const char *mod = *pp; const char *op = mod + 1; - if (!(op[0] == '=' || - (op[1] == '=' && - (op[0] == '!' || op[0] == '+' || op[0] == '?')))) - return AMR_UNKNOWN; /* "::<unrecognised>" */ + if (op[0] == '=') + goto ok; + if ((op[0] == '!' || op[0] == '+' || op[0] == '?') && op[1] == '=') + goto ok; + return AMR_UNKNOWN; /* "::<unrecognised>" */ +ok: - if (st->v->name[0] == 0) { + if (st->v->name[0] == '\0') { *pp = mod + 1; return AMR_BAD; } v_ctxt = st->ctxt; /* context where v belongs */ - sv_name = NULL; - if (st->v->flags & VAR_JUNK) { - /* - * We need to bmake_strdup() it in case ParseModifierPart() recurses. - */ - sv_name = st->v->name; - st->v->name = bmake_strdup(st->v->name); - } else if (st->ctxt != VAR_GLOBAL) { + if (!(st->exprFlags & VEF_UNDEF) && st->ctxt != VAR_GLOBAL) { Var *gv = VarFind(st->v->name, st->ctxt, 0); if (gv == NULL) v_ctxt = VAR_GLOBAL; @@ -2842,17 +2990,10 @@ ApplyModifier_Assign(const char **pp, ApplyModifiersState *st) break; } - delim = st->startc == PROPEN ? PRCLOSE : BRCLOSE; - val = ParseModifierPart(pp, delim, st->eflags, st->ctxt, NULL, NULL, NULL); - if (st->v->flags & VAR_JUNK) { - /* restore original name */ - free(st->v->name); - st->v->name = sv_name; - } - if (val == NULL) { - st->missing_delim = delim; + delim = st->startc == '(' ? ')' : '}'; + res = ParseModifierPart(pp, delim, st->eflags, st, &val, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } (*pp)--; @@ -2872,7 +3013,7 @@ ApplyModifier_Assign(const char **pp, ApplyModifiersState *st) break; } case '?': - if (!(st->v->flags & VAR_JUNK)) + if (!(st->exprFlags & VEF_UNDEF)) break; /* FALLTHROUGH */ default: @@ -2881,13 +3022,12 @@ ApplyModifier_Assign(const char **pp, ApplyModifiersState *st) } } free(val); - st->newVal = varNoError; /* XXX: varNoError is kind of an error, - * the intention here is to just return - * an empty string. */ + st->newVal = emptyString; return AMR_OK; } -/* remember current value */ +/* :_=... + * remember current value */ static ApplyModifierResult ApplyModifier_Remember(const char **pp, ApplyModifiersState *st) { @@ -2909,7 +3049,8 @@ ApplyModifier_Remember(const char **pp, ApplyModifiersState *st) return AMR_OK; } -/* Apply the given function to each word of the variable value. */ +/* Apply the given function to each word of the variable value, + * for a single-letter modifier such as :H, :T. */ static ApplyModifierResult ApplyModifier_WordFunc(const char **pp, ApplyModifiersState *st, ModifyWordsCallback modifyWord) @@ -2918,70 +3059,73 @@ ApplyModifier_WordFunc(const char **pp, ApplyModifiersState *st, if (delim != st->endc && delim != ':') return AMR_UNKNOWN; - st->newVal = ModifyWords(st->ctxt, st->sep, st->oneBigWord, - st->val, modifyWord, NULL); + st->newVal = ModifyWords(st->val, modifyWord, NULL, + st->oneBigWord, st->sep); (*pp)++; return AMR_OK; } +static ApplyModifierResult +ApplyModifier_Unique(const char **pp, ApplyModifiersState *st) +{ + if ((*pp)[1] == st->endc || (*pp)[1] == ':') { + st->newVal = VarUniq(st->val); + (*pp)++; + return AMR_OK; + } else + return AMR_UNKNOWN; +} + #ifdef SYSVVARSUB /* :from=to */ static ApplyModifierResult ApplyModifier_SysV(const char **pp, ApplyModifiersState *st) { - char delim; char *lhs, *rhs; + VarParseResult res; const char *mod = *pp; Boolean eqFound = FALSE; /* - * First we make a pass through the string trying - * to verify it is a SYSV-make-style translation: - * it must be: <string1>=<string2>) + * First we make a pass through the string trying to verify it is a + * SysV-make-style translation. It must be: <lhs>=<rhs> */ - int nest = 1; - const char *next = mod; - while (*next != '\0' && nest > 0) { - if (*next == '=') { + 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 (*next == st->endc) - nest--; - else if (*next == st->startc) - nest++; - if (nest > 0) - next++; + } else if (*p == st->endc) + depth--; + else if (*p == st->startc) + depth++; + if (depth > 0) + p++; } - if (*next != st->endc || !eqFound) + if (*p != st->endc || !eqFound) return AMR_UNKNOWN; - delim = '='; *pp = mod; - lhs = ParseModifierPart(pp, delim, st->eflags, st->ctxt, NULL, NULL, NULL); - if (lhs == NULL) { - st->missing_delim = delim; + res = ParseModifierPart(pp, '=', st->eflags, st, + &lhs, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } - delim = st->endc; - rhs = ParseModifierPart(pp, delim, st->eflags, st->ctxt, NULL, NULL, NULL); - if (rhs == NULL) { - st->missing_delim = delim; + /* The SysV modifier lasts until the end of the variable expression. */ + res = ParseModifierPart(pp, st->endc, st->eflags, st, + &rhs, NULL, NULL, NULL); + if (res != VPR_OK) return AMR_CLEANUP; - } - /* - * SYSV modifications happen through the whole - * string. Note the pattern is anchored at the end. - */ (*pp)--; - if (lhs[0] == '\0' && *st->val == '\0') { + if (lhs[0] == '\0' && st->val[0] == '\0') { st->newVal = st->val; /* special case */ } else { - ModifyWord_SYSVSubstArgs args = {st->ctxt, lhs, rhs}; - st->newVal = ModifyWords(st->ctxt, st->sep, st->oneBigWord, st->val, - ModifyWord_SYSVSubst, &args); + struct ModifyWord_SYSVSubstArgs args = {st->ctxt, lhs, rhs}; + st->newVal = ModifyWords(st->val, ModifyWord_SYSVSubst, &args, + st->oneBigWord, st->sep); } free(lhs); free(rhs); @@ -2989,24 +3133,232 @@ ApplyModifier_SysV(const char **pp, ApplyModifiersState *st) } #endif +#ifdef SUNSHCMD +/* :sh */ +static ApplyModifierResult +ApplyModifier_SunShell(const char **pp, ApplyModifiersState *st) +{ + const char *p = *pp; + if (p[1] == 'h' && (p[2] == st->endc || p[2] == ':')) { + if (st->eflags & VARE_WANTRES) { + const char *errfmt; + st->newVal = Cmd_Exec(st->val, &errfmt); + if (errfmt) + Error(errfmt, st->val); + } else + st->newVal = emptyString; + *pp = p + 2; + return AMR_OK; + } else + return AMR_UNKNOWN; +} +#endif + +static void +LogBeforeApply(const ApplyModifiersState *st, const char *mod, const char endc) +{ + char eflags_str[VarEvalFlags_ToStringSize]; + char vflags_str[VarFlags_ToStringSize]; + char exprflags_str[VarExprFlags_ToStringSize]; + Boolean is_single_char = mod[0] != '\0' && + (mod[1] == endc || mod[1] == ':'); + + /* At this point, only the first character of the modifier can + * be used since the end of the modifier is not yet known. */ + debug_printf("Applying ${%s:%c%s} to \"%s\" (%s, %s, %s)\n", + st->v->name, mod[0], is_single_char ? "" : "...", st->val, + Enum_FlagsToString(eflags_str, sizeof eflags_str, + st->eflags, VarEvalFlags_ToStringSpecs), + Enum_FlagsToString(vflags_str, sizeof vflags_str, + st->v->flags, VarFlags_ToStringSpecs), + Enum_FlagsToString(exprflags_str, sizeof exprflags_str, + st->exprFlags, + VarExprFlags_ToStringSpecs)); +} + +static void +LogAfterApply(ApplyModifiersState *st, const char *p, const char *mod) +{ + char eflags_str[VarEvalFlags_ToStringSize]; + char vflags_str[VarFlags_ToStringSize]; + char exprflags_str[VarExprFlags_ToStringSize]; + const char *quot = st->newVal == var_Error ? "" : "\""; + const char *newVal = st->newVal == var_Error ? "error" : st->newVal; + + debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s, %s)\n", + st->v->name, (int)(p - mod), mod, quot, newVal, quot, + Enum_FlagsToString(eflags_str, sizeof eflags_str, + st->eflags, VarEvalFlags_ToStringSpecs), + Enum_FlagsToString(vflags_str, sizeof vflags_str, + st->v->flags, VarFlags_ToStringSpecs), + Enum_FlagsToString(exprflags_str, sizeof exprflags_str, + st->exprFlags, + VarExprFlags_ToStringSpecs)); +} + +static ApplyModifierResult +ApplyModifier(const char **pp, ApplyModifiersState *st) +{ + switch (**pp) { + case ':': + return ApplyModifier_Assign(pp, st); + case '@': + return ApplyModifier_Loop(pp, st); + case '_': + return ApplyModifier_Remember(pp, st); + case 'D': + case 'U': + return ApplyModifier_Defined(pp, st); + case 'L': + return ApplyModifier_Literal(pp, st); + case 'P': + return ApplyModifier_Path(pp, st); + case '!': + return ApplyModifier_ShellCommand(pp, st); + case '[': + return ApplyModifier_Words(pp, st); + case 'g': + return ApplyModifier_Gmtime(pp, st); + case 'h': + return ApplyModifier_Hash(pp, st); + case 'l': + return ApplyModifier_Localtime(pp, st); + case 't': + return ApplyModifier_To(pp, st); + case 'N': + case 'M': + return ApplyModifier_Match(pp, st); + case 'S': + return ApplyModifier_Subst(pp, st); + case '?': + return ApplyModifier_IfElse(pp, st); +#ifndef NO_REGEX + case 'C': + return ApplyModifier_Regex(pp, st); +#endif + case 'q': + case 'Q': + return ApplyModifier_Quote(pp, st); + case 'T': + return ApplyModifier_WordFunc(pp, st, ModifyWord_Tail); + case 'H': + return ApplyModifier_WordFunc(pp, st, ModifyWord_Head); + case 'E': + return ApplyModifier_WordFunc(pp, st, ModifyWord_Suffix); + case 'R': + return ApplyModifier_WordFunc(pp, st, ModifyWord_Root); + case 'r': + return ApplyModifier_Range(pp, st); + case 'O': + return ApplyModifier_Order(pp, st); + case 'u': + return ApplyModifier_Unique(pp, st); +#ifdef SUNSHCMD + case 's': + return ApplyModifier_SunShell(pp, st); +#endif + default: + return AMR_UNKNOWN; + } +} + +static char *ApplyModifiers(const char **, char *, char, char, Var *, + VarExprFlags *, GNode *, VarEvalFlags, void **); + +typedef enum ApplyModifiersIndirectResult { + AMIR_CONTINUE, + AMIR_APPLY_MODS, + AMIR_OUT +} ApplyModifiersIndirectResult; + +/* While expanding a variable expression, expand and apply indirect + * modifiers. */ +static ApplyModifiersIndirectResult +ApplyModifiersIndirect( + ApplyModifiersState *const st, + const char **inout_p, + void **const out_freeIt +) { + const char *p = *inout_p; + const char *nested_p = p; + void *freeIt; + const char *rval; + char c; + + (void)Var_Parse(&nested_p, st->ctxt, st->eflags, &rval, &freeIt); + /* TODO: handle errors */ + + /* + * If we have not parsed up to st->endc or ':', we are not + * interested. This means the expression ${VAR:${M_1}${M_2}} + * is not accepted, but ${VAR:${M_1}:${M_2}} is. + */ + if (rval[0] != '\0' && + (c = *nested_p) != '\0' && c != ':' && c != st->endc) { + if (DEBUG(LINT)) + Parse_Error(PARSE_FATAL, + "Missing delimiter ':' after indirect modifier \"%.*s\"", + (int)(nested_p - p), p); + + free(freeIt); + /* XXX: apply_mods doesn't sound like "not interested". */ + /* XXX: Why is the indirect modifier parsed again by + * apply_mods? If any, p should be advanced to nested_p. */ + return AMIR_APPLY_MODS; + } + + VAR_DEBUG3("Indirect modifier \"%s\" from \"%.*s\"\n", + rval, (int)(size_t)(nested_p - p), p); + + p = nested_p; + + if (rval[0] != '\0') { + const char *rval_pp = rval; + st->val = ApplyModifiers(&rval_pp, st->val, '\0', '\0', st->v, + &st->exprFlags, st->ctxt, st->eflags, out_freeIt); + if (st->val == var_Error + || (st->val == varUndefined && !(st->eflags & VARE_UNDEFERR)) + || *rval_pp != '\0') { + free(freeIt); + *inout_p = p; + return AMIR_OUT; /* error already reported */ + } + } + free(freeIt); + + if (*p == ':') + p++; + else if (*p == '\0' && st->endc != '\0') { + Error("Unclosed variable specification after complex " + "modifier (expecting '%c') for %s", st->endc, st->v->name); + *inout_p = p; + return AMIR_OUT; + } + + *inout_p = p; + return AMIR_CONTINUE; +} + /* Apply any modifiers (such as :Mpattern or :@var@loop@ or :Q or ::=value). */ static char * ApplyModifiers( const char **pp, /* the parsing position, updated upon return */ - char *val, /* the current value of the variable */ + char *const val, /* the current value of the expression */ char const startc, /* '(' or '{', or '\0' for indirect modifiers */ char const endc, /* ')' or '}', or '\0' for indirect modifiers */ - Var * const v, /* the variable may have its flags changed */ + Var * const v, + VarExprFlags *exprFlags, GNode * const ctxt, /* for looking up and modifying variables */ VarEvalFlags const eflags, - void ** const freePtr /* free this after using the return value */ + void ** const out_freeIt /* free this after using the return value */ ) { ApplyModifiersState st = { - startc, endc, v, ctxt, eflags, val, + startc, endc, v, ctxt, eflags, + val, /* .val */ var_Error, /* .newVal */ - '\0', /* .missing_delim */ ' ', /* .sep */ - FALSE /* .oneBigWord */ + FALSE, /* .oneBigWord */ + *exprFlags /* .exprFlags */ }; const char *p; const char *mod; @@ -3020,183 +3372,20 @@ ApplyModifiers( while (*p != '\0' && *p != endc) { if (*p == '$') { - /* - * We may have some complex modifiers in a variable. - */ - int rlen; - void *freeIt; - const char *rval = Var_Parse(p, st.ctxt, st.eflags, &rlen, &freeIt); - - /* - * If we have not parsed up to st.endc or ':', - * we are not interested. - */ - int c; - if (rval[0] != '\0' && - (c = p[rlen]) != '\0' && c != ':' && c != st.endc) { - free(freeIt); - goto apply_mods; - } - - VAR_DEBUG("Indirect modifier \"%s\" from \"%.*s\"\n", - rval, rlen, p); - - p += rlen; - - if (rval[0] != '\0') { - const char *rval_pp = rval; - st.val = ApplyModifiers(&rval_pp, st.val, '\0', '\0', v, - ctxt, eflags, freePtr); - if (st.val == var_Error - || (st.val == varNoError && !(st.eflags & VARE_UNDEFERR)) - || *rval_pp != '\0') { - free(freeIt); - goto out; /* error already reported */ - } - } - free(freeIt); - if (*p == ':') - p++; - else if (*p == '\0' && endc != '\0') { - Error("Unclosed variable specification after complex " - "modifier (expecting '%c') for %s", st.endc, st.v->name); - goto out; - } - continue; + ApplyModifiersIndirectResult amir; + amir = ApplyModifiersIndirect(&st, &p, out_freeIt); + if (amir == AMIR_CONTINUE) + continue; + if (amir == AMIR_OUT) + goto out; } - apply_mods: st.newVal = var_Error; /* default value, in case of errors */ - res = AMR_BAD; /* just a safe fallback */ mod = p; - if (DEBUG(VAR)) { - char eflags_str[VarEvalFlags_ToStringSize]; - char vflags_str[VarFlags_ToStringSize]; - Boolean is_single_char = mod[0] != '\0' && - (mod[1] == endc || mod[1] == ':'); - - /* At this point, only the first character of the modifier can - * be used since the end of the modifier is not yet known. */ - VAR_DEBUG("Applying ${%s:%c%s} to \"%s\" " - "(eflags = %s, vflags = %s)\n", - st.v->name, mod[0], is_single_char ? "" : "...", st.val, - Enum_FlagsToString(eflags_str, sizeof eflags_str, - st.eflags, VarEvalFlags_ToStringSpecs), - Enum_FlagsToString(vflags_str, sizeof vflags_str, - st.v->flags, VarFlags_ToStringSpecs)); - } + if (DEBUG(VAR)) + LogBeforeApply(&st, mod, endc); - switch (*mod) { - case ':': - res = ApplyModifier_Assign(&p, &st); - break; - case '@': - res = ApplyModifier_Loop(&p, &st); - break; - case '_': - res = ApplyModifier_Remember(&p, &st); - break; - case 'D': - case 'U': - res = ApplyModifier_Defined(&p, &st); - break; - case 'L': - if (st.v->flags & VAR_JUNK) - st.v->flags |= VAR_KEEP; - st.newVal = bmake_strdup(st.v->name); - p++; - res = AMR_OK; - break; - case 'P': - res = ApplyModifier_Path(&p, &st); - break; - case '!': - res = ApplyModifier_Exclam(&p, &st); - break; - case '[': - res = ApplyModifier_Words(&p, &st); - break; - case 'g': - res = ApplyModifier_Gmtime(&p, &st); - break; - case 'h': - res = ApplyModifier_Hash(&p, &st); - break; - case 'l': - res = ApplyModifier_Localtime(&p, &st); - break; - case 't': - res = ApplyModifier_To(&p, &st); - break; - case 'N': - case 'M': - res = ApplyModifier_Match(&p, &st); - break; - case 'S': - res = ApplyModifier_Subst(&p, &st); - break; - case '?': - res = ApplyModifier_IfElse(&p, &st); - break; -#ifndef NO_REGEX - case 'C': - res = ApplyModifier_Regex(&p, &st); - break; -#endif - case 'q': - case 'Q': - if (p[1] == st.endc || p[1] == ':') { - st.newVal = VarQuote(st.val, *mod == 'q'); - p++; - res = AMR_OK; - } else - res = AMR_UNKNOWN; - break; - case 'T': - res = ApplyModifier_WordFunc(&p, &st, ModifyWord_Tail); - break; - case 'H': - res = ApplyModifier_WordFunc(&p, &st, ModifyWord_Head); - break; - case 'E': - res = ApplyModifier_WordFunc(&p, &st, ModifyWord_Suffix); - break; - case 'R': - res = ApplyModifier_WordFunc(&p, &st, ModifyWord_Root); - break; - case 'r': - res = ApplyModifier_Range(&p, &st); - break; - case 'O': - res = ApplyModifier_Order(&p, &st); - break; - case 'u': - if (p[1] == st.endc || p[1] == ':') { - st.newVal = VarUniq(st.val); - p++; - res = AMR_OK; - } else - res = AMR_UNKNOWN; - break; -#ifdef SUNSHCMD - case 's': - if (p[1] == 'h' && (p[2] == st.endc || p[2] == ':')) { - if (st.eflags & VARE_WANTRES) { - const char *errfmt; - st.newVal = Cmd_Exec(st.val, &errfmt); - if (errfmt) - Error(errfmt, st.val); - } else - st.newVal = varNoError; - p += 2; - res = AMR_OK; - } else - res = AMR_UNKNOWN; - break; -#endif - default: - res = AMR_UNKNOWN; - } + res = ApplyModifier(&p, &st); #ifdef SYSVVARSUB if (res == AMR_UNKNOWN) { @@ -3216,29 +3405,18 @@ ApplyModifiers( if (res == AMR_BAD) goto bad_modifier; - if (DEBUG(VAR)) { - char eflags_str[VarEvalFlags_ToStringSize]; - char vflags_str[VarFlags_ToStringSize]; - const char *quot = st.newVal == var_Error ? "" : "\""; - const char *newVal = st.newVal == var_Error ? "error" : st.newVal; - - VAR_DEBUG("Result of ${%s:%.*s} is %s%s%s " - "(eflags = %s, vflags = %s)\n", - st.v->name, (int)(p - mod), mod, quot, newVal, quot, - Enum_FlagsToString(eflags_str, sizeof eflags_str, - st.eflags, VarEvalFlags_ToStringSpecs), - Enum_FlagsToString(vflags_str, sizeof vflags_str, - st.v->flags, VarFlags_ToStringSpecs)); - } + if (DEBUG(VAR)) + LogAfterApply(&st, p, mod); if (st.newVal != st.val) { - if (*freePtr) { + if (*out_freeIt) { free(st.val); - *freePtr = NULL; + *out_freeIt = NULL; } st.val = st.newVal; - if (st.val != var_Error && st.val != varNoError) { - *freePtr = st.val; + if (st.val != var_Error && st.val != varUndefined && + st.val != emptyString) { + *out_freeIt = st.val; } } if (*p == '\0' && st.endc != '\0') { @@ -3247,12 +3425,16 @@ ApplyModifiers( st.endc, st.v->name, st.val, *mod); } else if (*p == ':') { p++; + } else if (DEBUG(LINT) && *p != '\0' && *p != endc) { + Parse_Error(PARSE_FATAL, + "Missing delimiter ':' after modifier \"%.*s\"", + (int)(p - mod), mod); } - mod = p; } out: *pp = p; - assert(st.val != NULL); /* Use var_Error or varNoError instead. */ + assert(st.val != NULL); /* Use var_Error or varUndefined instead. */ + *exprFlags = st.exprFlags; return st.val; bad_modifier: @@ -3261,21 +3443,42 @@ bad_modifier: cleanup: *pp = p; - if (st.missing_delim != '\0') - Error("Unfinished modifier for %s ('%c' missing)", - st.v->name, st.missing_delim); - free(*freePtr); - *freePtr = NULL; + free(*out_freeIt); + *out_freeIt = NULL; + *exprFlags = st.exprFlags; return var_Error; } +/* Only four of the local variables are treated specially as they are the + * only four that will be set when dynamic sources are expanded. */ static Boolean -VarIsDynamic(GNode *ctxt, const char *varname, size_t namelen) +VarnameIsDynamic(const char *name, size_t len) { - if ((namelen == 1 || - (namelen == 2 && (varname[1] == 'F' || varname[1] == 'D'))) && - (ctxt == VAR_CMD || ctxt == VAR_GLOBAL)) - { + if (len == 1 || (len == 2 && (name[1] == 'F' || name[1] == 'D'))) { + switch (name[0]) { + case '@': + case '%': + case '*': + case '!': + return TRUE; + } + return FALSE; + } + + if ((len == 7 || len == 8) && name[0] == '.' && ch_isupper(name[1])) { + return strcmp(name, ".TARGET") == 0 || + strcmp(name, ".ARCHIVE") == 0 || + strcmp(name, ".PREFIX") == 0 || + strcmp(name, ".MEMBER") == 0; + } + + return FALSE; +} + +static const char * +UndefinedShortVarValue(char varname, const GNode *ctxt, VarEvalFlags eflags) +{ + if (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL) { /* * If substituting a local variable in a non-local context, * assume it's for dynamic source stuff. We have to handle @@ -3285,271 +3488,376 @@ VarIsDynamic(GNode *ctxt, const char *varname, size_t namelen) * specially as they are the only four that will be set * when dynamic sources are expanded. */ - switch (varname[0]) { + switch (varname) { case '@': + return "$(.TARGET)"; case '%': + return "$(.MEMBER)"; case '*': + return "$(.PREFIX)"; case '!': - return TRUE; + return "$(.ARCHIVE)"; + } + } + return eflags & VARE_UNDEFERR ? var_Error : varUndefined; +} + +/* Parse a variable name, until the end character or a colon, whichever + * comes first. */ +static char * +ParseVarname(const char **pp, char startc, char endc, + GNode *ctxt, VarEvalFlags eflags, + size_t *out_varname_len) +{ + Buffer buf; + const char *p = *pp; + int depth = 1; + + Buf_Init(&buf, 0); + + while (*p != '\0') { + /* Track depth so we can spot parse errors. */ + if (*p == startc) + depth++; + if (*p == endc) { + if (--depth == 0) + break; + } + if (*p == ':' && depth == 1) + break; + + /* A variable inside a variable, expand. */ + if (*p == '$') { + void *freeIt; + const char *rval; + (void)Var_Parse(&p, ctxt, eflags, &rval, &freeIt); + /* TODO: handle errors */ + Buf_AddStr(&buf, rval); + free(freeIt); + } else { + Buf_AddByte(&buf, *p); + p++; + } + } + *pp = p; + *out_varname_len = Buf_Len(&buf); + return Buf_Destroy(&buf, FALSE); +} + +static Boolean +ValidShortVarname(char varname, const char *start) +{ + switch (varname) { + case '\0': + case ')': + case '}': + case ':': + case '$': + break; /* and continue below */ + default: + return TRUE; + } + + if (!DEBUG(LINT)) + return FALSE; + + if (varname == '$') + Parse_Error(PARSE_FATAL, + "To escape a dollar, use \\$, not $$, at \"%s\"", start); + else if (varname == '\0') + Parse_Error(PARSE_FATAL, "Dollar followed by nothing"); + else + Parse_Error(PARSE_FATAL, + "Invalid variable name '%c', at \"%s\"", varname, start); + + return FALSE; +} + +/* Parse a single-character variable name such as $V or $@. + * Return whether to continue parsing. */ +static Boolean +ParseVarnameShort( + char startc, + const char **pp, + GNode *ctxt, + VarEvalFlags eflags, + VarParseResult *out_FALSE_res, + const char **out_FALSE_val, + Var **out_TRUE_var +) { + char name[2]; + Var *v; + + /* + * If it's not bounded by braces of some sort, life is much simpler. + * We just need to check for the first character and return the + * value if it exists. + */ + + if (!ValidShortVarname(startc, *pp)) { + (*pp)++; + *out_FALSE_val = var_Error; + *out_FALSE_res = VPR_PARSE_MSG; + return FALSE; + } + + name[0] = startc; + name[1] = '\0'; + v = VarFind(name, ctxt, TRUE); + if (v == NULL) { + *pp += 2; + + *out_FALSE_val = UndefinedShortVarValue(startc, ctxt, eflags); + if (DEBUG(LINT) && *out_FALSE_val == var_Error) { + Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", name); + *out_FALSE_res = VPR_UNDEF_MSG; + return FALSE; } + *out_FALSE_res = eflags & VARE_UNDEFERR ? VPR_UNDEF_SILENT : VPR_OK; return FALSE; } - if ((namelen == 7 || namelen == 8) && varname[0] == '.' && - isupper((unsigned char)varname[1]) && - (ctxt == VAR_CMD || ctxt == VAR_GLOBAL)) + *out_TRUE_var = v; + return TRUE; +} + +/* Parse a long variable name enclosed in braces or parentheses such as $(VAR) + * or ${VAR}, up to the closing brace or parenthesis, or in the case of + * ${VAR:Modifiers}, up to the ':' that starts the modifiers. + * Return whether to continue parsing. */ +static Boolean +ParseVarnameLong( + const char **pp, + char startc, + GNode *ctxt, + VarEvalFlags eflags, + + VarParseResult *out_FALSE_res, + const char **out_FALSE_val, + void **out_FALSE_freePtr, + + char *out_TRUE_endc, + const char **out_TRUE_p, + Var **out_TRUE_v, + Boolean *out_TRUE_haveModifier, + const char **out_TRUE_extraModifiers, + Boolean *out_TRUE_dynamic, + VarExprFlags *out_TRUE_exprFlags +) { + size_t namelen; + char *varname; + Var *v; + Boolean haveModifier; + Boolean dynamic = FALSE; + + const char *const start = *pp; + char endc = startc == '(' ? ')' : '}'; + + const char *p = start + 2; + varname = ParseVarname(&p, startc, endc, ctxt, eflags, &namelen); + + if (*p == ':') { + haveModifier = TRUE; + } else if (*p == endc) { + haveModifier = FALSE; + } else { + Parse_Error(PARSE_FATAL, "Unclosed variable \"%s\"", varname); + *pp = p; + free(varname); + *out_FALSE_val = var_Error; + *out_FALSE_res = VPR_PARSE_MSG; + return FALSE; + } + + v = VarFind(varname, ctxt, TRUE); + + /* At this point, p points just after the variable name, + * either at ':' or at endc. */ + + /* + * Check also for bogus D and F forms of local variables since we're + * in a local context and the name is the right length. + */ + if (v == NULL && ctxt != VAR_CMDLINE && ctxt != VAR_GLOBAL && + namelen == 2 && (varname[1] == 'F' || varname[1] == 'D') && + strchr("@%?*!<>", varname[0]) != NULL) { - return strcmp(varname, ".TARGET") == 0 || - strcmp(varname, ".ARCHIVE") == 0 || - strcmp(varname, ".PREFIX") == 0 || - strcmp(varname, ".MEMBER") == 0; + /* + * Well, it's local -- go look for it. + */ + char name[] = { varname[0], '\0' }; + v = VarFind(name, ctxt, 0); + + if (v != NULL) { + if (varname[1] == 'D') { + *out_TRUE_extraModifiers = "H:"; + } else { /* F */ + *out_TRUE_extraModifiers = "T:"; + } + } } - return FALSE; + if (v == NULL) { + /* Defer expansion of dynamic variables if they appear in non-local + * context since they are not defined there. */ + dynamic = VarnameIsDynamic(varname, namelen) && + (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL); + + if (!haveModifier) { + p++; /* skip endc */ + *pp = p; + if (dynamic) { + char *pstr = bmake_strsedup(start, p); + free(varname); + *out_FALSE_res = VPR_OK; + *out_FALSE_freePtr = pstr; + *out_FALSE_val = pstr; + return FALSE; + } + + if ((eflags & VARE_UNDEFERR) && (eflags & VARE_WANTRES) && + DEBUG(LINT)) + { + Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", + varname); + free(varname); + *out_FALSE_res = VPR_UNDEF_MSG; + *out_FALSE_val = var_Error; + return FALSE; + } + + if (eflags & VARE_UNDEFERR) { + free(varname); + *out_FALSE_res = VPR_UNDEF_SILENT; + *out_FALSE_val = var_Error; + return FALSE; + } + + free(varname); + *out_FALSE_res = VPR_OK; + *out_FALSE_val = varUndefined; + return FALSE; + } + + /* The variable expression is based on an undefined variable. + * Nevertheless it needs a Var, for modifiers that access the + * variable name, such as :L or :?. + * + * Most modifiers leave this expression in the "undefined" state + * (VEF_UNDEF), only a few modifiers like :D, :U, :L, :P turn this + * undefined expression into a defined expression (VEF_DEF). + * + * At the end, after applying all modifiers, if the expression + * is still undefined, Var_Parse will return an empty string + * instead of the actually computed value. */ + v = VarNew(varname, varname, "", 0); + *out_TRUE_exprFlags = VEF_UNDEF; + } else + free(varname); + + *out_TRUE_endc = endc; + *out_TRUE_p = p; + *out_TRUE_v = v; + *out_TRUE_haveModifier = haveModifier; + *out_TRUE_dynamic = dynamic; + return TRUE; } /*- *----------------------------------------------------------------------- * Var_Parse -- - * Given the start of a variable invocation (such as $v, $(VAR), + * Given the start of a variable expression (such as $v, $(VAR), * ${VAR:Mpattern}), extract the variable name, possibly some * modifiers and find its value by applying the modifiers to the * original value. * + * When parsing a condition in ParseEmptyArg, pp may also point to + * the "y" of "empty(VARNAME:Modifiers)", which is syntactically + * identical. + * * Input: * str The string to parse * ctxt The context for the variable - * flags VARE_UNDEFERR if undefineds are an error - * VARE_WANTRES if we actually want the result - * VARE_ASSIGN if we are in a := assignment - * lengthPtr OUT: The length of the specification - * freePtr OUT: Non-NULL if caller should free *freePtr + * flags Select the exact details of parsing + * out_val_freeIt Must be freed by the caller after using out_val * * Results: * Returns the value of the variable expression, never NULL. - * var_Error if there was a parse error and VARE_UNDEFERR was set. - * varNoError if there was a parse error and VARE_UNDEFERR was not set. + * Returns var_Error if there was a parse error and VARE_UNDEFERR was + * set. + * Returns varUndefined if there was an undefined variable and + * VARE_UNDEFERR was not set. + * + * Parsing should continue at *pp. + * TODO: Document the value of *pp on parse errors. It might be advanced + * by 0, or +1, or the index of the parse error, or the guessed end of the + * variable expression. * - * Parsing should continue at str + *lengthPtr. + * If var_Error is returned, a diagnostic may or may not have been + * printed. XXX: This is inconsistent. * - * After using the returned value, *freePtr must be freed, preferably - * using bmake_free since it is NULL in most cases. + * If varUndefined is returned, a diagnostic may or may not have been + * printed. XXX: This is inconsistent. + * + * After using the returned value, *out_val_freeIt must be freed, + * preferably using bmake_free since it is NULL in most cases. * * Side Effects: * Any effects from the modifiers, such as :!cmd! or ::=value. *----------------------------------------------------------------------- */ /* coverity[+alloc : arg-*4] */ -const char * -Var_Parse(const char * const str, GNode *ctxt, VarEvalFlags eflags, - int *lengthPtr, void **freePtr) +VarParseResult +Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags, + const char **out_val, void **out_val_freeIt) { - const char *tstr; /* Pointer into str */ - Boolean haveModifier; /* TRUE if have modifiers for the variable */ - char startc; /* Starting character if variable in parens + const char *const start = *pp; + const char *p; + Boolean haveModifier; /* TRUE if have modifiers for the variable */ + char startc; /* Starting character if variable in parens * or braces */ - char endc; /* Ending character if variable in parens + char endc; /* Ending character if variable in parens * or braces */ - Boolean dynamic; /* TRUE if the variable is local and we're + Boolean dynamic; /* TRUE if the variable is local and we're * expanding it in a non-local context. This * is done to support dynamic sources. The - * result is just the invocation, unaltered */ + * result is just the expression, unaltered */ const char *extramodifiers; Var *v; char *nstr; char eflags_str[VarEvalFlags_ToStringSize]; + VarExprFlags exprFlags = 0; - VAR_DEBUG("%s: %s with %s\n", __func__, str, - Enum_FlagsToString(eflags_str, sizeof eflags_str, eflags, - VarEvalFlags_ToStringSpecs)); + VAR_DEBUG2("Var_Parse: %s with %s\n", start, + Enum_FlagsToString(eflags_str, sizeof eflags_str, eflags, + VarEvalFlags_ToStringSpecs)); - *freePtr = NULL; + *out_val_freeIt = NULL; extramodifiers = NULL; /* extra modifiers to apply first */ dynamic = FALSE; -#ifdef USE_DOUBLE_BOOLEAN - /* Appease GCC 5.5.0, which thinks that the variable might not be + /* Appease GCC, which thinks that the variable might not be * initialized. */ endc = '\0'; -#endif - - startc = str[1]; - if (startc != PROPEN && startc != BROPEN) { - char name[2]; - - /* - * If it's not bounded by braces of some sort, life is much simpler. - * We just need to check for the first character and return the - * value if it exists. - */ - - /* Error out some really stupid names */ - if (startc == '\0' || strchr(")}:$", startc)) { - *lengthPtr = 1; - return var_Error; - } - - name[0] = startc; - name[1] = '\0'; - v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); - if (v == NULL) { - *lengthPtr = 2; - if (ctxt == VAR_CMD || ctxt == VAR_GLOBAL) { - /* - * If substituting a local variable in a non-local context, - * assume it's for dynamic source stuff. We have to handle - * this specially and return the longhand for the variable - * with the dollar sign escaped so it makes it back to the - * caller. Only four of the local variables are treated - * specially as they are the only four that will be set - * when dynamic sources are expanded. - */ - switch (str[1]) { - case '@': - return "$(.TARGET)"; - case '%': - return "$(.MEMBER)"; - case '*': - return "$(.PREFIX)"; - case '!': - return "$(.ARCHIVE)"; - } - } - return (eflags & VARE_UNDEFERR) ? var_Error : varNoError; - } else { - haveModifier = FALSE; - tstr = str + 1; - } + startc = start[1]; + if (startc != '(' && startc != '{') { + VarParseResult res; + if (!ParseVarnameShort(startc, pp, ctxt, eflags, &res, out_val, &v)) + return res; + haveModifier = FALSE; + p = start + 1; } else { - Buffer namebuf; /* Holds the variable name */ - int depth; - size_t namelen; - char *varname; - - endc = startc == PROPEN ? PRCLOSE : BRCLOSE; - - Buf_Init(&namebuf, 0); - - /* - * Skip to the end character or a colon, whichever comes first. - */ - depth = 1; - for (tstr = str + 2; *tstr != '\0'; tstr++) { - /* Track depth so we can spot parse errors. */ - if (*tstr == startc) - depth++; - if (*tstr == endc) { - if (--depth == 0) - break; - } - if (*tstr == ':' && depth == 1) - break; - /* A variable inside a variable, expand. */ - if (*tstr == '$') { - int rlen; - void *freeIt; - const char *rval = Var_Parse(tstr, ctxt, eflags, &rlen, - &freeIt); - Buf_AddStr(&namebuf, rval); - free(freeIt); - tstr += rlen - 1; - } else - Buf_AddByte(&namebuf, *tstr); - } - if (*tstr == ':') { - haveModifier = TRUE; - } else if (*tstr == endc) { - haveModifier = FALSE; - } else { - Parse_Error(PARSE_FATAL, "Unclosed variable \"%s\"", - Buf_GetAll(&namebuf, NULL)); - /* - * If we never did find the end character, return NULL - * right now, setting the length to be the distance to - * the end of the string, since that's what make does. - */ - *lengthPtr = (int)(size_t)(tstr - str); - Buf_Destroy(&namebuf, TRUE); - return var_Error; - } - - varname = Buf_GetAll(&namebuf, &namelen); - - /* - * At this point, varname points into newly allocated memory from - * namebuf, containing only the name of the variable. - * - * start and tstr point into the const string that was pointed - * to by the original value of the str parameter. start points - * to the '$' at the beginning of the string, while tstr points - * to the char just after the end of the variable name -- this - * will be '\0', ':', PRCLOSE, or BRCLOSE. - */ - - v = VarFind(varname, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); - /* - * Check also for bogus D and F forms of local variables since we're - * in a local context and the name is the right length. - */ - if (v == NULL && ctxt != VAR_CMD && ctxt != VAR_GLOBAL && - namelen == 2 && (varname[1] == 'F' || varname[1] == 'D') && - strchr("@%?*!<>", varname[0]) != NULL) - { - /* - * Well, it's local -- go look for it. - */ - char name[] = { varname[0], '\0' }; - v = VarFind(name, ctxt, 0); - - if (v != NULL) { - if (varname[1] == 'D') { - extramodifiers = "H:"; - } else { /* F */ - extramodifiers = "T:"; - } - } - } - - if (v == NULL) { - dynamic = VarIsDynamic(ctxt, varname, namelen); - - if (!haveModifier) { - /* - * No modifiers -- have specification length so we can return - * now. - */ - *lengthPtr = (int)(size_t)(tstr - str) + 1; - if (dynamic) { - char *pstr = bmake_strldup(str, (size_t)*lengthPtr); - *freePtr = pstr; - Buf_Destroy(&namebuf, TRUE); - return pstr; - } else { - Buf_Destroy(&namebuf, TRUE); - return (eflags & VARE_UNDEFERR) ? var_Error : varNoError; - } - } else { - /* - * Still need to get to the end of the variable specification, - * so kludge up a Var structure for the modifications - */ - v = bmake_malloc(sizeof(Var)); - v->name = varname; - Buf_Init(&v->val, 1); - v->flags = VAR_JUNK; - Buf_Destroy(&namebuf, FALSE); - } - } else - Buf_Destroy(&namebuf, TRUE); + VarParseResult res; + if (!ParseVarnameLong(pp, startc, ctxt, eflags, + &res, out_val, out_val_freeIt, + &endc, &p, &v, &haveModifier, &extramodifiers, + &dynamic, &exprFlags)) + return res; } - if (v->flags & VAR_IN_USE) { + if (v->flags & VAR_IN_USE) Fatal("Variable %s is recursive.", v->name); - /*NOTREACHED*/ - } else { - v->flags |= VAR_IN_USE; - } /* * Before doing any modification, we have to make sure the value @@ -3561,13 +3869,17 @@ Var_Parse(const char * const str, GNode *ctxt, VarEvalFlags eflags, * return. */ nstr = Buf_GetAll(&v->val, NULL); - if (strchr(nstr, '$') != NULL && (eflags & VARE_WANTRES) != 0) { - nstr = Var_Subst(nstr, ctxt, eflags); - *freePtr = nstr; + if (strchr(nstr, '$') != NULL && (eflags & VARE_WANTRES)) { + VarEvalFlags nested_eflags = eflags; + if (DEBUG(LINT)) + nested_eflags &= ~(unsigned)VARE_UNDEFERR; + v->flags |= VAR_IN_USE; + (void)Var_Subst(nstr, ctxt, nested_eflags, &nstr); + v->flags &= ~(unsigned)VAR_IN_USE; + /* TODO: handle errors */ + *out_val_freeIt = nstr; } - v->flags &= ~(unsigned)VAR_IN_USE; - if (haveModifier || extramodifiers != NULL) { void *extraFree; @@ -3575,58 +3887,56 @@ Var_Parse(const char * const str, GNode *ctxt, VarEvalFlags eflags, if (extramodifiers != NULL) { const char *em = extramodifiers; nstr = ApplyModifiers(&em, nstr, '(', ')', - v, ctxt, eflags, &extraFree); + v, &exprFlags, ctxt, eflags, &extraFree); } if (haveModifier) { /* Skip initial colon. */ - tstr++; + p++; - nstr = ApplyModifiers(&tstr, nstr, startc, endc, - v, ctxt, eflags, freePtr); + nstr = ApplyModifiers(&p, nstr, startc, endc, + v, &exprFlags, ctxt, eflags, out_val_freeIt); free(extraFree); } else { - *freePtr = extraFree; + *out_val_freeIt = extraFree; } } - /* Skip past endc if possible. */ - *lengthPtr = (int)(size_t)(tstr + (*tstr ? 1 : 0) - str); + if (*p != '\0') /* Skip past endc if possible. */ + p++; + + *pp = p; if (v->flags & VAR_FROM_ENV) { - Boolean destroy = nstr != Buf_GetAll(&v->val, NULL); - if (!destroy) { - /* - * Returning the value unmodified, so tell the caller to free - * the thing. - */ - *freePtr = nstr; - } - (void)VarFreeEnv(v, destroy); - } else if (v->flags & VAR_JUNK) { - /* - * Perform any freeing needed and set *freePtr to NULL so the caller - * doesn't try to free a static pointer. - * If VAR_KEEP is also set then we want to keep str(?) as is. - */ - if (!(v->flags & VAR_KEEP)) { - if (*freePtr != NULL) { - free(*freePtr); - *freePtr = NULL; + /* Free the environment variable now since we own it, + * but don't free the variable value if it will be returned. */ + Boolean keepValue = nstr == Buf_GetAll(&v->val, NULL); + if (keepValue) + *out_val_freeIt = nstr; + (void)VarFreeEnv(v, !keepValue); + + } else if (exprFlags & VEF_UNDEF) { + if (!(exprFlags & VEF_DEF)) { + if (*out_val_freeIt != NULL) { + free(*out_val_freeIt); + *out_val_freeIt = NULL; } if (dynamic) { - nstr = bmake_strldup(str, (size_t)*lengthPtr); - *freePtr = nstr; + nstr = bmake_strsedup(start, p); + *out_val_freeIt = nstr; } else { - nstr = (eflags & VARE_UNDEFERR) ? var_Error : varNoError; + /* The expression is still undefined, therefore discard the + * actual value and return an error marker instead. */ + nstr = (eflags & VARE_UNDEFERR) ? var_Error : varUndefined; } } if (nstr != Buf_GetAll(&v->val, NULL)) Buf_Destroy(&v->val, TRUE); - free(v->name); + free(v->name_freeIt); free(v); } - return nstr; + *out_val = nstr; + return VPR_UNKNOWN; } /* Substitute for all variables in the given string in the given context. @@ -3647,11 +3957,11 @@ Var_Parse(const char * const str, GNode *ctxt, VarEvalFlags eflags, * Results: * The resulting string. */ -char * -Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags) +VarParseResult +Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res) { + const char *p = str; Buffer buf; /* Buffer for forming things */ - Boolean trailingBslash; /* Set true if an error has already been reported, * to prevent a plethora of messages when recursing */ @@ -3659,38 +3969,32 @@ Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags) Buf_Init(&buf, 0); errorReported = FALSE; - trailingBslash = FALSE; /* variable ends in \ */ - - while (*str) { - if (*str == '\n' && trailingBslash) - Buf_AddByte(&buf, ' '); - if (*str == '$' && str[1] == '$') { - /* - * A dollar sign may be escaped with another dollar sign. - * In such a case, we skip over the escape character and store the - * dollar sign into the buffer directly. - */ + while (*p != '\0') { + if (p[0] == '$' && p[1] == '$') { + /* A dollar sign may be escaped with another dollar sign. */ if (save_dollars && (eflags & VARE_ASSIGN)) Buf_AddByte(&buf, '$'); Buf_AddByte(&buf, '$'); - str += 2; - } else if (*str != '$') { + p += 2; + } else if (*p != '$') { /* * Skip as many characters as possible -- either to the end of - * the string or to the next dollar sign (variable invocation). + * the string or to the next dollar sign (variable expression). */ - const char *cp; + const char *plainStart = p; - for (cp = str++; *str != '$' && *str != '\0'; str++) + for (p++; *p != '$' && *p != '\0'; p++) continue; - Buf_AddBytesBetween(&buf, cp, str); + Buf_AddBytesBetween(&buf, plainStart, p); } else { - int length; + const char *nested_p = p; void *freeIt; - const char *val = Var_Parse(str, ctxt, eflags, &length, &freeIt); + const char *val; + (void)Var_Parse(&nested_p, ctxt, eflags, &val, &freeIt); + /* TODO: handle errors */ - if (val == var_Error || val == varNoError) { + if (val == var_Error || val == varUndefined) { /* * If performing old-time variable substitution, skip over * the variable and continue with the substitution. Otherwise, @@ -3698,7 +4002,7 @@ Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags) * the string... */ if (oldVars) { - str += length; + p = nested_p; } else if ((eflags & VARE_UNDEFERR) || val == var_Error) { /* * If variable is undefined, complain and skip the @@ -3707,41 +4011,41 @@ Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags) */ if (!errorReported) { Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"", - length, str); + (int)(size_t)(nested_p - p), p); } - str += length; + p = nested_p; errorReported = TRUE; } else { - Buf_AddByte(&buf, *str); - str += 1; + /* 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 { - size_t val_len; - - str += length; - - val_len = strlen(val); - Buf_AddBytes(&buf, val, val_len); - trailingBslash = val_len > 0 && val[val_len - 1] == '\\'; + p = nested_p; + Buf_AddStr(&buf, val); } free(freeIt); freeIt = NULL; } } - return Buf_DestroyCompact(&buf); + *out_res = Buf_DestroyCompact(&buf); + return VPR_OK; } -/* Initialize the module. */ +/* Initialize the variables module. */ void Var_Init(void) { VAR_INTERNAL = Targ_NewGN("Internal"); VAR_GLOBAL = Targ_NewGN("Global"); - VAR_CMD = Targ_NewGN("Command"); + VAR_CMDLINE = Targ_NewGN("Command"); } - +/* Clean up the variables module. */ void Var_End(void) { @@ -3751,21 +4055,32 @@ Var_End(void) void Var_Stats(void) { - Hash_DebugStats(&VAR_GLOBAL->context, "VAR_GLOBAL"); + HashTable_DebugStats(&VAR_GLOBAL->context, "VAR_GLOBAL"); } - -/****************** PRINT DEBUGGING INFO *****************/ -static void -VarPrintVar(void *vp, void *data MAKE_ATTR_UNUSED) -{ - Var *v = (Var *)vp; - fprintf(debug_file, "%-16s = %s\n", v->name, Buf_GetAll(&v->val, NULL)); -} - -/* Print all variables in a context, unordered. */ +/* Print all variables in a context, sorted by name. */ void Var_Dump(GNode *ctxt) { - Hash_ForEach(&ctxt->context, VarPrintVar, NULL); + Vector /* of const char * */ vec; + HashIter hi; + size_t i; + const char **varnames; + + Vector_Init(&vec, sizeof(const char *)); + + HashIter_Init(&hi, &ctxt->context); + while (HashIter_Next(&hi) != NULL) + *(const char **)Vector_Push(&vec) = hi.entry->key; + varnames = vec.items; + + qsort(varnames, vec.len, sizeof varnames[0], str_cmp_asc); + + for (i = 0; i < vec.len; i++) { + const char *varname = varnames[i]; + Var *var = HashTable_FindValue(&ctxt->context, varname); + debug_printf("%-16s = %s\n", varname, Buf_GetAll(&var->val, NULL)); + } + + Vector_Done(&vec); } |