From cdde9e894dee2074ef0dd12ddc171e5d3f1513e3 Mon Sep 17 00:00:00 2001 From: "Simon J. Gerraty" Date: Sat, 5 Feb 2022 12:03:50 -0800 Subject: [PATCH] Import bmake-20220204 Features of interest: Allow setting target local variables (similar to gmake) In META_MODE .MAKE.META.CMP_FILTER can be used for filtering commands before comparion. contrib/bmake/mk/cc-wrap.mk is an example of using these See ChangeLog for the gory details. --- ChangeLog | 155 ++ FILES | 41 +- VERSION | 2 +- arch.c | 90 +- bmake.1 | 51 +- bmake.cat1 | 39 +- buf.c | 15 +- buf.h | 19 +- compat.c | 93 +- cond.c | 427 ++--- dir.c | 21 +- dir.h | 14 +- filemon/filemon.h | 4 +- for.c | 228 ++- hash.c | 80 +- hash.h | 24 +- job.c | 99 +- job.h | 34 +- lst.h | 20 +- main.c | 548 +++--- make.1 | 51 +- make.c | 32 +- make.h | 672 +++++-- make_malloc.c | 12 +- make_malloc.h | 26 +- meta.c | 170 +- meta.h | 8 +- metachar.h | 12 +- mk/ChangeLog | 57 + mk/FILES | 1 + mk/cc-wrap.mk | 61 + mk/dirdeps.mk | 101 +- mk/host-target.mk | 7 +- mk/init.mk | 8 +- mk/install-mk | 4 +- mk/meta2deps.py | 38 +- mk/meta2deps.sh | 29 +- mk/mk-files.txt | 93 +- mk/sys.clean-env.mk | 6 +- nonints.h | 348 ---- parse.c | 1614 +++++++---------- str.c | 29 +- str.h | 46 +- suff.c | 66 +- targ.c | 40 +- trace.c | 17 +- unit-tests/Makefile | 30 +- unit-tests/comment.mk | 6 +- unit-tests/cond-func-empty.mk | 17 +- unit-tests/cond-func.exp | 10 +- unit-tests/cond-func.mk | 20 +- unit-tests/cond-op-parentheses.exp | 7 +- unit-tests/cond-op-parentheses.mk | 36 +- unit-tests/cond-short.mk | 12 +- unit-tests/cond-token-number.exp | 2 +- unit-tests/cond-token-number.mk | 15 +- unit-tests/cond-token-plain.exp | 3 +- unit-tests/cond-token-plain.mk | 49 +- unit-tests/cond-token-string.exp | 4 +- unit-tests/dep-duplicate.exp | 4 + unit-tests/dep-duplicate.mk | 27 + unit-tests/dep-op-missing.exp | 4 + unit-tests/dep-op-missing.mk | 13 + unit-tests/dep-wildcards.exp | 2 + unit-tests/dep.exp | 6 +- unit-tests/dep.mk | 16 +- unit-tests/depsrc-meta.exp | 2 + unit-tests/depsrc-meta.mk | 19 +- unit-tests/depsrc-use.mk | 7 +- unit-tests/depsrc-usebefore.mk | 7 +- unit-tests/depsrc.exp | 1 + unit-tests/depsrc.mk | 14 +- unit-tests/deptgt-error.exp | 10 +- unit-tests/deptgt-error.mk | 22 +- unit-tests/deptgt-ignore.exp | 12 +- unit-tests/deptgt-ignore.mk | 30 +- unit-tests/deptgt-interrupt.exp | 3 +- unit-tests/deptgt-interrupt.mk | 9 +- unit-tests/deptgt-main.exp | 1 + unit-tests/deptgt-main.mk | 27 +- unit-tests/deptgt-notparallel.exp | 8 + unit-tests/deptgt-notparallel.mk | 18 +- unit-tests/deptgt-order.exp | 7 + unit-tests/deptgt-order.mk | 4 +- unit-tests/deptgt-path-suffix.exp | 5 +- unit-tests/deptgt-path-suffix.mk | 10 +- unit-tests/deptgt.exp | 13 +- unit-tests/deptgt.mk | 11 +- unit-tests/directive-dinclude.exp | 5 +- unit-tests/directive-dinclude.mk | 25 +- unit-tests/directive-elifdef.mk | 21 +- unit-tests/directive-elifndef.mk | 23 +- unit-tests/directive-export-impl.exp | 16 +- unit-tests/directive-for-escape.exp | 102 +- unit-tests/directive-for-escape.mk | 62 +- unit-tests/directive-for-generating-endif.exp | 2 +- unit-tests/directive-for.exp | 18 + unit-tests/directive-for.mk | 81 +- unit-tests/directive-hyphen-include.exp | 5 +- unit-tests/directive-hyphen-include.mk | 22 +- unit-tests/directive-if.exp | 23 +- unit-tests/directive-if.mk | 15 +- unit-tests/directive-ifdef.exp | 3 - unit-tests/directive-ifdef.mk | 48 +- unit-tests/directive-ifmake.exp | 6 +- unit-tests/directive-ifmake.mk | 39 +- unit-tests/directive-include.exp | 2 + unit-tests/directive-include.mk | 33 +- unit-tests/directive-info.exp | 2 +- unit-tests/directive-info.mk | 13 +- unit-tests/directive-sinclude.exp | 5 +- unit-tests/directive-sinclude.mk | 18 +- unit-tests/directive-undef.exp | 2 +- unit-tests/directive-unexport-env.mk | 5 +- unit-tests/directive-warning.exp | 16 +- unit-tests/directive-warning.mk | 9 +- unit-tests/directive.exp | 10 +- unit-tests/directive.mk | 22 +- unit-tests/envfirst.mk | 44 - unit-tests/hanoi-include.mk | 6 +- unit-tests/include-main.exp | 12 +- unit-tests/include-main.mk | 4 +- unit-tests/include-sub.mk | 10 +- unit-tests/meta-cmd-cmp.exp | 16 + unit-tests/meta-cmd-cmp.mk | 36 +- unit-tests/modts.exp | 14 - unit-tests/modts.mk | 47 - unit-tests/modword.exp | 126 -- unit-tests/modword.mk | 161 -- unit-tests/opt-debug-cond.exp | 5 + unit-tests/opt-debug-cond.mk | 22 +- unit-tests/opt-debug-curdir.mk | 10 +- unit-tests/opt-debug-file.exp | 13 +- unit-tests/opt-debug-file.mk | 48 +- unit-tests/opt-debug-hash.exp | 7 +- unit-tests/opt-debug-hash.mk | 9 +- unit-tests/opt-debug-parse.exp | 25 + unit-tests/opt-debug-parse.mk | 34 +- unit-tests/opt-debug-var.exp | 6 + unit-tests/opt-debug-var.mk | 30 +- unit-tests/opt-debug-x-trace.exp | 2 + unit-tests/opt-debug-x-trace.mk | 10 +- unit-tests/opt-define.mk | 30 +- unit-tests/opt-env.exp | 6 +- unit-tests/opt-env.mk | 45 +- unit-tests/opt-jobs-internal.exp | 7 +- unit-tests/opt-jobs-internal.mk | 11 +- unit-tests/opt-raw.mk | 16 +- unit-tests/opt-silent.exp | 2 + unit-tests/opt-silent.mk | 8 +- unit-tests/{var-class.exp => opt-version.exp} | 1 + unit-tests/opt-version.mk | 12 + unit-tests/opt-where-am-i.exp | 3 + unit-tests/opt-where-am-i.mk | 14 +- unit-tests/parse.exp | 5 + unit-tests/parse.mk | 14 + unit-tests/posix.mk | 7 +- unit-tests/sh.mk | 5 +- unit-tests/suff-incomplete.exp | 12 +- unit-tests/suff-main-several.exp | 30 +- unit-tests/suff-rebuild.exp | 22 +- unit-tests/var-class-cmdline.exp | 4 - unit-tests/var-class-global.mk | 8 - unit-tests/var-class-local.exp | 5 - unit-tests/var-class-local.mk | 45 - unit-tests/var-class.mk | 9 - unit-tests/var-eval-short.exp | 16 +- unit-tests/var-eval-short.mk | 11 +- unit-tests/var-op-expand.mk | 4 +- unit-tests/var-op-shell.exp | 12 +- unit-tests/var-op-shell.mk | 15 +- unit-tests/var-op-sunsh.mk | 49 +- unit-tests/var-recursive.exp | 7 + unit-tests/var-recursive.mk | 16 +- unit-tests/var-scope-cmdline.exp | 4 + ...-class-cmdline.mk => var-scope-cmdline.mk} | 2 +- .../{envfirst.exp => var-scope-env.exp} | 0 .../{var-class-env.mk => var-scope-env.mk} | 2 +- ...var-class-env.exp => var-scope-global.exp} | 0 unit-tests/var-scope-global.mk | 18 + ...-global.exp => var-scope-local-legacy.exp} | 0 ...al-legacy.mk => var-scope-local-legacy.mk} | 2 +- unit-tests/var-scope-local.exp | 21 + unit-tests/var-scope-local.mk | 200 ++ ...r-class-local-legacy.exp => var-scope.exp} | 0 unit-tests/var-scope.mk | 9 + unit-tests/varmod-assign-shell.exp | 14 + unit-tests/varmod-assign-shell.mk | 36 + unit-tests/varmod-ifelse.mk | 6 +- unit-tests/varmod-indirect.exp | 12 +- unit-tests/varmod-indirect.mk | 6 +- unit-tests/varmod-loop.exp | 6 +- unit-tests/varmod-order-numeric.mk | 7 +- unit-tests/varmod-order.mk | 16 +- unit-tests/varmod-quote-dollar.exp | 1 + unit-tests/varmod-quote-dollar.mk | 6 +- unit-tests/varmod-select-words.exp | 125 ++ unit-tests/varmod-select-words.mk | 162 +- unit-tests/varmod-shell.exp | 10 + unit-tests/varmod-shell.mk | 10 +- unit-tests/varmod-sun-shell.exp | 11 + unit-tests/varmod-sun-shell.mk | 7 +- unit-tests/varmod-to-separator.exp | 26 +- unit-tests/varmod-to-separator.mk | 93 +- unit-tests/varname-dot-make-jobs.exp | 7 + unit-tests/varname-dot-make-jobs.mk | 24 +- unit-tests/varname-dot-make-pid.mk | 18 +- unit-tests/varname-dot-make-ppid.mk | 25 +- unit-tests/varname-dot-shell.exp | 12 +- unit-tests/varname-dot-suffixes.mk | 6 +- unit-tests/varname-empty.exp | 1 + unit-tests/varname-makeflags.mk | 20 +- unit-tests/varname.mk | 44 +- unit-tests/varparse-errors.exp | 6 + unit-tests/varparse-errors.mk | 22 +- unit-tests/varquote.mk | 6 +- util.c | 43 +- var.c | 696 ++++--- 218 files changed, 5446 insertions(+), 4172 deletions(-) create mode 100644 mk/cc-wrap.mk delete mode 100644 nonints.h create mode 100644 unit-tests/dep-duplicate.exp create mode 100644 unit-tests/dep-duplicate.mk create mode 100644 unit-tests/dep-op-missing.exp create mode 100644 unit-tests/dep-op-missing.mk delete mode 100644 unit-tests/envfirst.mk delete mode 100644 unit-tests/modts.exp delete mode 100644 unit-tests/modts.mk delete mode 100644 unit-tests/modword.exp delete mode 100644 unit-tests/modword.mk rename unit-tests/{var-class.exp => opt-version.exp} (93%) create mode 100644 unit-tests/opt-version.mk create mode 100644 unit-tests/parse.exp create mode 100644 unit-tests/parse.mk delete mode 100644 unit-tests/var-class-cmdline.exp delete mode 100644 unit-tests/var-class-global.mk delete mode 100644 unit-tests/var-class-local.exp delete mode 100644 unit-tests/var-class-local.mk delete mode 100644 unit-tests/var-class.mk create mode 100644 unit-tests/var-scope-cmdline.exp rename unit-tests/{var-class-cmdline.mk => var-scope-cmdline.mk} (98%) rename unit-tests/{envfirst.exp => var-scope-env.exp} (100%) rename unit-tests/{var-class-env.mk => var-scope-env.mk} (60%) rename unit-tests/{var-class-env.exp => var-scope-global.exp} (100%) create mode 100644 unit-tests/var-scope-global.mk rename unit-tests/{var-class-global.exp => var-scope-local-legacy.exp} (100%) rename unit-tests/{var-class-local-legacy.mk => var-scope-local-legacy.mk} (64%) create mode 100644 unit-tests/var-scope-local.exp create mode 100644 unit-tests/var-scope-local.mk rename unit-tests/{var-class-local-legacy.exp => var-scope.exp} (100%) create mode 100644 unit-tests/var-scope.mk create mode 100644 unit-tests/varmod-assign-shell.exp create mode 100644 unit-tests/varmod-assign-shell.mk diff --git a/ChangeLog b/ChangeLog index 5eb2d7c5bb4a..a87f39fc49c9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,158 @@ +2022-02-04 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20220204 + Merge with NetBSD make, pick up + o use unsigned consistently for line numbers, avoid the need for %z + o parse.c: do not step off end of input in Parse_IsVar + when checking for target local variable assignments + +2022-02-02 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20220202 + Merge with NetBSD make, pick up + o remove redundant declaration of HashIter_Init + o make DEBUG0 simpler + +2022-01-30 Simon J Gerraty + + * cast gn->lineno to avoid %z + + * VERSION (_MAKE_VERSION): 20220130 + Merge with NetBSD make, pick up + o more unit tests + o make GNode lineno unsigned to please lint + o print location of recursive variable references in commands + o print "stack trace" (makefile includes) on fatal errors + o make.1: refine documentation for target local assignments + +2022-01-28 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20220128 + Merge with NetBSD make, pick up + o inline functions called only once + o for.c: clean up AddEscape for building the body of a .for loop + o hash.c: merge duplicate code for finding an entry in a hash table + replace HashEntry_KeyEquals with strncmp + o make.1: document quirks of target local variable assignments. + o parse.c: cleanup white-space + +2022-01-26 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20220126 + Merge with NetBSD make, pick up + o allow setting target local variables + o more unit tests + o add missing newline after "cannot continue" message + o meta.c: clean up eat_dots + o parse.c: fix filename in warning about duplicate script + o var.c: when expanding nested variables, check simple things first + +2022-01-16 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20220116 + Merge with NetBSD make, pick up + o fix for unit-tests/varname-makeflags on non-BSD systems + o use Var_Exists rather than Var_Value where appropriate + o remove unnecessary functions for expanding variable names + o cond.c: inline EvalBare + o main.c: lint cleanup + o parse.c: condense code in Parse_IsVar + use islower for parsing directives (none have upper case) + +2022-01-12 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20220112 + Merge with NetBSD make, pick up + o meta.c: add .MAKE.META.CMP_FILTER for filtering commands before + comparion, rarely needed but useful when it is. + +2022-01-10 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20220110 + Merge with NetBSD make, pick up + o inline Buf_Clear + o remove redundant braces + o rename and inline Targ_Precious + o cond.c: remove redundant initializer in CondParser_ComparisonOrLeaf + o for.c: clean up handling of .for loops + fix reported line numbers of continuation lines + add details about .for loop variables to stack traces + o job.c: reduce code for initializing error handling in shell + o main.c: in Cmd_Exec, return error message instead of format string + have as few statements as possible between va_start and va_end + add debug logging for capturing the output of external commands + o make.c: use consistent variable names for varargs + o make_malloc.c: remove duplicate code from bmake_strdup + o parse.c: add missing printflike annotations + remove redundant lines from stack traces + fix stack traces in -dp mode + reduce confusing code in ParseForLoop + fix line number in debug log after returning from a file + rename IFile and its fields to match their actual content + clean up ParseDependencySources + o var.c: shorten ApplyModifier_Assign + rename is_shell_metachar, fix character conversion warning + merge calls to ApplyModifier_Time + merge duplicate code for modifiers 'gmtime' and 'localtime' + +2022-01-04 Simon J Gerraty + + * parse.c: loadfile restore extra byte in buffer. + +2022-01-01 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20220101 + Merge with NetBSD make, pick up + o more unit-tests + o remove unnecessary words from command line options in CmdOpts + o rename eunlink to unlink_file + o cond.c: make ParseWord in condition parser simpler + internally return false for irrelevant leaves in conditions + replace table for function lookup in conditions with simple code + merge duplicate types CondEvalResult and CondResult + o for.c: clean up handling of .for loops and .include directives + o main.c: constify cached_realpath + clean up Cmd_Exec + o parse.c: sync API documentation + fix error message when reading more than 1 GB from stdin + clean up parsing of makefiles + fix line number in error message about open conditionals + unexport types VarAssignOp and VarAssign + clean up function names + remove redundant parameters in dependency parsing functions + reduce scope of the list of wildcard target names + extract OP_NOTARGET into separate function + clean up variable names for parsing dependency lines + make debug logging a bit more human-friendly + o var.c: condense code in ApplyModifier_Assign + +2021-12-21 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20211221 + Merge with NetBSD make, pick up + o more unit-tests + o style cleanup + o in CLEANUP mode, free interned strings at the very end + o fix memory leak for filenames in .for loops + o buf.c: avoid memory leak + o cond.c: condense CondParser_ComparisonOp + o hash.c: change return type of HashTable_Set to void + o job.c: change return type of Compat_RunCommand from int to bool + o main.c: remove bmake_free + o parse.c: condense repetetive code in ParseDirective + remove dead code for handling traditional include directives + clean up parsing of variable assignments + remove unreachable code for parsing the dependency operator + clean up loading of files + fix memory leak in IncludeFile + o var.c: fix memory leak when parsing a variable name + fix memory leak from ${.SUFFIXES} + reduce memory allocation in modifier ':?' and ':C' + condense RegexReplace for the modifier ':C' and avoid strlen + merge duplicate code for memory handling in Var_Parse + distinguish between short-lived and environment variables + rename VarFreeEnv to VarFreeShortLived + 2021-12-15 Simon J Gerraty * cond.c: fix mem leak in CondParser_Leaf diff --git a/FILES b/FILES index 53b961468db6..8193debe5032 100644 --- a/FILES +++ b/FILES @@ -53,7 +53,6 @@ metachar.c metachar.h missing/sys/cdefs.h mkdeps.sh -nonints.h os.sh parse.c pathnames.h @@ -173,10 +172,14 @@ 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-duplicate.exp +unit-tests/dep-duplicate.mk unit-tests/dep-exclam.exp unit-tests/dep-exclam.mk unit-tests/dep-none.exp unit-tests/dep-none.mk +unit-tests/dep-op-missing.exp +unit-tests/dep-op-missing.mk unit-tests/dep-percent.exp unit-tests/dep-percent.mk unit-tests/dep-var.exp @@ -371,8 +374,6 @@ unit-tests/doterror.exp unit-tests/doterror.mk unit-tests/dotwait.exp unit-tests/dotwait.mk -unit-tests/envfirst.exp -unit-tests/envfirst.mk unit-tests/error.exp unit-tests/error.mk unit-tests/escape.exp @@ -427,10 +428,6 @@ unit-tests/modmatch.exp unit-tests/modmatch.mk unit-tests/modmisc.exp unit-tests/modmisc.mk -unit-tests/modts.exp -unit-tests/modts.mk -unit-tests/modword.exp -unit-tests/modword.mk unit-tests/objdir-writable.exp unit-tests/objdir-writable.mk unit-tests/opt-backwards.exp @@ -535,6 +532,8 @@ unit-tests/opt-var-expanded.exp unit-tests/opt-var-expanded.mk unit-tests/opt-var-literal.exp unit-tests/opt-var-literal.mk +unit-tests/opt-version.exp +unit-tests/opt-version.mk unit-tests/opt-warnings-as-errors.exp unit-tests/opt-warnings-as-errors.mk unit-tests/opt-where-am-i.exp @@ -547,6 +546,8 @@ unit-tests/order.exp unit-tests/order.mk unit-tests/parse-var.exp unit-tests/parse-var.mk +unit-tests/parse.exp +unit-tests/parse.mk unit-tests/phony-end.exp unit-tests/phony-end.mk unit-tests/posix.exp @@ -625,18 +626,6 @@ unit-tests/unexport.exp unit-tests/unexport.mk unit-tests/use-inference.exp unit-tests/use-inference.mk -unit-tests/var-class-cmdline.exp -unit-tests/var-class-cmdline.mk -unit-tests/var-class-env.exp -unit-tests/var-class-env.mk -unit-tests/var-class-global.exp -unit-tests/var-class-global.mk -unit-tests/var-class-local-legacy.exp -unit-tests/var-class-local-legacy.mk -unit-tests/var-class-local.exp -unit-tests/var-class-local.mk -unit-tests/var-class.exp -unit-tests/var-class.mk unit-tests/var-eval-short.exp unit-tests/var-eval-short.mk unit-tests/var-op-append.exp @@ -655,6 +644,18 @@ unit-tests/var-op.exp unit-tests/var-op.mk unit-tests/var-recursive.exp unit-tests/var-recursive.mk +unit-tests/var-scope-cmdline.exp +unit-tests/var-scope-cmdline.mk +unit-tests/var-scope-env.exp +unit-tests/var-scope-env.mk +unit-tests/var-scope-global.exp +unit-tests/var-scope-global.mk +unit-tests/var-scope-local-legacy.exp +unit-tests/var-scope-local-legacy.mk +unit-tests/var-scope-local.exp +unit-tests/var-scope-local.mk +unit-tests/var-scope.exp +unit-tests/var-scope.mk unit-tests/varcmd.exp unit-tests/varcmd.mk unit-tests/vardebug.exp @@ -663,6 +664,8 @@ unit-tests/varfind.exp unit-tests/varfind.mk unit-tests/varmisc.exp unit-tests/varmisc.mk +unit-tests/varmod-assign-shell.exp +unit-tests/varmod-assign-shell.mk unit-tests/varmod-assign.exp unit-tests/varmod-assign.mk unit-tests/varmod-defined.exp diff --git a/VERSION b/VERSION index 8485c4810bf0..ae363192cf1c 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20211212 +_MAKE_VERSION=20220204 diff --git a/arch.c b/arch.c index 515713af7af7..5e561b132f36 100644 --- a/arch.c +++ b/arch.c @@ -1,4 +1,4 @@ -/* $NetBSD: arch.c,v 1.205 2021/12/12 22:41:47 rillig Exp $ */ +/* $NetBSD: arch.c,v 1.210 2022/01/15 18:34:41 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -147,7 +147,7 @@ struct ar_hdr { #include "dir.h" /* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: arch.c,v 1.205 2021/12/12 22:41:47 rillig Exp $"); +MAKE_RCSID("$NetBSD: arch.c,v 1.210 2022/01/15 18:34:41 rillig Exp $"); typedef struct List ArchList; typedef struct ListNode ArchListNode; @@ -247,19 +247,20 @@ FullName(const char *archive, const char *member) bool Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) { - char *cp; /* Pointer into line */ + char *spec; /* For modifying some bytes of *pp */ + const char *cp; /* Pointer into line */ GNode *gn; /* New node */ - MFStr libName; /* Library-part of specification */ + FStr lib; /* Library-part of specification */ FStr mem; /* Member-part of specification */ char saveChar; /* Ending delimiter of member-name */ - bool expandLibName; /* Whether the parsed libName contains - * variable expressions that need to be - * expanded */ + bool expandLib; /* Whether the parsed lib contains variable + * expressions that need to be expanded */ - libName = MFStr_InitRefer(*pp); - expandLibName = false; + spec = *pp; + lib = FStr_InitRefer(spec); + expandLib = false; - for (cp = libName.str; *cp != '(' && *cp != '\0';) { + for (cp = lib.str; *cp != '(' && *cp != '\0';) { if (*cp == '$') { /* Expand nested variable expressions. */ /* XXX: This code can probably be shortened. */ @@ -276,20 +277,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) if (isError) return false; - expandLibName = true; + expandLib = true; cp += nested_p - cp; } else cp++; } - *cp++ = '\0'; - if (expandLibName) { - char *expanded; - (void)Var_Subst(libName.str, scope, VARE_UNDEFERR, &expanded); - /* TODO: handle errors */ - libName = MFStr_InitOwn(expanded); - } - + spec[cp++ - spec] = '\0'; + if (expandLib) + Var_Expand(&lib, scope, VARE_UNDEFERR); for (;;) { /* @@ -299,13 +295,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) */ bool doSubst = false; - pp_skip_whitespace(&cp); + cpp_skip_whitespace(&cp); mem = FStr_InitRefer(cp); while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) { if (*cp == '$') { /* Expand nested variable expressions. */ - /* XXX: This code can probably be shortened. */ + /* + * XXX: This code can probably be shortened. + */ FStr result; bool isError; const char *nested_p = cp; @@ -334,8 +332,8 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) */ if (*cp == '\0') { Parse_Error(PARSE_FATAL, - "No closing parenthesis " - "in archive specification"); + "No closing parenthesis " + "in archive specification"); return false; } @@ -346,7 +344,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) break; saveChar = *cp; - *cp = '\0'; + spec[cp - spec] = '\0'; /* * XXX: This should be taken care of intelligently by @@ -363,19 +361,16 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) */ if (doSubst) { char *fullName; - char *p, *expandedMem; + char *p; const char *unexpandedMem = mem.str; - (void)Var_Subst(mem.str, scope, VARE_UNDEFERR, - &expandedMem); - /* TODO: handle errors */ - mem = FStr_InitOwn(expandedMem); + Var_Expand(&mem, scope, VARE_UNDEFERR); /* * Now form an archive spec and recurse to deal with * nested variables and multi-word variable values. */ - fullName = FullName(libName.str, mem.str); + fullName = FullName(lib.str, mem.str); p = fullName; if (strcmp(mem.str, unexpandedMem) == 0) { @@ -404,7 +399,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) while (!Lst_IsEmpty(&members)) { char *member = Lst_Dequeue(&members); - char *fullname = FullName(libName.str, member); + char *fullname = FullName(lib.str, member); free(member); gn = Targ_GetNode(fullname); @@ -416,7 +411,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) Lst_Done(&members); } else { - char *fullname = FullName(libName.str, mem.str); + char *fullname = FullName(lib.str, mem.str); gn = Targ_GetNode(fullname); free(fullname); @@ -432,15 +427,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) } FStr_Done(&mem); - *cp = saveChar; + spec[cp - spec] = saveChar; } - MFStr_Done(&libName); + FStr_Done(&lib); cp++; /* skip the ')' */ /* We promised that pp would be set up at the next non-space. */ - pp_skip_whitespace(&cp); - *pp = cp; + cpp_skip_whitespace(&cp); + *pp += cp - *pp; return true; } @@ -672,7 +667,7 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch) if (ar->fnametab != NULL) { DEBUG0(ARCH, - "Attempted to redefine an SVR4 name table\n"); + "Attempted to redefine an SVR4 name table\n"); return -1; } @@ -693,8 +688,9 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch) entry++; *ptr = '\0'; } - DEBUG1(ARCH, "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; } @@ -708,7 +704,7 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch) } if (entry >= ar->fnamesize) { DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n", - inout_name, (unsigned long)ar->fnamesize); + inout_name, (unsigned long)ar->fnamesize); return 2; } @@ -737,8 +733,10 @@ ArchiveMember_HasName(const struct ar_hdr *hdr, if (ar_name[namelen] == ' ') return true; - /* In archives created by GNU binutils 2.27, the member names end with - * a slash. */ + /* + * In archives created by GNU binutils 2.27, the member names end + * with a slash. + */ if (ar_name[namelen] == '/' && (namelen == ar_name_len || ar_name[namelen + 1] == ' ')) return true; @@ -809,9 +807,9 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh, } DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n", - archive, - (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME, - (int)sizeof out_arh->ar_date, out_arh->ar_date); + archive, + (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME, + (int)sizeof out_arh->ar_date, out_arh->ar_date); if (ArchiveMember_HasName(out_arh, member, len)) { /* @@ -912,7 +910,7 @@ Arch_Touch(GNode *gn) struct ar_hdr arh; f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, - "r+"); + "r+"); if (f == NULL) return; diff --git a/bmake.1 b/bmake.1 index daea73cf538d..8f34441c34b4 100644 --- a/bmake.1 +++ b/bmake.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.300 2021/12/12 20:45:48 sjg Exp $ +.\" $NetBSD: make.1,v 1.304 2022/01/29 20:54:58 sjg 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 December 12, 2021 +.Dd January 28, 2022 .Dt BMAKE 1 .Os .Sh NAME @@ -691,10 +691,38 @@ Variables defined as part of the command line. Variables that are defined specific to a certain target. .El .Pp -Local variables are all built in and their values vary magically from -target to target. -It is not currently possible to define new local variables. -The seven local variables are as follows: +Local variables can be set on a dependency line, if +.Va .MAKE.TARGET_LOCAL_VARIABLES , +is not set to +.Ql false . +The rest of the line +(which will already have had Global variables expanded), +is the variable value. +For example: +.Bd -literal -offset indent +COMPILER_WRAPPERS+= ccache distcc icecc + +${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,} +.Ed +.Pp +Only the targets +.Ql ${OBJS} +will be impacted by that filter (in "meta" mode) and +simply enabling/disabling any of the wrappers will not render all +of those targets out-of-date. +.Pp +.Em NOTE : +target local variable assignments behave differently in that; +.Bl -tag -width Ds -offset indent +.It Ic \&+= +Only appends to a previous local assignment +for the same target and variable. +.It Ic \&:= +Is redundant with respect to Global variables, +which have already been expanded. +.El +.Pp +The seven built-in local variables are as follows: .Bl -tag -width ".ARCHIVE" -offset indent .It Va .ALLSRC The list of all sources for this target; also known as @@ -846,6 +874,11 @@ For example: would produce tokens like .Ql ---make[1234] target --- making it easier to track the degree of parallelism being achieved. +.It .MAKE.TARGET_LOCAL_VARIABLES +If set to +.Ql false , +apparent variable assignments in dependency lines are +treated as normal sources. .It Ev MAKEFLAGS The environment variable .Ql Ev MAKEFLAGS @@ -954,6 +987,12 @@ If a file that was generated outside of .Va .OBJDIR but within said bailiwick is missing, the current target is considered out-of-date. +.It Va .MAKE.META.CMP_FILTER +In "meta" mode, it can (very rarely!) be useful to filter command +lines before comparison. +This variable can be set to a set of modifiers that will be applied to +each line of the old and new command that differ, if the filtered +commands still differ, the target is considered out-of-date. .It Va .MAKE.META.CREATED In "meta" mode, this variable contains a list of all the meta files updated. diff --git a/bmake.cat1 b/bmake.cat1 index 73191dea7344..6aa6f382d54b 100644 --- a/bmake.cat1 +++ b/bmake.cat1 @@ -443,9 +443,28 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) Local variables Variables that are defined specific to a certain target. - Local variables are all built in and their values vary magically from - target to target. It is not currently possible to define new local vari- - ables. The seven local variables are as follows: + Local variables can be set on a dependency line, if + .MAKE.TARGET_LOCAL_VARIABLES, is not set to `false'. The rest of the + line (which will already have had Global variables expanded), is the + variable value. For example: + + COMPILER_WRAPPERS+= ccache distcc icecc + + ${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,} + + Only the targets `${OBJS}' will be impacted by that filter (in "meta" + mode) and simply enabling/disabling any of the wrappers will not render + all of those targets out-of-date. + + NOTE: target local variable assignments behave differently in that; + + += Only appends to a previous local assignment for the same + target and variable. + + := Is redundant with respect to Global variables, which have + already been expanded. + + The seven built-in local variables are as follows: .ALLSRC The list of all sources for this target; also known as `>'. @@ -538,6 +557,10 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) ing it easier to track the degree of parallelism being achieved. + .MAKE.TARGET_LOCAL_VARIABLES + If set to `false', apparent variable assignments in de- + pendency lines are treated as normal sources. + MAKEFLAGS The environment variable `MAKEFLAGS' may contain anything that may be specified on bmake's command line. Anything specified on bmake's command line is appended to the @@ -616,6 +639,14 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) generated outside of .OBJDIR but within said bailiwick is missing, the current target is considered out-of-date. + .MAKE.META.CMP_FILTER + In "meta" mode, it can (very rarely!) be useful to filter + command lines before comparison. This variable can be + set to a set of modifiers that will be applied to each + line of the old and new command that differ, if the fil- + tered commands still differ, the target is considered + out-of-date. + .MAKE.META.CREATED In "meta" mode, this variable contains a list of all the meta files updated. If not empty, it can be used to @@ -1591,4 +1622,4 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) There is no way of escaping a space character in a filename. -FreeBSD 13.0 December 12, 2021 FreeBSD 13.0 +FreeBSD 13.0 January 28, 2022 FreeBSD 13.0 diff --git a/buf.c b/buf.c index 8c26cfa24955..73b2eac17d4f 100644 --- a/buf.c +++ b/buf.c @@ -1,4 +1,4 @@ -/* $NetBSD: buf.c,v 1.53 2021/11/28 22:48:06 rillig Exp $ */ +/* $NetBSD: buf.c,v 1.55 2022/01/08 17:25:19 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -75,7 +75,7 @@ #include "make.h" /* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: buf.c,v 1.53 2021/11/28 22:48:06 rillig Exp $"); +MAKE_RCSID("$NetBSD: buf.c,v 1.55 2022/01/08 17:25:19 rillig Exp $"); /* Make space in the buffer for adding at least 16 more bytes. */ void @@ -138,14 +138,6 @@ Buf_AddFlag(Buffer *buf, bool flag, const char *name) } } -/* Mark the buffer as empty, so it can be filled with data again. */ -void -Buf_Empty(Buffer *buf) -{ - buf->len = 0; - buf->data[0] = '\0'; -} - /* Initialize a buffer. */ void Buf_InitSize(Buffer *buf, size_t cap) @@ -214,8 +206,9 @@ Buf_DoneDataCompact(Buffer *buf) if (buf->cap - buf->len >= BUF_COMPACT_LIMIT) { /* We trust realloc to be smart */ char *data = bmake_realloc(buf->data, buf->len + 1); + buf->data = NULL; data[buf->len] = '\0'; /* XXX: unnecessary */ - Buf_DoneData(buf); + Buf_Done(buf); return data; } #endif diff --git a/buf.h b/buf.h index af36773405b0..14d048bfa514 100644 --- a/buf.h +++ b/buf.h @@ -1,4 +1,4 @@ -/* $NetBSD: buf.h,v 1.44 2021/11/28 22:48:06 rillig Exp $ */ +/* $NetBSD: buf.h,v 1.47 2022/01/08 17:25:19 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -88,6 +88,14 @@ typedef struct Buffer { void Buf_Expand(Buffer *); +/* Mark the buffer as empty, so it can be filled with data again. */ +MAKE_INLINE void +Buf_Clear(Buffer *buf) +{ + buf->len = 0; + buf->data[0] = '\0'; +} + /* Buf_AddByte adds a single byte to a buffer. */ MAKE_INLINE void Buf_AddByte(Buffer *buf, char byte) @@ -101,7 +109,7 @@ Buf_AddByte(Buffer *buf, char byte) end[1] = '\0'; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE Buf_EndsWith(const Buffer *buf, char ch) { return buf->len > 0 && buf->data[buf->len - 1] == ch; @@ -112,11 +120,10 @@ void Buf_AddBytesBetween(Buffer *, const char *, const char *); void Buf_AddStr(Buffer *, const char *); void Buf_AddInt(Buffer *, int); void Buf_AddFlag(Buffer *, bool, const char *); -void Buf_Empty(Buffer *); void Buf_Init(Buffer *); void Buf_InitSize(Buffer *, size_t); void Buf_Done(Buffer *); -char *Buf_DoneData(Buffer *); -char *Buf_DoneDataCompact(Buffer *); +char *Buf_DoneData(Buffer *) MAKE_ATTR_USE; +char *Buf_DoneDataCompact(Buffer *) MAKE_ATTR_USE; -#endif /* MAKE_BUF_H */ +#endif diff --git a/compat.c b/compat.c index df487dbdf254..ad280206157a 100644 --- a/compat.c +++ b/compat.c @@ -1,4 +1,4 @@ -/* $NetBSD: compat.c,v 1.229 2021/11/28 23:12:51 rillig Exp $ */ +/* $NetBSD: compat.c,v 1.238 2022/01/22 18:59:23 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -99,7 +99,7 @@ #include "pathnames.h" /* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: compat.c,v 1.229 2021/11/28 23:12:51 rillig Exp $"); +MAKE_RCSID("$NetBSD: compat.c,v 1.238 2022/01/22 18:59:23 rillig Exp $"); static GNode *curTarg = NULL; static pid_t compatChild; @@ -112,10 +112,10 @@ static int compatSigno; static void CompatDeleteTarget(GNode *gn) { - if (gn != NULL && !Targ_Precious(gn)) { + if (gn != NULL && !GNode_IsPrecious(gn)) { const char *file = GNode_VarTarget(gn); - if (!opts.noExecute && eunlink(file) != -1) { + if (!opts.noExecute && unlink_file(file)) { Error("*** %s removed", file); } } @@ -135,7 +135,7 @@ CompatInterrupt(int signo) { CompatDeleteTarget(curTarg); - if (curTarg != NULL && !Targ_Precious(curTarg)) { + if (curTarg != NULL && !GNode_IsPrecious(curTarg)) { /* * Run .INTERRUPT only if hit with interrupt signal */ @@ -168,10 +168,12 @@ DebugFailedTarget(const char *cmd, const GNode *gn) { const char *p = cmd; debug_printf("\n*** Failed target: %s\n*** Failed command: ", - gn->name); + gn->name); - /* Replace runs of whitespace with a single space, to reduce - * the amount of whitespace for multi-line command lines. */ + /* + * Replace runs of whitespace with a single space, to reduce the + * amount of whitespace for multi-line command lines. + */ while (*p != '\0') { if (ch_isspace(*p)) { debug_printf(" "); @@ -220,24 +222,24 @@ UseShell(const char *cmd MAKE_ATTR_UNUSED) * ln List node that contains the command * * Results: - * 0 if the command succeeded, 1 if an error occurred. + * true if the command succeeded. */ -int +bool Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) { char *cmdStart; /* Start of expanded command */ char *bp; bool silent; /* Don't print command */ bool doIt; /* Execute even if -n */ - volatile bool errCheck; /* Check errors */ + volatile bool errCheck; /* Check errors */ WAIT_T reason; /* Reason for child's death */ WAIT_T status; /* Description of child's death */ pid_t cpid; /* Child actually found */ pid_t retstat; /* Result of wait */ const char **volatile av; /* Argument vector for thing to exec */ char **volatile mav; /* Copy of the argument vector for freeing */ - bool useShell; /* True if command should be executed - * using a shell */ + bool useShell; /* True if command should be executed using a + * shell */ const char *volatile cmd = cmdp; silent = (gn->type & OP_SILENT) != OP_NONE; @@ -249,7 +251,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) if (cmdStart[0] == '\0') { free(cmdStart); - return 0; + return true; } cmd = cmdStart; LstNode_Set(ln, cmdStart); @@ -269,12 +271,12 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) * usual '$$'. */ Lst_Append(&endNode->commands, cmdStart); - return 0; + return true; } } if (strcmp(cmdStart, "...") == 0) { gn->type |= OP_SAVE_CMDS; - return 0; + return true; } for (;;) { @@ -298,7 +300,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) * If we did not end up with a command, just skip it. */ if (cmd[0] == '\0') - return 0; + return true; useShell = UseShell(cmd); /* @@ -315,7 +317,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) * we go... */ if (!doIt && !GNode_ShouldExecute(gn)) - return 0; + return true; DEBUG1(JOB, "Execute: '%s'\n", cmd); @@ -350,25 +352,20 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) } #ifdef USE_META - if (useMeta) { + if (useMeta) meta_compat_start(); - } #endif Var_ReexportVars(); - /* - * Fork and execute the single command. If the fork fails, we abort. - */ compatChild = cpid = vfork(); - if (cpid < 0) { + if (cpid < 0) Fatal("Could not fork"); - } + if (cpid == 0) { #ifdef USE_META - if (useMeta) { + if (useMeta) meta_compat_child(); - } #endif (void)execvp(av[0], (char *const *)UNCONST(av)); execDie("exec", av[0]); @@ -382,9 +379,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) LstNode_SetNull(ln); #ifdef USE_META - if (useMeta) { + if (useMeta) meta_compat_parent(cpid); - } #endif /* @@ -406,9 +402,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) } 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)) @@ -424,9 +419,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) if (!WIFEXITED(reason) || status != 0) { if (errCheck) { #ifdef USE_META - if (useMeta) { + if (useMeta) meta_job_error(NULL, gn, false, status); - } #endif gn->made = ERROR; if (opts.keepgoing) { @@ -457,7 +451,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) kill(myPid, compatSigno); } - return status; + return status == 0; } static void @@ -467,7 +461,7 @@ RunCommands(GNode *gn) for (ln = gn->commands.first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; - if (Compat_RunCommand(cmd, gn, ln) != 0) + if (!Compat_RunCommand(cmd, gn, ln)) break; } } @@ -532,7 +526,7 @@ MakeUnmade(GNode *gn, GNode *pgn) * to tell him/her "yes". */ DEBUG0(MAKE, "out-of-date.\n"); - if (opts.queryFlag) + if (opts.query) exit(1); /* @@ -547,7 +541,7 @@ MakeUnmade(GNode *gn, GNode *pgn) */ if (opts.ignoreErrors) gn->type |= OP_IGNORE; - if (opts.beSilent) + if (opts.silent) gn->type |= OP_SILENT; if (Job_CheckCommands(gn, Fatal)) { @@ -555,12 +549,11 @@ MakeUnmade(GNode *gn, GNode *pgn) * Our commands are ok, but we still have to worry about * the -t flag. */ - if (!opts.touchFlag || (gn->type & OP_MAKE)) { + if (!opts.touch || (gn->type & OP_MAKE)) { curTarg = gn; #ifdef USE_META - if (useMeta && GNode_ShouldExecute(gn)) { + if (useMeta && GNode_ShouldExecute(gn)) meta_job_start(NULL, gn); - } #endif RunCommands(gn); curTarg = NULL; @@ -593,7 +586,7 @@ MakeUnmade(GNode *gn, GNode *pgn) } else if (opts.keepgoing) { pgn->flags.remake = false; } else { - PrintOnError(gn, "\nStop."); + PrintOnError(gn, "\nStop.\n"); exit(1); } return true; @@ -681,7 +674,7 @@ MakeBeginNode(void) Compat_Make(gn, gn); if (GNode_IsError(gn)) { - PrintOnError(gn, "\nStop."); + PrintOnError(gn, "\nStop.\n"); exit(1); } } @@ -715,12 +708,14 @@ Compat_Run(GNodeList *targs) InitSignals(); - /* Create the .END node now, to keep the (debug) output of the - * counter.mk test the same as before 2020-09-23. This implementation - * detail probably doesn't matter though. */ + /* + * Create the .END node now, to keep the (debug) output of the + * counter.mk test the same as before 2020-09-23. This + * implementation detail probably doesn't matter though. + */ (void)Targ_GetEndNode(); - if (!opts.queryFlag) + if (!opts.query) MakeBeginNode(); /* @@ -737,7 +732,7 @@ Compat_Run(GNodeList *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); + gn->name); } if (GNode_IsError(gn) && errorNode == NULL) errorNode = gn; @@ -756,7 +751,7 @@ Compat_Run(GNodeList *targs) Targ_PrintGraph(2); else if (DEBUG(GRAPH3)) Targ_PrintGraph(3); - PrintOnError(errorNode, "\nStop."); + PrintOnError(errorNode, "\nStop.\n"); exit(1); } } diff --git a/cond.c b/cond.c index 96609682866b..df0129a979a9 100644 --- a/cond.c +++ b/cond.c @@ -1,4 +1,4 @@ -/* $NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.327 2022/01/29 01:12:36 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -95,10 +95,10 @@ #include "dir.h" /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.327 2022/01/29 01:12:36 rillig Exp $"); /* - * The parsing of conditional expressions is based on this grammar: + * Conditional expressions conform to this grammar: * Or -> And ('||' And)* * And -> Term ('&&' Term)* * Term -> Function '(' Argument ')' @@ -109,13 +109,13 @@ MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $"); * Leaf -> "string" * Leaf -> Number * Leaf -> VariableExpression - * Leaf -> Symbol + * Leaf -> BareWord * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' * - * 'Symbol' is an unquoted string literal to which the default function is - * applied. + * BareWord is an unquoted string literal, its evaluation depends on the kind + * of '.if' directive. * - * The tokens are scanned by CondToken, which returns: + * The tokens are scanned by CondParser_Token, which returns: * TOK_AND for '&&' * TOK_OR for '||' * TOK_NOT for '!' @@ -123,18 +123,14 @@ MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $"); * 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. + * the function given in the terminal, they return either TOK_TRUE, TOK_FALSE + * or TOK_ERROR. */ typedef enum Token { TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT, TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR } Token; -typedef enum CondResult { - CR_FALSE, CR_TRUE, CR_ERROR -} CondResult; - typedef enum ComparisonOp { LT, LE, GT, GE, EQ, NE } ComparisonOp; @@ -186,10 +182,14 @@ static unsigned int cond_min_depth = 0; /* depth at makefile open */ /* Names for ComparisonOp. */ static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" }; -static bool -is_token(const char *str, const char *tok, unsigned char len) +MAKE_INLINE bool +skip_string(const char **pp, const char *str) { - return strncmp(str, tok, (size_t)len) == 0 && !ch_isalpha(str[len]); + size_t len = strlen(str); + bool ok = strncmp(*pp, str, len) == 0; + if (ok) + *pp += len; + return ok; } static Token @@ -208,31 +208,13 @@ CondParser_SkipWhitespace(CondParser *par) * Parse a single word, taking into account balanced parentheses as well as * embedded expressions. Used for the argument of a built-in function as * well as for bare words, which are then passed to the default 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 - * NULL when parsing a bare word. - * - * Return the length of the argument, or an ambiguous 0 on error. */ -static size_t -ParseWord(CondParser *par, const char **pp, bool doEval, const char *func, - char **out_arg) +static char * +ParseWord(const char **pp, bool doEval) { const char *p = *pp; Buffer argBuf; int paren_depth; - size_t argLen; - - if (func != NULL) - p++; /* Skip opening '(' - verified by caller */ - - cpp_skip_hspace(&p); Buf_InitSize(&argBuf, 16); @@ -243,7 +225,7 @@ ParseWord(CondParser *par, const char **pp, bool doEval, const char *func, break; if ((ch == '&' || ch == '|') && paren_depth == 0) break; - if (*p == '$') { + if (ch == '$') { /* * Parse the variable expression and install it as * part of the argument if it's valid. We tell @@ -266,58 +248,73 @@ ParseWord(CondParser *par, const char **pp, bool doEval, const char *func, paren_depth++; else if (ch == ')' && --paren_depth < 0) break; - Buf_AddByte(&argBuf, *p); + Buf_AddByte(&argBuf, ch); p++; } - argLen = argBuf.len; - *out_arg = Buf_DoneData(&argBuf); + cpp_skip_hspace(&p); + *pp = p; + return Buf_DoneData(&argBuf); +} + +/* Parse the function argument, including the surrounding parentheses. */ +static char * +ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func) +{ + const char *p = *pp; + char *res; + + p++; /* Skip opening '(' - verified by caller */ + cpp_skip_hspace(&p); + res = ParseWord(&p, doEval); cpp_skip_hspace(&p); - if (func != NULL && *p++ != ')') { + if (*p++ != ')') { + int len = 0; + while (ch_isalpha(func[len])) + len++; + Parse_Error(PARSE_FATAL, - "Missing closing parenthesis for %s()", func); + "Missing closing parenthesis for %.*s()", len, func); par->printedError = true; - return 0; + free(res); + return NULL; } *pp = p; - return argLen; + return res; } /* Test whether the given variable is defined. */ static bool -FuncDefined(const char *arg) +FuncDefined(const char *var) { - FStr value = Var_Value(SCOPE_CMDLINE, arg); - bool result = value.str != NULL; - FStr_Done(&value); - return result; + return Var_Exists(SCOPE_CMDLINE, var); } /* See if the given target is requested to be made. */ static bool -FuncMake(const char *arg) +FuncMake(const char *target) { StringListNode *ln; for (ln = opts.create.first; ln != NULL; ln = ln->next) - if (Str_Match(ln->datum, arg)) + if (Str_Match(ln->datum, target)) return true; return false; } /* See if the given file exists. */ static bool -FuncExists(const char *arg) +FuncExists(const char *file) { bool result; char *path; - path = Dir_FindFile(arg, &dirSearchPath); + path = Dir_FindFile(file, &dirSearchPath); DEBUG2(COND, "exists(%s) result is \"%s\"\n", - arg, path != NULL ? path : ""); + file, path != NULL ? path : ""); result = path != NULL; free(path); return result; @@ -325,9 +322,9 @@ FuncExists(const char *arg) /* See if the given node exists and is an actual target. */ static bool -FuncTarget(const char *arg) +FuncTarget(const char *node) { - GNode *gn = Targ_FindNode(arg); + GNode *gn = Targ_FindNode(node); return gn != NULL && GNode_IsTarget(gn); } @@ -336,20 +333,16 @@ FuncTarget(const char *arg) * associated with it. */ static bool -FuncCommands(const char *arg) +FuncCommands(const char *node) { - GNode *gn = Targ_FindNode(arg); - return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands); + GNode *gn = Targ_FindNode(node); + return gn != NULL && GNode_IsTarget(gn) && + !Lst_IsEmpty(&gn->commands); } /* - * Convert the given number into a double. - * We try a base 10 or 16 integer conversion first, if that fails - * then we try a floating point conversion instead. - * - * Results: - * Returns true if the conversion succeeded. - * Sets 'out_value' to the converted number. + * Convert the string into a floating-point number. Accepted formats are + * base-10 integer, base-16 integer and finite floating point numbers. */ static bool TryParseNumber(const char *str, double *out_value) @@ -399,7 +392,7 @@ CondParser_StringExpr(CondParser *par, const char *start, Buffer *buf, FStr *inout_str) { VarEvalMode emode; - const char *nested_p; + const char *p; bool atStart; VarParseResult parseResult; @@ -407,9 +400,9 @@ CondParser_StringExpr(CondParser *par, const char *start, : doEval ? VARE_UNDEFERR : VARE_PARSE_ONLY; - nested_p = par->p; - atStart = nested_p == start; - parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, emode, inout_str); + p = par->p; + atStart = p == start; + parseResult = Var_Parse(&p, SCOPE_CMDLINE, emode, inout_str); /* TODO: handle errors */ if (inout_str->str == var_Error) { if (parseResult == VPR_ERR) { @@ -433,7 +426,7 @@ CondParser_StringExpr(CondParser *par, const char *start, *inout_str = FStr_InitRefer(NULL); return false; } - par->p = nested_p; + par->p = p; /* * If the '$' started the string literal (which means no quotes), and @@ -445,17 +438,16 @@ CondParser_StringExpr(CondParser *par, const char *start, Buf_AddStr(buf, inout_str->str); FStr_Done(inout_str); - *inout_str = FStr_InitRefer(NULL); /* not finished yet */ + *inout_str = FStr_InitRefer(NULL); /* not finished yet */ return true; } /* - * Parse a string from a variable expression or an optionally quoted - * string. This is called for the left-hand and right-hand sides of - * comparisons. + * Parse a string from a variable expression or an optionally quoted string, + * on the left-hand and right-hand sides of comparisons. * * Results: - * Returns the string, absent any quotes, or NULL on error. + * Returns the string without any enclosing quotes, or NULL on error. * Sets out_quoted if the leaf was a quoted string literal. */ static void @@ -486,7 +478,7 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, case '"': par->p++; if (quoted) - goto got_str; /* skip the closing quote */ + goto return_buf; /* skip the closing quote */ Buf_AddByte(&buf, '"'); continue; case ')': /* see is_separator */ @@ -497,14 +489,14 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, case ' ': case '\t': if (!quoted) - goto got_str; + goto return_buf; Buf_AddByte(&buf, par->p[0]); par->p++; continue; case '$': if (!CondParser_StringExpr(par, start, doEval, quoted, &buf, &str)) - goto cleanup; + goto return_str; continue; default: if (!unquotedOK && !quoted && *start != '$' && @@ -514,28 +506,21 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, * a variable expression or a number. */ str = FStr_InitRefer(NULL); - goto cleanup; + goto return_str; } Buf_AddByte(&buf, par->p[0]); par->p++; continue; } } -got_str: +return_buf: str = FStr_InitOwn(buf.data); buf.data = NULL; -cleanup: +return_str: Buf_Done(&buf); *out_str = str; } -static bool -EvalBare(const CondParser *par, const char *arg) -{ - bool res = par->evalBare(arg); - return par->negateEvalBare ? !res : res; -} - /* * Evaluate a "comparison without operator", such as in ".if ${VAR}" or * ".if 0". @@ -553,9 +538,11 @@ EvalNotEmpty(CondParser *par, const char *value, bool quoted) if (TryParseNumber(value, &num)) return num != 0.0; - /* For .if ${...}, check for non-empty string. This is different from - * the evaluation function from that .if variant, which would test - * whether a variable of the given name were defined. */ + /* + * For .if ${...}, check for non-empty string. This is different + * from the evaluation function from that .if variant, which would + * test whether a variable of the given name were defined. + */ /* * XXX: Whitespace should count as empty, just as in * CondParser_FuncCallEmpty. @@ -563,7 +550,7 @@ EvalNotEmpty(CondParser *par, const char *value, bool quoted) if (par->plain) return value[0] != '\0'; - return EvalBare(par, value); + return par->evalBare(value) != par->negateEvalBare; } /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ @@ -623,33 +610,19 @@ CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) { const char *p = par->p; - if (p[0] == '<' && p[1] == '=') { - *out_op = LE; - goto length_2; - } else if (p[0] == '<') { - *out_op = LT; - goto length_1; - } else if (p[0] == '>' && p[1] == '=') { - *out_op = GE; - goto length_2; - } else if (p[0] == '>') { - *out_op = GT; - goto length_1; - } else if (p[0] == '=' && p[1] == '=') { - *out_op = EQ; - goto length_2; - } else if (p[0] == '!' && p[1] == '=') { - *out_op = NE; - goto length_2; - } + if (p[0] == '<' && p[1] == '=') + return par->p += 2, *out_op = LE, true; + if (p[0] == '<') + return par->p += 1, *out_op = LT, true; + if (p[0] == '>' && p[1] == '=') + return par->p += 2, *out_op = GE, true; + if (p[0] == '>') + return par->p += 1, *out_op = GT, true; + if (p[0] == '=' && p[1] == '=') + return par->p += 2, *out_op = EQ, true; + if (p[0] == '!' && p[1] == '=') + return par->p += 2, *out_op = NE, true; return false; - -length_2: - par->p = p + 2; - return true; -length_1: - par->p = p + 1; - return true; } /* @@ -718,9 +691,8 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) Token tok; FStr val; - if (!is_token(cp, "empty", 5)) + if (!skip_string(&cp, "empty")) return false; - cp += 5; cpp_skip_whitespace(&cp); if (*cp != '(') @@ -735,7 +707,7 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) tok = TOK_ERROR; else { cpp_skip_whitespace(&val.str); - tok = val.str[0] != '\0' && doEval ? TOK_FALSE : TOK_TRUE; + tok = ToToken(doEval && val.str[0] == '\0'); } FStr_Done(&val); @@ -748,37 +720,34 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) static bool CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) { - static const struct fn_def { - const char fn_name[9]; - unsigned char fn_name_len; - bool (*fn_eval)(const char *); - } fns[] = { - { "defined", 7, FuncDefined }, - { "make", 4, FuncMake }, - { "exists", 6, FuncExists }, - { "target", 6, FuncTarget }, - { "commands", 8, FuncCommands } - }; - const struct fn_def *fn; - char *arg = NULL; - size_t arglen; - const char *cp = par->p; - const struct fn_def *last_fn = fns + sizeof fns / sizeof fns[0] - 1; + char *arg; + const char *p = par->p; + bool (*fn)(const char *); + const char *fn_name = p; - for (fn = fns; !is_token(cp, fn->fn_name, fn->fn_name_len); fn++) - if (fn == last_fn) - return false; - - cp += fn->fn_name_len; - cpp_skip_whitespace(&cp); - if (*cp != '(') + if (skip_string(&p, "defined")) + fn = FuncDefined; + else if (skip_string(&p, "make")) + fn = FuncMake; + else if (skip_string(&p, "exists")) + fn = FuncExists; + else if (skip_string(&p, "target")) + fn = FuncTarget; + else if (skip_string(&p, "commands")) + fn = FuncCommands; + else return false; - arglen = ParseWord(par, &cp, doEval, fn->fn_name, &arg); - *out_token = ToToken(arglen != 0 && (!doEval || fn->fn_eval(arg))); + cpp_skip_whitespace(&p); + if (*p != '(') + return false; + arg = ParseFuncArg(par, &p, doEval, fn_name); + *out_token = ToToken(doEval && + arg != NULL && arg[0] != '\0' && fn(arg)); free(arg); - par->p = cp; + + par->p = p; return true; } @@ -793,9 +762,8 @@ static Token CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) { Token t; - char *arg = NULL; + char *arg; const char *cp; - const char *cp1; /* Push anything numeric through the compare expression */ cp = par->p; @@ -811,13 +779,13 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) * as an expression. */ /* - * XXX: Is it possible to have a variable expression evaluated twice - * at this point? + * XXX: In edge cases, a variable expression may be evaluated twice, + * see cond-token-plain.mk, keyword 'twice'. */ - (void)ParseWord(par, &cp, doEval, NULL, &arg); - cp1 = cp; - cpp_skip_whitespace(&cp1); - if (*cp1 == '=' || *cp1 == '!' || *cp1 == '<' || *cp1 == '>') + arg = ParseWord(&cp, doEval); + assert(arg[0] != '\0'); + + if (*cp == '=' || *cp == '!' || *cp == '<' || *cp == '>') return CondParser_Comparison(par, doEval); par->p = cp; @@ -827,7 +795,7 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) * after .if must have been taken literally, so the argument cannot * be empty - even if it contained a variable expansion. */ - t = ToToken(!doEval || EvalBare(par, arg)); + t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare); free(arg); return t; } @@ -998,42 +966,32 @@ CondParser_Or(CondParser *par, bool doEval) return res; } -static CondEvalResult -CondParser_Eval(CondParser *par, bool *out_value) +static CondResult +CondParser_Eval(CondParser *par) { CondResult res; DEBUG1(COND, "CondParser_Eval: %s\n", par->p); res = CondParser_Or(par, true); - if (res == CR_ERROR) - return COND_INVALID; + if (res != CR_ERROR && CondParser_Token(par, false) != TOK_EOF) + return CR_ERROR; - if (CondParser_Token(par, false) != TOK_EOF) - return COND_INVALID; - - *out_value = res == CR_TRUE; - return COND_PARSE; + return res; } /* * 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. - * - * *out_value is set to the boolean value of the condition */ -static CondEvalResult -CondEvalExpression(const char *cond, bool *out_value, bool plain, +static CondResult +CondEvalExpression(const char *cond, bool plain, bool (*evalBare)(const char *), bool negate, bool eprint, bool leftUnquotedOK) { CondParser par; - CondEvalResult rval; + CondResult rval; cpp_skip_hspace(&cond); @@ -1045,9 +1003,9 @@ CondEvalExpression(const char *cond, bool *out_value, bool plain, par.curr = TOK_NONE; par.printedError = false; - rval = CondParser_Eval(&par, out_value); + rval = CondParser_Eval(&par); - if (rval == COND_INVALID && eprint && !par.printedError) + if (rval == CR_ERROR && eprint && !par.printedError) Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); return rval; @@ -1057,10 +1015,10 @@ CondEvalExpression(const char *cond, bool *out_value, bool plain, * Evaluate a condition in a :? modifier, such as * ${"${VAR}" == value:?yes:no}. */ -CondEvalResult -Cond_EvalCondition(const char *cond, bool *out_value) +CondResult +Cond_EvalCondition(const char *cond) { - return CondEvalExpression(cond, out_value, true, + return CondEvalExpression(cond, true, FuncDefined, false, false, true); } @@ -1076,36 +1034,33 @@ DetermineKindOfConditional(const char **pp, bool *out_plain, bool (**out_evalBare)(const char *), bool *out_negate) { - const char *p = *pp; + const char *p = *pp + 2; - p += 2; *out_plain = false; *out_evalBare = FuncDefined; - *out_negate = false; - if (*p == 'n') { - p++; - *out_negate = true; - } - if (is_token(p, "def", 3)) { /* .ifdef and .ifndef */ - p += 3; - } else if (is_token(p, "make", 4)) { /* .ifmake and .ifnmake */ - p += 4; + *out_negate = skip_string(&p, "n"); + + if (skip_string(&p, "def")) { /* .ifdef and .ifndef */ + } else if (skip_string(&p, "make")) /* .ifmake and .ifnmake */ *out_evalBare = FuncMake; - } else if (is_token(p, "", 0) && !*out_negate) { /* plain .if */ + else if (!*out_negate) /* plain .if */ *out_plain = true; - } else { - /* - * TODO: Add error message about unknown directive, - * since there is no other known directive that starts - * with 'el' or 'if'. - * - * Example: .elifx 123 - */ - return false; - } + else + goto unknown_directive; + if (ch_isalpha(*p)) + goto unknown_directive; *pp = p; return true; + +unknown_directive: + /* + * TODO: Add error message about unknown directive, since there is no + * other known directive that starts with 'el' or 'if'. + * + * Example: .elifx 123 + */ + return false; } /* @@ -1129,16 +1084,16 @@ DetermineKindOfConditional(const char **pp, bool *out_plain, * parenthetical groupings thereof. * * Results: - * COND_PARSE to continue parsing the lines that follow the + * CR_TRUE to continue parsing the lines that follow the * conditional (when evaluates to true) - * COND_SKIP to skip the lines after the conditional + * CR_FALSE to skip the lines after the conditional * (when evaluates to false, or when a previous * branch has already been taken) - * COND_INVALID if the conditional was not valid, either because of + * CR_ERROR 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 +CondResult Cond_EvalLine(const char *line) { typedef enum IfState { @@ -1146,8 +1101,10 @@ Cond_EvalLine(const char *line) /* None of the previous evaluated to true. */ IFS_INITIAL = 0, - /* The previous evaluated to true. - * The lines following this condition are interpreted. */ + /* + * The previous evaluated to true. The lines following + * this condition are interpreted. + */ IFS_ACTIVE = 1 << 0, /* The previous directive was an '.else'. */ @@ -1165,7 +1122,7 @@ Cond_EvalLine(const char *line) bool (*evalBare)(const char *); bool negate; bool isElif; - bool value; + CondResult res; IfState state; const char *p = line; @@ -1186,13 +1143,13 @@ Cond_EvalLine(const char *line) if (cond_depth == cond_min_depth) { Parse_Error(PARSE_FATAL, "if-less endif"); - return COND_PARSE; + return CR_TRUE; } /* Return state for previous conditional */ cond_depth--; return cond_states[cond_depth] & IFS_ACTIVE - ? COND_PARSE : COND_SKIP; + ? CR_TRUE : CR_FALSE; } /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ @@ -1203,13 +1160,12 @@ Cond_EvalLine(const char *line) * transformation rule like '.err.txt', * therefore no error message here. */ - return COND_INVALID; + return CR_ERROR; } /* Quite likely this is 'else' or 'elif' */ p += 2; - if (is_token(p, "se", 2)) { /* It is an 'else'. */ - + if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) { if (p[2] != '\0') Parse_Error(PARSE_FATAL, "The .else directive " @@ -1217,7 +1173,7 @@ Cond_EvalLine(const char *line) if (cond_depth == cond_min_depth) { Parse_Error(PARSE_FATAL, "if-less else"); - return COND_PARSE; + return CR_TRUE; } state = cond_states[cond_depth]; @@ -1226,12 +1182,12 @@ Cond_EvalLine(const char *line) } else { if (state & IFS_SEEN_ELSE) Parse_Error(PARSE_WARNING, - "extra else"); + "extra else"); state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; } cond_states[cond_depth] = state; - return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; + return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE; } /* Assume for now it is an elif */ isElif = true; @@ -1243,27 +1199,27 @@ Cond_EvalLine(const char *line) * Unknown directive. It might still be a transformation rule * like '.elisp.scm', therefore no error message here. */ - return COND_INVALID; /* Not an ifxxx or elifxxx line */ + return CR_ERROR; /* Not an ifxxx or elifxxx line */ } if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) - return COND_INVALID; + return CR_ERROR; if (isElif) { if (cond_depth == cond_min_depth) { Parse_Error(PARSE_FATAL, "if-less elif"); - return COND_PARSE; + return CR_TRUE; } state = cond_states[cond_depth]; if (state & IFS_SEEN_ELSE) { Parse_Error(PARSE_WARNING, "extra elif"); cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; - return COND_SKIP; + return CR_FALSE; } if (state != IFS_INITIAL) { cond_states[cond_depth] = IFS_WAS_ACTIVE; - return COND_SKIP; + return CR_FALSE; } } else { /* Normal .if */ @@ -1275,8 +1231,7 @@ Cond_EvalLine(const char *line) */ cond_states_cap += 32; cond_states = bmake_realloc(cond_states, - cond_states_cap * - sizeof *cond_states); + cond_states_cap * sizeof *cond_states); } state = cond_states[cond_depth]; cond_depth++; @@ -1286,26 +1241,22 @@ Cond_EvalLine(const char *line) * treat as always false. */ cond_states[cond_depth] = IFS_WAS_ACTIVE; - return COND_SKIP; + return CR_FALSE; } } /* And evaluate the conditional expression */ - if (CondEvalExpression(p, &value, plain, evalBare, negate, - true, false) == COND_INVALID) { - /* Syntax error in conditional, error message already output. */ - /* Skip everything to matching .endif */ - /* XXX: An extra '.else' is not detected in this case. */ + res = CondEvalExpression(p, plain, evalBare, negate, true, false); + if (res == CR_ERROR) { + /* Syntax error, error message already output. */ + /* Skip everything to the matching '.endif'. */ + /* An extra '.else' is not detected in this case. */ cond_states[cond_depth] = IFS_WAS_ACTIVE; - return COND_SKIP; + return CR_FALSE; } - if (!value) { - cond_states[cond_depth] = IFS_INITIAL; - return COND_SKIP; - } - cond_states[cond_depth] = IFS_ACTIVE; - return COND_PARSE; + cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL; + return res; } void @@ -1315,7 +1266,7 @@ Cond_restore_depth(unsigned int saved_depth) if (open_conds != 0 || saved_depth > cond_depth) { Parse_Error(PARSE_FATAL, "%u open conditional%s", - open_conds, open_conds == 1 ? "" : "s"); + open_conds, open_conds == 1 ? "" : "s"); cond_depth = cond_min_depth; } diff --git a/dir.c b/dir.c index e10d7b7421db..93479271c94a 100644 --- a/dir.c +++ b/dir.c @@ -1,4 +1,4 @@ -/* $NetBSD: dir.c,v 1.275 2021/11/28 21:46:17 rillig Exp $ */ +/* $NetBSD: dir.c,v 1.278 2022/02/04 23:22:19 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -138,7 +138,7 @@ #include "job.h" /* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: dir.c,v 1.275 2021/11/28 21:46:17 rillig Exp $"); +MAKE_RCSID("$NetBSD: dir.c,v 1.278 2022/02/04 23:22:19 rillig Exp $"); /* * A search path is a list of CachedDir structures. A CachedDir has in it the @@ -672,8 +672,8 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions) { char *fullName = isDot - ? bmake_strdup(base) - : str_concat3(dirName, "/", base); + ? bmake_strdup(base) + : str_concat3(dirName, "/", base); Lst_Append(expansions, fullName); } } @@ -792,7 +792,7 @@ DirExpandCurly(const char *word, const char *brace, SearchPath *path, size_t piece_len = (size_t)(piece_end - piece); char *file = concat3(prefix, prefix_len, piece, piece_len, - suffix, suffix_len); + suffix, suffix_len); if (contains_wildcard(file)) { SearchPath_Expand(path, file, expansions); @@ -984,8 +984,9 @@ static char * DirLookupSubdir(CachedDir *dir, const char *name) { struct cached_stat cst; - char *file = dir == dot ? bmake_strdup(name) - : str_concat3(dir->name, "/", name); + char *file = dir == dot + ? bmake_strdup(name) + : str_concat3(dir->name, "/", name); DEBUG1(DIR, "checking %s ...\n", file); @@ -1424,9 +1425,9 @@ ResolveMovedDepends(GNode *gn) gn->path = bmake_strdup(fullName); if (!Job_RunTarget(".STALE", gn->fname)) fprintf(stdout, /* XXX: Why stdout? */ - "%s: %s, %d: ignoring stale %s for %s, found %s\n", - progname, gn->fname, gn->lineno, - makeDependfile, gn->name, fullName); + "%s: %s, %u: ignoring stale %s for %s, found %s\n", + progname, gn->fname, gn->lineno, + makeDependfile, gn->name, fullName); return fullName; } diff --git a/dir.h b/dir.h index d96393c62ebb..3d98fb0201b4 100644 --- a/dir.h +++ b/dir.h @@ -1,4 +1,4 @@ -/* $NetBSD: dir.h,v 1.44 2021/04/03 11:08:40 rillig Exp $ */ +/* $NetBSD: dir.h,v 1.46 2021/12/15 12:08:25 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -82,18 +82,18 @@ void Dir_InitCur(const char *); void Dir_InitDot(void); void Dir_End(void); void Dir_SetPATH(void); -bool Dir_HasWildcards(const char *); +bool Dir_HasWildcards(const char *) MAKE_ATTR_USE; void SearchPath_Expand(SearchPath *, const char *, StringList *); -char *Dir_FindFile(const char *, SearchPath *); -char *Dir_FindHereOrAbove(const char *, const char *); +char *Dir_FindFile(const char *, SearchPath *) MAKE_ATTR_USE; +char *Dir_FindHereOrAbove(const char *, const char *) MAKE_ATTR_USE; void Dir_UpdateMTime(GNode *, bool); CachedDir *SearchPath_Add(SearchPath *, const char *); -char *SearchPath_ToFlags(SearchPath *, const char *); +char *SearchPath_ToFlags(SearchPath *, const char *) MAKE_ATTR_USE; void SearchPath_Clear(SearchPath *); void SearchPath_AddAll(SearchPath *, SearchPath *); void Dir_PrintDirectories(void); void SearchPath_Print(const SearchPath *); -SearchPath *Dir_CopyDirSearchPath(void); +SearchPath *Dir_CopyDirSearchPath(void) MAKE_ATTR_USE; /* Stripped-down variant of struct stat. */ struct cached_stat { @@ -104,4 +104,4 @@ struct cached_stat { int cached_lstat(const char *, struct cached_stat *); int cached_stat(const char *, struct cached_stat *); -#endif /* MAKE_DIR_H */ +#endif diff --git a/filemon/filemon.h b/filemon/filemon.h index 139d62f1a1f6..d74c99160ba5 100644 --- a/filemon/filemon.h +++ b/filemon/filemon.h @@ -1,4 +1,4 @@ -/* $NetBSD: filemon.h,v 1.5 2021/01/19 20:51:46 rillig Exp $ */ +/* $NetBSD: filemon.h,v 1.6 2021/12/15 12:08:25 rillig Exp $ */ /* * Copyright (c) 2019 The NetBSD Foundation, Inc. @@ -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 /* MAKE_FILEMON_H */ +#endif diff --git a/for.c b/for.c index 013b0f21d841..c36641fa8c4e 100644 --- a/for.c +++ b/for.c @@ -1,4 +1,4 @@ -/* $NetBSD: for.c,v 1.150 2021/12/12 15:44:41 rillig Exp $ */ +/* $NetBSD: for.c,v 1.167 2022/02/04 23:22:19 rillig Exp $ */ /* * Copyright (c) 1992, The Regents of the University of California. @@ -58,26 +58,18 @@ #include "make.h" /* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: for.c,v 1.150 2021/12/12 15:44:41 rillig Exp $"); +MAKE_RCSID("$NetBSD: for.c,v 1.167 2022/02/04 23:22:19 rillig Exp $"); -/* One of the variables to the left of the "in" in a .for loop. */ -typedef struct ForVar { - char *name; - size_t nameLen; -} ForVar; - typedef struct ForLoop { - Buffer body; /* Unexpanded body of the loop */ - Vector /* of ForVar */ vars; /* Iteration variables */ + Vector /* of 'char *' */ vars; /* Iteration variables */ SubstringWords items; /* Substitution items */ - Buffer curBody; /* Expanded body of the current iteration */ + Buffer body; /* Unexpanded body of the loop */ unsigned int nextItem; /* Where to continue iterating */ } ForLoop; static ForLoop *accumFor; /* Loop being accumulated */ -static int forLevel = 0; /* Nesting level */ static ForLoop * @@ -85,38 +77,49 @@ ForLoop_New(void) { ForLoop *f = bmake_malloc(sizeof *f); - Buf_Init(&f->body); - Vector_Init(&f->vars, sizeof(ForVar)); + Vector_Init(&f->vars, sizeof(char *)); SubstringWords_Init(&f->items); - Buf_Init(&f->curBody); + Buf_Init(&f->body); f->nextItem = 0; return f; } -static void +void ForLoop_Free(ForLoop *f) { - Buf_Done(&f->body); - - while (f->vars.len > 0) { - ForVar *var = Vector_Pop(&f->vars); - free(var->name); - } + while (f->vars.len > 0) + free(*(char **)Vector_Pop(&f->vars)); Vector_Done(&f->vars); SubstringWords_Free(f->items); - Buf_Done(&f->curBody); + Buf_Done(&f->body); free(f); } -static void -ForLoop_AddVar(ForLoop *f, const char *name, size_t len) +char * +ForLoop_Details(ForLoop *f) { - ForVar *var = Vector_Push(&f->vars); - var->name = bmake_strldup(name, len); - var->nameLen = len; + size_t i, n; + const char **vars; + const Substring *items; + Buffer buf; + + n = f->vars.len; + vars = f->vars.items; + assert(f->nextItem >= n); + items = f->items.words + f->nextItem - n; + + Buf_Init(&buf); + for (i = 0; i < n; i++) { + if (i > 0) + Buf_AddStr(&buf, ", "); + Buf_AddStr(&buf, vars[i]); + Buf_AddStr(&buf, " = "); + Buf_AddBytesBetween(&buf, items[i].start, items[i].end); + } + return Buf_DoneData(&buf); } static bool @@ -145,7 +148,7 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp) break; } - ForLoop_AddVar(f, p, len); + *(char **)Vector_Push(&f->vars) = bmake_strldup(p, len); p += len; } @@ -174,9 +177,9 @@ ForLoop_ParseItems(ForLoop *f, const char *p) free(items); if (f->items.len == 1 && Substring_IsEmpty(f->items.words[0])) - f->items.len = 0; /* .for var in ${:U} */ + f->items.len = 0; /* .for var in ${:U} */ - if (f->items.len != 0 && f->items.len % f->vars.len != 0) { + if (f->items.len % f->vars.len != 0) { Parse_Error(PARSE_FATAL, "Wrong number of words (%u) in .for " "substitution list with %u variables", @@ -204,47 +207,38 @@ IsEndfor(const char *p) * Evaluate the for loop in the passed line. The line looks like this: * .for in * - * Input: - * line Line to parse - * * Results: - * 0: Not a .for statement, parse the line - * 1: We found a for loop - * -1: A .for statement with a bad syntax error, discard. + * 0 not a .for directive + * 1 found a .for directive + * -1 erroneous .for directive */ int For_Eval(const char *line) { - ForLoop *f; const char *p; + ForLoop *f; p = line + 1; /* skip the '.' */ cpp_skip_whitespace(&p); - if (!IsFor(p)) { - if (IsEndfor(p)) { - Parse_Error(PARSE_FATAL, "for-less endfor"); + if (IsFor(p)) { + p += 3; + + f = ForLoop_New(); + if (!ForLoop_ParseVarnames(f, &p)) { + ForLoop_Free(f); return -1; } - return 0; - } - p += 3; + if (!ForLoop_ParseItems(f, p)) + f->items.len = 0; /* don't iterate */ - f = ForLoop_New(); - - if (!ForLoop_ParseVarnames(f, &p)) { - ForLoop_Free(f); + accumFor = f; + return 1; + } else if (IsEndfor(p)) { + Parse_Error(PARSE_FATAL, "for-less endfor"); return -1; - } - - if (!ForLoop_ParseItems(f, p)) { - /* Continue parsing the .for loop, but don't iterate. */ - f->items.len = 0; - } - - accumFor = f; - forLevel = 1; - return 1; + } else + return 0; } /* @@ -252,7 +246,7 @@ For_Eval(const char *line) * Returns false when the matching .endfor is reached. */ bool -For_Accum(const char *line) +For_Accum(const char *line, int *forLevel) { const char *p = line; @@ -261,12 +255,12 @@ For_Accum(const char *line) cpp_skip_whitespace(&p); if (IsEndfor(p)) { - DEBUG1(FOR, "For: end for %d\n", forLevel); - if (--forLevel <= 0) + DEBUG1(FOR, "For: end for %d\n", *forLevel); + if (--*forLevel == 0) return false; } else if (IsFor(p)) { - forLevel++; - DEBUG1(FOR, "For: new loop %d\n", forLevel); + (*forLevel)++; + DEBUG1(FOR, "For: new loop %d\n", *forLevel); } } @@ -325,12 +319,11 @@ NeedsEscapes(Substring value, char endc) /* * While expanding the body of a .for loop, write the item in the ${:U...} - * expression, escaping characters as needed. - * - * The result is later unescaped by ApplyModifier_Defined. + * expression, escaping characters as needed. The result is later unescaped + * by ApplyModifier_Defined. */ static void -Buf_AddEscaped(Buffer *cmds, Substring item, char endc) +AddEscaped(Buffer *cmds, Substring item, char endc) { const char *p; char ch; @@ -340,9 +333,7 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc) return; } - /* Escape ':', '$', '\\' and 'endc' - these will be removed later by - * :U processing, see ApplyModifier_Defined. */ - for (p = item.start; p != item.end; p++) { + for (p = item.start; p != item.end;) { ch = *p; if (ch == '$') { size_t len = ExprLen(p + 1, item.end); @@ -352,7 +343,7 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc) * See directive-for-escape.mk, ExprLen. */ Buf_AddBytes(cmds, p, 1 + len); - p += len; + p += 1 + len; continue; } Buf_AddByte(cmds, '\\'); @@ -363,6 +354,7 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc) ch = ' '; /* prevent newline injection */ } Buf_AddByte(cmds, ch); + p++; } } @@ -371,36 +363,30 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc) * expression like ${i} or ${i:...} or $(i) or $(i:...) with ":Uvalue". */ static void -ForLoop_SubstVarLong(ForLoop *f, const char **pp, const char *bodyEnd, - char endc, const char **inout_mark) +ForLoop_SubstVarLong(ForLoop *f, unsigned int firstItem, Buffer *body, + const char **pp, char endc, const char **inout_mark) { size_t i; - const char *p = *pp; + const char *start = *pp; + const char **vars = Vector_Get(&f->vars, 0); for (i = 0; i < f->vars.len; i++) { - const ForVar *forVar = Vector_Get(&f->vars, i); - const char *varname = forVar->name; - size_t varnameLen = forVar->nameLen; + const char *p = start; - if (varnameLen >= (size_t)(bodyEnd - p)) - continue; - if (memcmp(p, varname, varnameLen) != 0) + if (!cpp_skip_string(&p, vars[i])) continue; /* XXX: why test for backslash here? */ - if (p[varnameLen] != ':' && p[varnameLen] != endc && - p[varnameLen] != '\\') + if (*p != ':' && *p != endc && *p != '\\') continue; /* * Found a variable match. Skip over the variable name and * instead add ':U' to the current body. */ - Buf_AddBytesBetween(&f->curBody, *inout_mark, p); - Buf_AddStr(&f->curBody, ":U"); - Buf_AddEscaped(&f->curBody, - f->items.words[f->nextItem + i], endc); + Buf_AddBytesBetween(body, *inout_mark, start); + Buf_AddStr(body, ":U"); + AddEscaped(body, f->items.words[firstItem + i], endc); - p += varnameLen; *inout_mark = p; *pp = p; return; @@ -412,10 +398,11 @@ ForLoop_SubstVarLong(ForLoop *f, const char **pp, const char *bodyEnd, * variable expressions like $i with their ${:U...} expansion. */ static void -ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark) +ForLoop_SubstVarShort(ForLoop *f, unsigned int firstItem, Buffer *body, + const char *p, const char **inout_mark) { const char ch = *p; - const ForVar *vars; + const char **vars; size_t i; /* Skip $$ and stupid ones. */ @@ -424,20 +411,20 @@ ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark) vars = Vector_Get(&f->vars, 0); for (i = 0; i < f->vars.len; i++) { - const char *varname = vars[i].name; + const char *varname = vars[i]; if (varname[0] == ch && varname[1] == '\0') goto found; } return; found: - Buf_AddBytesBetween(&f->curBody, *inout_mark, p); + Buf_AddBytesBetween(body, *inout_mark, p); *inout_mark = p + 1; /* Replace $ with ${:U} */ - Buf_AddStr(&f->curBody, "{:U"); - Buf_AddEscaped(&f->curBody, f->items.words[f->nextItem + i], '}'); - Buf_AddByte(&f->curBody, '}'); + Buf_AddStr(body, "{:U"); + AddEscaped(body, f->items.words[firstItem + i], '}'); + Buf_AddByte(body, '}'); } /* @@ -454,68 +441,59 @@ ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark) * possible to contrive a makefile where an unwanted substitution happens. */ static void -ForLoop_SubstBody(ForLoop *f) +ForLoop_SubstBody(ForLoop *f, unsigned int firstItem, Buffer *body) { - const char *p, *bodyEnd; + const char *p, *end; const char *mark; /* where the last substitution left off */ - Buf_Empty(&f->curBody); + Buf_Clear(body); mark = f->body.data; - bodyEnd = f->body.data + f->body.len; + end = f->body.data + f->body.len; for (p = mark; (p = strchr(p, '$')) != NULL;) { if (p[1] == '{' || p[1] == '(') { char endc = p[1] == '{' ? '}' : ')'; p += 2; - ForLoop_SubstVarLong(f, &p, bodyEnd, endc, &mark); + ForLoop_SubstVarLong(f, firstItem, body, + &p, endc, &mark); } else if (p[1] != '\0') { - ForLoop_SubstVarShort(f, p + 1, &mark); + ForLoop_SubstVarShort(f, firstItem, body, + p + 1, &mark); p += 2; } else break; } - Buf_AddBytesBetween(&f->curBody, mark, bodyEnd); + Buf_AddBytesBetween(body, mark, end); } /* * Compute the body for the current iteration by copying the unexpanded body, * replacing the expressions for the iteration variables on the way. */ -static char * -ForReadMore(void *v_arg, size_t *out_len) +bool +For_NextIteration(ForLoop *f, Buffer *body) { - ForLoop *f = v_arg; + if (f->nextItem == f->items.len) + return false; - if (f->nextItem == f->items.len) { - /* No more iterations */ - ForLoop_Free(f); - return NULL; - } - - ForLoop_SubstBody(f); - DEBUG1(FOR, "For: loop body:\n%s", f->curBody.data); f->nextItem += (unsigned int)f->vars.len; - - *out_len = f->curBody.len; - return f->curBody.data; + ForLoop_SubstBody(f, f->nextItem - (unsigned int)f->vars.len, body); + DEBUG1(FOR, "For: loop body:\n%s", body->data); + return true; } /* Run the .for loop, imitating the actions of an include file. */ void -For_Run(int lineno) +For_Run(unsigned headLineno, unsigned bodyReadLines) { + Buffer buf; ForLoop *f = accumFor; accumFor = NULL; - if (f->items.len == 0) { - /* - * Nothing to expand - possibly due to an earlier syntax - * error. - */ + if (f->items.len > 0) { + Buf_Init(&buf); + Parse_PushInput(NULL, headLineno, bodyReadLines, buf, f); + } else ForLoop_Free(f); - return; - } - - Parse_PushInput(NULL, lineno, -1, ForReadMore, f); } diff --git a/hash.c b/hash.c index ec0079fa4ba4..beef2a8419de 100644 --- a/hash.c +++ b/hash.c @@ -1,4 +1,4 @@ -/* $NetBSD: hash.c,v 1.66 2021/12/07 21:58:01 rillig Exp $ */ +/* $NetBSD: hash.c,v 1.71 2022/01/27 11:00:07 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -74,7 +74,7 @@ #include "make.h" /* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: hash.c,v 1.66 2021/12/07 21:58:01 rillig Exp $"); +MAKE_RCSID("$NetBSD: hash.c,v 1.71 2022/01/27 11:00:07 rillig Exp $"); /* * The ratio of # entries to # buckets at which we rebuild the table to @@ -84,7 +84,7 @@ MAKE_RCSID("$NetBSD: hash.c,v 1.66 2021/12/07 21:58:01 rillig Exp $"); /* This hash function matches Gosling's Emacs and java.lang.String. */ static unsigned int -Hash_String(const char *key, size_t *out_keylen) +Hash_String(const char *key, const char **out_keyEnd) { unsigned int h; const char *p; @@ -93,8 +93,7 @@ Hash_String(const char *key, size_t *out_keylen) for (p = key; *p != '\0'; p++) h = 31 * h + (unsigned char)*p; - if (out_keylen != NULL) - *out_keylen = (size_t)(p - key); + *out_keyEnd = p; return h; } @@ -112,53 +111,22 @@ Hash_Substring(Substring key) } static HashEntry * -HashTable_Find(HashTable *t, unsigned int h, const char *key) +HashTable_Find(HashTable *t, Substring key, unsigned int h) { HashEntry *e; unsigned int chainlen = 0; + size_t keyLen = Substring_Length(key); #ifdef DEBUG_HASH_LOOKUP - DEBUG4(HASH, "%s: %p h=%08x key=%s\n", __func__, t, h, key); + DEBUG4(HASH, "HashTable_Find: %p h=%08x key=%.*s\n", + t, h, (int)keyLen, key.start); #endif for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { chainlen++; - if (e->key_hash == h && strcmp(e->key, key) == 0) - break; - } - - if (chainlen > t->maxchain) - t->maxchain = chainlen; - - return e; -} - -static bool -HashEntry_KeyEquals(const HashEntry *he, Substring key) -{ - const char *heKey, *p; - - heKey = he->key; - for (p = key.start; p != key.end; p++, heKey++) - if (*p != *heKey || *heKey == '\0') - return false; - return *heKey == '\0'; -} - -static HashEntry * -HashTable_FindEntryBySubstring(HashTable *t, Substring key, unsigned int h) -{ - HashEntry *e; - unsigned int chainlen = 0; - -#ifdef DEBUG_HASH_LOOKUP - DEBUG5(HASH, "%s: %p h=%08x key=%.*s\n", __func__, t, h, - (int)Substring_Length(key), key.start); -#endif - - for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { - chainlen++; - if (e->key_hash == h && HashEntry_KeyEquals(e, key)) + if (e->key_hash == h && + strncmp(e->key, key.start, keyLen) == 0 && + e->key[keyLen] == '\0') break; } @@ -213,8 +181,9 @@ HashTable_Done(HashTable *t) HashEntry * HashTable_FindEntry(HashTable *t, const char *key) { - unsigned int h = Hash_String(key, NULL); - return HashTable_Find(t, h, key); + const char *keyEnd; + unsigned int h = Hash_String(key, &keyEnd); + return HashTable_Find(t, Substring_Init(key, keyEnd), h); } /* Find the value corresponding to the key, or return NULL. */ @@ -232,7 +201,7 @@ HashTable_FindValue(HashTable *t, const char *key) void * HashTable_FindValueBySubstringHash(HashTable *t, Substring key, unsigned int h) { - HashEntry *he = HashTable_FindEntryBySubstring(t, key, h); + HashEntry *he = HashTable_Find(t, key, h); return he != NULL ? he->value : NULL; } @@ -268,8 +237,8 @@ HashTable_Enlarge(HashTable *t) t->bucketsSize = newSize; t->bucketsMask = newMask; t->buckets = newBuckets; - DEBUG5(HASH, "%s: %p size=%d entries=%d maxchain=%d\n", - __func__, (void *)t, t->bucketsSize, t->numEntries, t->maxchain); + DEBUG4(HASH, "HashTable_Enlarge: %p size=%d entries=%d maxchain=%d\n", + (void *)t, t->bucketsSize, t->numEntries, t->maxchain); t->maxchain = 0; } @@ -280,9 +249,9 @@ HashTable_Enlarge(HashTable *t) HashEntry * HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew) { - size_t keylen; - unsigned int h = Hash_String(key, &keylen); - HashEntry *he = HashTable_Find(t, h, key); + const char *keyEnd; + unsigned int h = Hash_String(key, &keyEnd); + HashEntry *he = HashTable_Find(t, Substring_Init(key, keyEnd), h); if (he != NULL) { if (out_isNew != NULL) @@ -293,10 +262,10 @@ HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew) if (t->numEntries >= rebuildLimit * t->bucketsSize) HashTable_Enlarge(t); - he = bmake_malloc(sizeof *he + keylen); + he = bmake_malloc(sizeof *he + (size_t)(keyEnd - key)); he->value = NULL; he->key_hash = h; - memcpy(he->key, key, keylen + 1); + memcpy(he->key, key, (size_t)(keyEnd - key) + 1); he->next = t->buckets[h & t->bucketsMask]; t->buckets[h & t->bucketsMask] = he; @@ -307,12 +276,11 @@ HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew) return he; } -HashEntry * +void HashTable_Set(HashTable *t, const char *key, void *value) { HashEntry *he = HashTable_CreateEntry(t, key, NULL); HashEntry_Set(he, value); - return he; } /* Delete the entry from the table and free the associated memory. */ @@ -361,5 +329,5 @@ void HashTable_DebugStats(HashTable *t, const char *name) { DEBUG4(HASH, "HashTable %s: size=%u numEntries=%u maxchain=%u\n", - name, t->bucketsSize, t->numEntries, t->maxchain); + name, t->bucketsSize, t->numEntries, t->maxchain); } diff --git a/hash.h b/hash.h index 8ff5490bdd95..016108c39060 100644 --- a/hash.h +++ b/hash.h @@ -1,4 +1,4 @@ -/* $NetBSD: hash.h,v 1.41 2021/12/07 21:58:01 rillig Exp $ */ +/* $NetBSD: hash.h,v 1.46 2022/01/31 22:58:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -88,8 +88,8 @@ typedef struct HashEntry { /* The hash table containing the entries. */ typedef struct HashTable { - HashEntry **buckets; /* Pointers to HashEntry, one - * for each bucket in the table. */ + HashEntry **buckets; /* Pointers to HashEntry, one for each bucket + * in the table. */ unsigned int bucketsSize; unsigned int numEntries; /* Number of entries in the table. */ unsigned int bucketsMask; /* Used to select the bucket for a hash. */ @@ -108,7 +108,7 @@ typedef struct HashSet { HashTable tbl; } HashSet; -MAKE_INLINE void * +MAKE_INLINE void * MAKE_ATTR_USE HashEntry_Get(HashEntry *h) { return h->value; @@ -131,16 +131,16 @@ HashIter_Init(HashIter *hi, HashTable *t) void HashTable_Init(HashTable *); void HashTable_Done(HashTable *); -HashEntry *HashTable_FindEntry(HashTable *, const char *); -void *HashTable_FindValue(HashTable *, const char *); -unsigned int Hash_Substring(Substring); -void *HashTable_FindValueBySubstringHash(HashTable *, Substring, unsigned int); +HashEntry *HashTable_FindEntry(HashTable *, const char *) MAKE_ATTR_USE; +void *HashTable_FindValue(HashTable *, const char *) MAKE_ATTR_USE; +unsigned int Hash_Substring(Substring) MAKE_ATTR_USE; +void *HashTable_FindValueBySubstringHash(HashTable *, Substring, unsigned int) + MAKE_ATTR_USE; HashEntry *HashTable_CreateEntry(HashTable *, const char *, bool *); -HashEntry *HashTable_Set(HashTable *, const char *, void *); +void HashTable_Set(HashTable *, const char *, void *); void HashTable_DeleteEntry(HashTable *, HashEntry *); void HashTable_DebugStats(HashTable *, const char *); -void HashIter_Init(HashIter *, HashTable *); HashEntry *HashIter_Next(HashIter *); MAKE_INLINE void @@ -164,7 +164,7 @@ HashSet_Add(HashSet *set, const char *key) return isNew; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE HashSet_Contains(HashSet *set, const char *key) { return HashTable_FindEntry(&set->tbl, key) != NULL; @@ -176,4 +176,4 @@ HashIter_InitSet(HashIter *hi, HashSet *set) HashIter_Init(hi, &set->tbl); } -#endif /* MAKE_HASH_H */ +#endif diff --git a/job.c b/job.c index 41fb8c34c263..2c04a17a200b 100644 --- a/job.c +++ b/job.c @@ -1,4 +1,4 @@ -/* $NetBSD: job.c,v 1.440 2021/11/28 19:51:06 rillig Exp $ */ +/* $NetBSD: job.c,v 1.451 2022/02/04 23:22:19 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -155,7 +155,7 @@ #include "trace.h" /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: job.c,v 1.440 2021/11/28 19:51:06 rillig Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.451 2022/02/04 23:22:19 rillig Exp $"); /* * A shell defines how the commands are run. All commands for a target are @@ -214,13 +214,15 @@ typedef struct Shell { const char *errOff; /* command to turn off error checking */ const char *echoTmpl; /* template to echo a command */ - const char *runIgnTmpl; /* template to run a command - * without error checking */ - const char *runChkTmpl; /* template to run a command - * with error checking */ + const char *runIgnTmpl; /* template to run a command without error + * checking */ + const char *runChkTmpl; /* template to run a command with error + * checking */ - /* string literal that results in a newline character when it appears - * outside of any 'quote' or "quote" characters */ + /* + * A string literal that results in a newline character when it + * occurs outside of any 'quote' or "quote" characters. + */ const char *newline; char commentChar; /* character used by shell for comment lines */ @@ -438,7 +440,7 @@ static void watchfd(Job *); static void clearfd(Job *); static bool readyfd(Job *); -static char *targPrefix = NULL; /* To identify a job change in the output. */ +static char *targPrefix = NULL; /* To identify a job change in the output. */ static Job tokenWaitJob; /* token wait pseudo-job */ static Job childExitJob; /* child exit pseudo-job */ @@ -517,13 +519,13 @@ JobDeleteTarget(GNode *gn) return; if (gn->type & OP_PHONY) return; - if (Targ_Precious(gn)) + if (GNode_IsPrecious(gn)) return; if (opts.noExecute) return; file = GNode_Path(gn); - if (eunlink(file) != -1) + if (unlink_file(file)) Error("*** %s removed", file); } @@ -924,7 +926,7 @@ JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) run = GNode_ShouldExecute(job->node); - Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd); + (void)Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd); /* TODO: handle errors */ xcmdStart = xcmd; @@ -938,7 +940,7 @@ JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) * We're not actually executing anything... * but this one needs to be - use compat mode just for it. */ - Compat_RunCommand(ucmd, job->node, ln); + (void)Compat_RunCommand(ucmd, job->node, ln); free(xcmdStart); return; } @@ -1119,7 +1121,7 @@ JobFinishDoneExitedError(Job *job, WAIT_T *inout_status) else { if (deleteOnError) JobDeleteTarget(job->node); - PrintOnError(job->node, NULL); + PrintOnError(job->node, "\n"); } } @@ -1279,9 +1281,11 @@ TouchRegular(GNode *gn) return; /* XXX: What about propagating the error? */ } - /* Last resort: update the file's time stamps in the traditional way. + /* + * Last resort: update the file's time stamps in the traditional way. * XXX: This doesn't work for empty files, which are sometimes used - * as marker files. */ + * as marker files. + */ if (read(fd, &c, 1) == 1) { (void)lseek(fd, 0, SEEK_SET); while (write(fd, &c, 1) == -1 && errno == EAGAIN) @@ -1383,7 +1387,7 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) if (gn->flags.fromDepend) { if (!Job_RunTarget(".STALE", gn->fname)) fprintf(stdout, - "%s: %s, %d: ignoring stale %s for %s\n", + "%s: %s, %u: ignoring stale %s for %s\n", progname, gn->fname, gn->lineno, makeDependfile, gn->name); return true; @@ -1455,9 +1459,8 @@ JobExec(Job *job, char **argv) sigset_t tmask; #ifdef USE_META - if (useMeta) { + if (useMeta) meta_job_child(job); - } #endif /* * Reset all signal handlers; this is necessary because we @@ -1539,9 +1542,8 @@ JobExec(Job *job, char **argv) Trace_Log(JOBSTART, job); #ifdef USE_META - if (useMeta) { + if (useMeta) meta_job_parent(job, cpid); - } #endif /* @@ -1635,7 +1637,7 @@ JobWriteShellCommands(Job *job, GNode *gn, bool *out_run) #ifdef USE_META if (useMeta) { meta_job_start(job, gn); - if (gn->type & OP_SILENT) /* might have changed */ + if (gn->type & OP_SILENT) /* might have changed */ job->echo = false; } #endif @@ -1679,7 +1681,7 @@ JobStart(GNode *gn, bool special) job->special = special || gn->type & OP_SPECIAL; job->ignerr = opts.ignoreErrors || gn->type & OP_IGNORE; - job->echo = !(opts.beSilent || gn->type & OP_SILENT); + job->echo = !(opts.silent || gn->type & OP_SILENT); /* * Check the commands now so any attributes from .DEFAULT have a @@ -1698,11 +1700,11 @@ JobStart(GNode *gn, bool special) * also dead... */ if (!cmdsOK) { - PrintOnError(gn, NULL); /* provide some clue */ + PrintOnError(gn, "\n"); /* provide some clue */ DieHorribly(); } } else if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) || - (!opts.noExecute && !opts.touchFlag)) { + (!opts.noExecute && !opts.touch)) { /* * The above condition looks very similar to * GNode_ShouldExecute but is subtly different. It prevents @@ -1715,7 +1717,7 @@ JobStart(GNode *gn, bool special) * also dead... */ if (!cmdsOK) { - PrintOnError(gn, NULL); /* provide some clue */ + PrintOnError(gn, "\n"); /* provide some clue */ DieHorribly(); } @@ -1935,7 +1937,7 @@ CollectOutput(Job *job, bool finish) * we add one of our own free will. */ if (*cp != '\0') { - if (!opts.beSilent) + if (!opts.silent) SwitchOutputTo(job->node); #ifdef USE_META if (useMeta) { @@ -1999,7 +2001,7 @@ JobRun(GNode *targ) Compat_Make(targ, targ); /* XXX: Replace with GNode_IsError(gn) */ if (targ->made == ERROR) { - PrintOnError(targ, "\n\nStop."); + PrintOnError(targ, "\n\nStop.\n"); exit(1); } #endif @@ -2147,9 +2149,8 @@ Job_CatchOutput(void) * than job->inPollfd. */ if (useMeta && job->inPollfd != &fds[i]) { - if (meta_job_event(job) <= 0) { - fds[i].events = 0; /* never mind */ - } + if (meta_job_event(job) <= 0) + fds[i].events = 0; /* never mind */ } #endif if (--nready == 0) @@ -2203,14 +2204,8 @@ Shell_Init(void) free(shellErrFlag); shellErrFlag = NULL; } - if (shellErrFlag == NULL) { - size_t n = strlen(shell->errFlag) + 2; - - shellErrFlag = bmake_malloc(n); - if (shellErrFlag != NULL) - snprintf(shellErrFlag, n, "-%s", - shell->errFlag); - } + if (shellErrFlag == NULL) + shellErrFlag = str_concat2("-", shell->errFlag); } else if (shellErrFlag != NULL) { free(shellErrFlag); shellErrFlag = NULL; @@ -2329,8 +2324,10 @@ Job_Init(void) AddSig(SIGCONT, JobContinueSig); (void)Job_RunTarget(".BEGIN", NULL); - /* Create the .END node now, even though no code in the unit tests - * depends on it. See also Targ_GetEndNode in Compat_Run. */ + /* + * 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(); } @@ -2470,13 +2467,17 @@ Job_ParseShell(char *line) } else if (strncmp(arg, "newline=", 8) == 0) { newShell.newline = arg + 8; } else if (strncmp(arg, "check=", 6) == 0) { - /* Before 2020-12-10, these two variables - * had been a single variable. */ + /* + * Before 2020-12-10, these two variables had + * been a single variable. + */ newShell.errOn = arg + 6; newShell.echoTmpl = arg + 6; } else if (strncmp(arg, "ignore=", 7) == 0) { - /* Before 2020-12-10, these two variables - * had been a single variable. */ + /* + * Before 2020-12-10, these two variables had + * been a single variable. + */ newShell.errOff = arg + 7; newShell.runIgnTmpl = arg + 7; } else if (strncmp(arg, "errout=", 7) == 0) { @@ -2618,7 +2619,7 @@ JobInterrupt(bool runINTERRUPT, int signo) JobSigUnlock(&mask); - if (runINTERRUPT && !opts.touchFlag) { + if (runINTERRUPT && !opts.touch) { interrupt = Targ_FindNode(".INTERRUPT"); if (interrupt != NULL) { opts.ignoreErrors = false; @@ -2838,7 +2839,7 @@ Job_TempFile(const char *pattern, char *tfile, size_t tfile_sz) JobSigLock(&mask); fd = mkTempFile(pattern, tfile, tfile_sz); if (tfile != NULL && !DEBUG(SCRIPT)) - unlink(tfile); + unlink(tfile); JobSigUnlock(&mask); return fd; @@ -2970,7 +2971,7 @@ Job_RunTarget(const char *target, const char *fname) JobRun(gn); /* XXX: Replace with GNode_IsError(gn) */ if (gn->made == ERROR) { - PrintOnError(gn, "\n\nStop."); + PrintOnError(gn, "\n\nStop.\n"); exit(1); } return true; @@ -3035,4 +3036,4 @@ emul_poll(struct pollfd *fd, int nfd, int timeout) return npoll; } -#endif /* USE_SELECT */ +#endif /* USE_SELECT */ diff --git a/job.h b/job.h index ef66602518d7..574c12ed16a0 100644 --- a/job.h +++ b/job.h @@ -1,4 +1,4 @@ -/* $NetBSD: job.h,v 1.73 2021/04/03 11:08:40 rillig Exp $ */ +/* $NetBSD: job.h,v 1.77 2021/12/15 12:58:01 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -80,7 +80,7 @@ #ifndef MAKE_JOB_H #define MAKE_JOB_H -#define TMPPAT "makeXXXXXX" /* relative to tmpdir */ +#define TMPPAT "makeXXXXXX" /* relative to tmpdir */ #ifdef USE_SELECT /* @@ -92,16 +92,15 @@ #define pollfd emul_pollfd struct emul_pollfd { - int fd; - short events; - short revents; + int fd; + short events; + short revents; }; #define POLLIN 0x0001 #define POLLOUT 0x0004 -int -emul_poll(struct pollfd *fd, int nfd, int timeout); +int emul_poll(struct pollfd *, int, int); #endif /* @@ -145,9 +144,11 @@ typedef struct Job { /* 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. */ + /* + * If one of the shell commands is "...", all following commands are + * delayed until the .END node is made. This list node points to the + * first of these commands, if any. + */ StringListNode *tailCmds; /* This is where the shell commands go. */ @@ -187,24 +188,25 @@ extern char *shellErrFlag; extern int jobTokensRunning; /* tokens currently "out" */ void Shell_Init(void); -const char *Shell_GetNewline(void); +const char *Shell_GetNewline(void) MAKE_ATTR_USE; void Job_Touch(GNode *, bool); -bool Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...)); +bool Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...)) + MAKE_ATTR_USE; void Job_CatchChildren(void); void Job_CatchOutput(void); void Job_Make(GNode *); void Job_Init(void); -bool Job_ParseShell(char *); +bool Job_ParseShell(char *) MAKE_ATTR_USE; int Job_Finish(void); void Job_End(void); void Job_Wait(void); void Job_AbortAll(void); void Job_TokenReturn(void); -bool Job_TokenWithdraw(void); +bool Job_TokenWithdraw(void) MAKE_ATTR_USE; void Job_ServerStart(int, int, int); void Job_SetPrefix(void); bool Job_RunTarget(const char *, const char *); void Job_FlagsToString(const Job *, char *, size_t); -int Job_TempFile(const char *, char *, size_t); +int Job_TempFile(const char *, char *, size_t) MAKE_ATTR_USE; -#endif /* MAKE_JOB_H */ +#endif diff --git a/lst.h b/lst.h index 02f1ae9ec38c..597b687215f7 100644 --- a/lst.h +++ b/lst.h @@ -1,4 +1,4 @@ -/* $NetBSD: lst.h,v 1.99 2021/12/05 10:11:31 rillig Exp $ */ +/* $NetBSD: lst.h,v 1.102 2021/12/15 12:24:13 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -109,7 +109,7 @@ typedef void LstFreeProc(void *); /* Create or destroy a list */ /* Create a new list. */ -List *Lst_New(void); +List *Lst_New(void) MAKE_ATTR_USE; /* Free the list nodes, but not the list itself. */ void Lst_Done(List *); /* Free the list nodes, freeing the node data using the given function. */ @@ -129,14 +129,14 @@ Lst_Init(List *list) /* Get information about a list */ -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE Lst_IsEmpty(List *list) { return list->first == NULL; } /* Find the first node that contains the given datum, or NULL. */ -ListNode *Lst_FindDatum(List *, const void *); +ListNode *Lst_FindDatum(List *, const void *) MAKE_ATTR_USE; /* Modify a list */ @@ -163,12 +163,13 @@ void LstNode_SetNull(ListNode *); /* Add a datum at the tail of the queue. */ MAKE_INLINE void -Lst_Enqueue(List *list, void *datum) { +Lst_Enqueue(List *list, void *datum) +{ Lst_Append(list, datum); } /* Remove the head node of the queue and return its datum. */ -void *Lst_Dequeue(List *); +void *Lst_Dequeue(List *) MAKE_ATTR_USE; /* * A vector is an ordered collection of items, allowing for fast indexed @@ -187,7 +188,7 @@ void Vector_Init(Vector *, size_t); * Return the pointer to the given item in the vector. * The returned data is valid until the next modifying operation. */ -MAKE_INLINE void * +MAKE_INLINE void * MAKE_ATTR_USE Vector_Get(Vector *v, size_t i) { unsigned char *items = v->items; @@ -198,8 +199,9 @@ void *Vector_Push(Vector *); void *Vector_Pop(Vector *); MAKE_INLINE void -Vector_Done(Vector *v) { +Vector_Done(Vector *v) +{ free(v->items); } -#endif /* MAKE_LST_H */ +#endif diff --git a/main.c b/main.c index 60be60c7ca90..d328779ac082 100644 --- a/main.c +++ b/main.c @@ -1,4 +1,4 @@ -/* $NetBSD: main.c,v 1.541 2021/08/14 13:32:12 rillig Exp $ */ +/* $NetBSD: main.c,v 1.577 2022/01/29 09:38:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -111,7 +111,7 @@ #include "trace.h" /* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: main.c,v 1.541 2021/08/14 13:32:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: main.c,v 1.577 2022/01/29 09:38:26 rillig Exp $"); #if defined(MAKE_NATIVE) && !defined(lint) __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " "The Regents of the University of California. " @@ -135,10 +135,10 @@ static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ bool doing_depend; /* Set while reading .depend */ static bool jobsRunning; /* true if the jobs might be running */ static const char *tracefile; -static int ReadMakefile(const char *); +static bool ReadMakefile(const char *); static void purge_relative_cached_realpaths(void); -static bool ignorePWD; /* if we use -C, PWD is meaningless */ +static bool ignorePWD; /* if we use -C, PWD is meaningless */ static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ char curdir[MAXPATHLEN + 1]; /* Startup directory */ const char *progname; @@ -157,35 +157,26 @@ static HashTable cached_realpaths; static char * explode(const char *flags) { - size_t len; - char *nf, *st; - const char *f; + char *exploded, *ep; + const char *p; if (flags == NULL) return NULL; - for (f = flags; *f != '\0'; f++) - if (!ch_isalpha(*f)) - break; + for (p = flags; *p != '\0'; p++) + if (!ch_isalpha(*p)) + return bmake_strdup(flags); - if (*f != '\0') - return bmake_strdup(flags); - - len = strlen(flags); - st = nf = bmake_malloc(len * 3 + 1); - while (*flags != '\0') { - *nf++ = '-'; - *nf++ = *flags++; - *nf++ = ' '; + exploded = bmake_malloc((size_t)(p - flags) * 3 + 1); + for (p = flags, ep = exploded; *p != '\0'; p++) { + *ep++ = '-'; + *ep++ = *p; + *ep++ = ' '; } - *nf = '\0'; - return st; + *ep = '\0'; + return exploded; } -/* - * usage -- - * exit with usage message - */ MAKE_ATTR_DEAD static void usage(void) { @@ -229,15 +220,15 @@ MainParseArgDebugFile(const char *arg) fname = bmake_malloc(len + 20); memcpy(fname, arg, len + 1); - /* Let the filename be modified by the pid */ - if (strcmp(fname + len - 3, ".%d") == 0) + /* Replace the trailing '%d' after '.%d' with the pid. */ + if (len >= 3 && memcmp(fname + len - 3, ".%d", 3) == 0) snprintf(fname + len - 2, 20, "%d", getpid()); opts.debug_file = fopen(fname, mode); if (opts.debug_file == NULL) { - fprintf(stderr, "Cannot open debug file %s\n", + fprintf(stderr, "Cannot open debug file \"%s\"\n", fname); - usage(); + exit(2); } free(fname); } @@ -251,79 +242,79 @@ MainParseArgDebug(const char *argvalue) for (modules = argvalue; *modules != '\0'; modules++) { switch (*modules) { case '0': /* undocumented, only intended for tests */ - debug = DEBUG_NONE; + memset(&debug, 0, sizeof(debug)); break; case 'A': - debug = DEBUG_ALL; + memset(&debug, ~0, sizeof(debug)); break; case 'a': - debug |= DEBUG_ARCH; + debug.DEBUG_ARCH = true; break; case 'C': - debug |= DEBUG_CWD; + debug.DEBUG_CWD = true; break; case 'c': - debug |= DEBUG_COND; + debug.DEBUG_COND = true; break; case 'd': - debug |= DEBUG_DIR; + debug.DEBUG_DIR = true; break; case 'e': - debug |= DEBUG_ERROR; + debug.DEBUG_ERROR = true; break; case 'f': - debug |= DEBUG_FOR; + debug.DEBUG_FOR = true; break; case 'g': if (modules[1] == '1') { - debug |= DEBUG_GRAPH1; + debug.DEBUG_GRAPH1 = true; modules++; } else if (modules[1] == '2') { - debug |= DEBUG_GRAPH2; + debug.DEBUG_GRAPH2 = true; modules++; } else if (modules[1] == '3') { - debug |= DEBUG_GRAPH3; + debug.DEBUG_GRAPH3 = true; modules++; } break; case 'h': - debug |= DEBUG_HASH; + debug.DEBUG_HASH = true; break; case 'j': - debug |= DEBUG_JOB; + debug.DEBUG_JOB = true; break; case 'L': opts.strict = true; break; case 'l': - debug |= DEBUG_LOUD; + debug.DEBUG_LOUD = true; break; case 'M': - debug |= DEBUG_META; + debug.DEBUG_META = true; break; case 'm': - debug |= DEBUG_MAKE; + debug.DEBUG_MAKE = true; break; case 'n': - debug |= DEBUG_SCRIPT; + debug.DEBUG_SCRIPT = true; break; case 'p': - debug |= DEBUG_PARSE; + debug.DEBUG_PARSE = true; break; case 's': - debug |= DEBUG_SUFF; + debug.DEBUG_SUFF = true; break; case 't': - debug |= DEBUG_TARG; + debug.DEBUG_TARG = true; break; case 'V': opts.debugVflag = true; break; case 'v': - debug |= DEBUG_VAR; + debug.DEBUG_VAR = true; break; case 'x': - debug |= DEBUG_SHELL; + debug.DEBUG_SHELL = true; break; case 'F': MainParseArgDebugFile(modules + 1); @@ -403,12 +394,6 @@ MainParseArgJobsInternal(const char *argvalue) } 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; @@ -469,8 +454,9 @@ MainParseArg(char c, const char *argvalue) MainParseArgChdir(argvalue); break; case 'D': - if (argvalue[0] == '\0') return false; - Global_SetExpand(argvalue, "1"); + if (argvalue[0] == '\0') + return false; + Var_SetExpand(SCOPE_GLOBAL, argvalue, "1"); Global_Append(MAKEFLAGS, "-D"); Global_Append(MAKEFLAGS, argvalue); break; @@ -506,7 +492,7 @@ MainParseArg(char c, const char *argvalue) break; case 'W': opts.parseWarnFatal = true; - /* XXX: why no Var_Append? */ + /* XXX: why no Global_Append? */ break; case 'X': opts.varNoExportEnv = true; @@ -549,7 +535,7 @@ MainParseArg(char c, const char *argvalue) Global_Append(MAKEFLAGS, "-n"); break; case 'q': - opts.queryFlag = true; + opts.query = true; /* Kind of nonsensical, wot? */ Global_Append(MAKEFLAGS, "-q"); break; @@ -558,11 +544,11 @@ MainParseArg(char c, const char *argvalue) Global_Append(MAKEFLAGS, "-r"); break; case 's': - opts.beSilent = true; + opts.silent = true; Global_Append(MAKEFLAGS, "-s"); break; case 't': - opts.touchFlag = true; + opts.touch = true; Global_Append(MAKEFLAGS, "-t"); break; case 'w': @@ -570,7 +556,6 @@ MainParseArg(char c, const char *argvalue) Global_Append(MAKEFLAGS, "-w"); break; default: - case '?': usage(); } return true; @@ -622,7 +607,10 @@ MainParseArgs(int argc, char **argv) /* '-' found at some earlier point */ optspec = strchr(optspecs, c); if (c != '\0' && optspec != NULL && optspec[1] == ':') { - /* - found, and should have an arg */ + /* + * - found, and should have an + * argument + */ inOption = false; arginc = 1; argvalue = optscan; @@ -657,10 +645,7 @@ MainParseArgs(int argc, char **argv) * on the end of the "create" list. */ for (; argc > 1; argv++, argc--) { - VarAssign var; - if (Parse_IsVar(argv[1], &var)) { - Parse_Var(&var, SCOPE_CMDLINE); - } else { + if (!Parse_VarAssign(argv[1], false, SCOPE_CMDLINE)) { if (argv[1][0] == '\0') Punt("illegal (null) argument."); if (argv[1][0] == '-' && !dashDash) @@ -735,7 +720,6 @@ Main_SetObjdir(bool writable, const char *fmt, ...) char *path; char buf[MAXPATHLEN + 1]; char buf2[MAXPATHLEN + 1]; - bool rc = false; va_list ap; va_start(ap, fmt); @@ -748,49 +732,39 @@ Main_SetObjdir(bool writable, const char *fmt, ...) } /* look for the directory and try to chdir there */ - if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { - if ((writable && access(path, W_OK) != 0) || - (chdir(path) != 0)) { - (void)fprintf(stderr, "%s warning: %s: %s.\n", - progname, path, strerror(errno)); - } else { - snprintf(objdir, sizeof objdir, "%s", path); - Global_Set(".OBJDIR", objdir); - setenv("PWD", objdir, 1); - Dir_InitDot(); - purge_relative_cached_realpaths(); - rc = true; - if (opts.enterFlag && strcmp(objdir, curdir) != 0) - enterFlagObj = true; - } + if (stat(path, &sb) != 0 || !S_ISDIR(sb.st_mode)) + return false; + + if ((writable && access(path, W_OK) != 0) || chdir(path) != 0) { + (void)fprintf(stderr, "%s warning: %s: %s.\n", + progname, path, strerror(errno)); + return false; } - return rc; + snprintf(objdir, sizeof objdir, "%s", path); + Global_Set(".OBJDIR", objdir); + setenv("PWD", objdir, 1); + Dir_InitDot(); + purge_relative_cached_realpaths(); + if (opts.enterFlag && strcmp(objdir, curdir) != 0) + enterFlagObj = true; + return true; } static bool SetVarObjdir(bool writable, const char *var, const char *suffix) { FStr path = Var_Value(SCOPE_CMDLINE, var); - FStr xpath; if (path.str == NULL || path.str[0] == '\0') { FStr_Done(&path); return false; } - /* expand variable substitutions */ - xpath = FStr_InitRefer(path.str); - if (strchr(path.str, '$') != 0) { - char *expanded; - (void)Var_Subst(path.str, SCOPE_GLOBAL, VARE_WANTRES, &expanded); - /* TODO: handle errors */ - xpath = FStr_InitOwn(expanded); - } + Var_Expand(&path, SCOPE_GLOBAL, VARE_WANTRES); - (void)Main_SetObjdir(writable, "%s%s", xpath.str, suffix); + (void)Main_SetObjdir(writable, "%s%s", path.str, suffix); - FStr_Done(&xpath); FStr_Done(&path); return true; } @@ -861,7 +835,7 @@ PrintVar(const char *varname, bool expandVars) (void)Var_Subst(varname, SCOPE_GLOBAL, VARE_WANTRES, &evalue); /* TODO: handle errors */ printf("%s\n", evalue); - bmake_free(evalue); + free(evalue); } else if (expandVars) { char *expr = str_concat3("${", varname, "}"); @@ -870,7 +844,7 @@ PrintVar(const char *varname, bool expandVars) /* TODO: handle errors */ free(expr); printf("%s\n", evalue); - bmake_free(evalue); + free(evalue); } else { FStr value = Var_Value(SCOPE_GLOBAL, varname); @@ -923,7 +897,7 @@ static bool runTargets(void) { GNodeList targs = LST_INIT; /* target nodes to create */ - bool outOfDate; /* false if all targets up to date */ + bool outOfDate; /* false if all targets up to date */ /* * Have now read the entire graph and need to make a list of @@ -944,7 +918,7 @@ runTargets(void) * (to prevent the .BEGIN from being executed should * it exist). */ - if (!opts.queryFlag) { + if (!opts.query) { Job_Init(); jobsRunning = true; } @@ -964,9 +938,9 @@ runTargets(void) } /* - * 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. + * Set up the .TARGETS variable to contain the list of targets to be created. + * If none specified, make the variable empty for now, the parser will fill + * in the default or .MAIN target later. */ static void InitVarTargets(void) @@ -1071,17 +1045,14 @@ static void HandlePWD(const struct stat *curdir_st) { char *pwd; - FStr prefix, makeobjdir; + FStr makeobjdir; struct stat pwd_st; if (ignorePWD || (pwd = getenv("PWD")) == NULL) return; - prefix = Var_Value(SCOPE_CMDLINE, "MAKEOBJDIRPREFIX"); - if (prefix.str != NULL) { - FStr_Done(&prefix); + if (Var_Exists(SCOPE_CMDLINE, "MAKEOBJDIRPREFIX")) return; - } makeobjdir = Var_Value(SCOPE_CMDLINE, "MAKEOBJDIR"); if (makeobjdir.str != NULL && strchr(makeobjdir.str, '$') != NULL) @@ -1098,13 +1069,13 @@ HandlePWD(const struct stat *curdir_st) #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. + * 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. + * 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) @@ -1141,7 +1112,7 @@ static void CmdOpts_Init(void) { opts.compatMake = false; - opts.debug = DEBUG_NONE; + memset(&opts.debug, 0, sizeof(opts.debug)); /* opts.debug_file has already been initialized earlier */ opts.strict = false; opts.debugVflag = false; @@ -1152,10 +1123,10 @@ CmdOpts_Init(void) opts.keepgoing = false; /* Stop on error */ opts.noRecursiveExecute = false; /* Execute all .MAKE targets */ opts.noExecute = false; /* Execute all commands */ - opts.queryFlag = false; + opts.query = false; opts.noBuiltins = false; /* Read the built-in rules */ - opts.beSilent = false; /* Print commands as executed */ - opts.touchFlag = false; + opts.silent = false; /* Print commands as executed */ + opts.touch = false; opts.printVars = PVM_NONE; Lst_Init(&opts.variables); opts.parseWarnFatal = false; @@ -1245,16 +1216,14 @@ ReadBuiltinRules(void) Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); for (ln = sysMkFiles.first; ln != NULL; ln = ln->next) - if (ReadMakefile(ln->datum) == 0) + if (ReadMakefile(ln->datum)) break; if (ln == NULL) Fatal("%s: cannot open %s.", progname, (const char *)sysMkFiles.first->datum); - /* Free the list nodes but not the actual filenames since these may - * still be used in GNodes. */ - Lst_Done(&sysMkFiles); + Lst_DoneCall(&sysMkFiles, free); } static void @@ -1322,13 +1291,13 @@ InitVpath(void) } static void -ReadAllMakefiles(StringList *makefiles) +ReadAllMakefiles(const StringList *makefiles) { StringListNode *ln; for (ln = makefiles->first; ln != NULL; ln = ln->next) { const char *fname = ln->datum; - if (ReadMakefile(fname) != 0) + if (!ReadMakefile(fname)) Fatal("%s: cannot open %s.", progname, fname); } } @@ -1336,6 +1305,7 @@ ReadAllMakefiles(StringList *makefiles) static void ReadFirstDefaultMakefile(void) { + StringList makefiles = LST_INIT; StringListNode *ln; char *prefs; @@ -1343,16 +1313,13 @@ ReadFirstDefaultMakefile(void) SCOPE_CMDLINE, VARE_WANTRES, &prefs); /* TODO: handle errors */ - /* XXX: This should use a local list instead of opts.makefiles - * since these makefiles do not come from the command line. They - * also have different semantics in that only the first file that - * is found is processed. See ReadAllMakefiles. */ - (void)str2Lst_Append(&opts.makefiles, prefs); + (void)str2Lst_Append(&makefiles, prefs); - for (ln = opts.makefiles.first; ln != NULL; ln = ln->next) - if (ReadMakefile(ln->datum) == 0) + for (ln = makefiles.first; ln != NULL; ln = ln->next) + if (ReadMakefile(ln->datum)) break; + Lst_Done(&makefiles); free(prefs); } @@ -1373,6 +1340,7 @@ main_Init(int argc, char **argv) /* default to writing debug to stderr */ opts.debug_file = stderr; + Str_Intern_Init(); HashTable_Init(&cached_realpaths); #ifdef SIGINFO @@ -1415,11 +1383,9 @@ main_Init(int argc, char **argv) #ifdef MAKE_VERSION Global_Set("MAKE_VERSION", MAKE_VERSION); #endif - Global_Set(".newline", "\n"); /* handy for :@ loops */ - /* - * This is the traditional preference for makefiles. - */ + Global_Set(".newline", "\n"); /* handy for :@ loops */ #ifndef MAKEFILE_PREFERENCE_LIST + /* This is the traditional preference for makefiles. */ # define MAKEFILE_PREFERENCE_LIST "makefile Makefile" #endif Global_Set(MAKE_MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST); @@ -1451,26 +1417,25 @@ main_Init(int argc, char **argv) Global_Set(MAKEOVERRIDES, ""); Global_Set("MFLAGS", ""); Global_Set(".ALLTARGETS", ""); - /* some makefiles need to know this */ Var_Set(SCOPE_CMDLINE, MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV); /* Set some other useful variables. */ { - char tmp[64], *ep = getenv(MAKE_LEVEL_ENV); + char buf[64], *ep = getenv(MAKE_LEVEL_ENV); makelevel = ep != NULL && ep[0] != '\0' ? atoi(ep) : 0; if (makelevel < 0) makelevel = 0; - snprintf(tmp, sizeof tmp, "%d", makelevel); - Global_Set(MAKE_LEVEL, tmp); - snprintf(tmp, sizeof tmp, "%u", myPid); - Global_Set(".MAKE.PID", tmp); - snprintf(tmp, sizeof tmp, "%u", getppid()); - Global_Set(".MAKE.PPID", tmp); - snprintf(tmp, sizeof tmp, "%u", getuid()); - Global_Set(".MAKE.UID", tmp); - snprintf(tmp, sizeof tmp, "%u", getgid()); - Global_Set(".MAKE.GID", tmp); + snprintf(buf, sizeof buf, "%d", makelevel); + Global_Set(MAKE_LEVEL, buf); + snprintf(buf, sizeof buf, "%u", myPid); + Global_Set(".MAKE.PID", buf); + snprintf(buf, sizeof buf, "%u", getppid()); + Global_Set(".MAKE.PPID", buf); + snprintf(buf, sizeof buf, "%u", getuid()); + Global_Set(".MAKE.UID", buf); + snprintf(buf, sizeof buf, "%u", getgid()); + Global_Set(".MAKE.GID", buf); } if (makelevel > 0) { char pn[1024]; @@ -1483,25 +1448,21 @@ main_Init(int argc, char **argv) #endif Dir_Init(); +#ifdef POSIX + { + char *makeflags = explode(getenv("MAKEFLAGS")); + Main_ParseArgLine(makeflags); + free(makeflags); + } +#else /* * First snag any flags out of the MAKE environment variable. * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's * in a different format). */ -#ifdef POSIX - { - char *p1 = explode(getenv("MAKEFLAGS")); - Main_ParseArgLine(p1); - free(p1); - } -#else Main_ParseArgLine(getenv("MAKE")); #endif - /* - * Find where we are (now). - * We take care of PWD for the automounter below... - */ if (getcwd(curdir, MAXPATHLEN) == NULL) { (void)fprintf(stderr, "%s: getcwd: %s.\n", progname, strerror(errno)); @@ -1513,9 +1474,6 @@ main_Init(int argc, char **argv) if (opts.enterFlag) printf("%s: Entering directory `%s'\n", progname, curdir); - /* - * Verify that cwd is sane. - */ if (stat(curdir, &sa) == -1) { (void)fprintf(stderr, "%s: %s: %s.\n", progname, curdir, strerror(errno)); @@ -1529,10 +1487,6 @@ main_Init(int argc, char **argv) InitObjdir(machine, machine_arch); - /* - * Initialize archive, target and suffix modules in preparation for - * parsing the makefile(s) - */ Arch_Init(); Suff_Init(); Trace_Init(tracefile); @@ -1593,10 +1547,6 @@ main_PrepareMaking(void) InitMaxJobs(); - /* - * Be compatible if the user did not specify -j and did not explicitly - * turn compatibility on. - */ if (!opts.compatMake && !forceJobs) opts.compatMake = true; @@ -1649,15 +1599,10 @@ main_CleanUp(void) { #ifdef CLEANUP Lst_DoneCall(&opts.variables, free); - /* - * Don't free the actual strings from opts.makefiles, they may be - * used in GNodes. - */ - Lst_Done(&opts.makefiles); + Lst_DoneCall(&opts.makefiles, free); Lst_DoneCall(&opts.create, free); #endif - /* print the graph now it's been processed if the user requested it */ if (DEBUG(GRAPH2)) Targ_PrintGraph(2); @@ -1679,6 +1624,7 @@ main_CleanUp(void) Dir_End(); Job_End(); Trace_End(); + Str_Intern_End(); } /* Determine the exit code. */ @@ -1705,18 +1651,16 @@ main(int argc, char **argv) /* * Open and parse the given makefile, with all its side effects. - * - * Results: - * 0 if ok. -1 if couldn't open file. + * Return false if the file could not be opened. */ -static int +static bool ReadMakefile(const char *fname) { int fd; char *name, *path = NULL; if (strcmp(fname, "-") == 0) { - Parse_File(NULL /*stdin*/, -1); + Parse_File("(stdin)", -1); Var_Set(SCOPE_INTERNAL, "MAKEFILE", ""); } else { /* if we've chdir'd, rebuild the path name */ @@ -1745,13 +1689,13 @@ ReadMakefile(const char *fname) name = Dir_FindFile(fname, parseIncPath); if (name == NULL) { SearchPath *sysInc = Lst_IsEmpty(&sysIncPath->dirs) - ? defSysIncPath : sysIncPath; + ? defSysIncPath : sysIncPath; name = Dir_FindFile(fname, sysInc); } if (name == NULL || (fd = open(name, O_RDONLY)) == -1) { free(name); free(path); - return -1; + return false; } fname = name; /* @@ -1765,154 +1709,127 @@ ReadMakefile(const char *fname) Parse_File(fname, fd); } free(path); - return 0; + return true; } /* - * Cmd_Exec -- - * Execute the command in cmd, and return the output of that command - * in a string. In the output, newlines are replaced with spaces. - * - * Results: - * A string containing the output of the command, or the empty string. - * *errfmt returns a format string describing the command failure, - * if any, using a single %s conversion specification. - * - * Side Effects: - * The string must be freed by the caller. + * Execute the command in cmd, and return its output (only stdout, not + * stderr, possibly empty). In the output, replace newlines with spaces. */ char * -Cmd_Exec(const char *cmd, const char **errfmt) +Cmd_Exec(const char *cmd, char **error) { - const char *args[4]; /* Args for invoking the shell */ + const char *args[4]; /* Arguments for invoking the shell */ int pipefds[2]; int cpid; /* Child PID */ int pid; /* PID from wait() */ int status; /* command exit status */ Buffer buf; /* buffer to store the result */ ssize_t bytes_read; - char *res; /* result */ - size_t res_len; + char *output; char *cp; - int savederr; /* saved errno */ - - *errfmt = NULL; + int saved_errno; if (shellName == NULL) Shell_Init(); - /* - * Set up arguments for shell - */ + args[0] = shellName; args[1] = "-c"; args[2] = cmd; args[3] = NULL; + DEBUG1(VAR, "Capturing the output of command \"%s\"\n", cmd); - /* - * Open a pipe for fetching its output - */ if (pipe(pipefds) == -1) { - *errfmt = "Couldn't create pipe for \"%s\""; - goto bad; + *error = str_concat3( + "Couldn't create pipe for \"", cmd, "\""); + return bmake_strdup(""); } Var_ReexportVars(); - /* - * Fork - */ switch (cpid = vfork()) { case 0: - (void)close(pipefds[0]); /* Close input side of pipe */ - - /* - * Duplicate the output stream to the shell's output, then - * shut the extra thing down. Note we don't fetch the error - * stream...why not? Why? - */ - (void)dup2(pipefds[1], 1); + (void)close(pipefds[0]); + (void)dup2(pipefds[1], STDOUT_FILENO); (void)close(pipefds[1]); (void)execv(shellPath, UNCONST(args)); _exit(1); - /*NOTREACHED*/ + /* NOTREACHED */ case -1: - *errfmt = "Couldn't exec \"%s\""; - goto bad; - - default: - (void)close(pipefds[1]); /* No need for the writing half */ - - savederr = 0; - Buf_Init(&buf); - - do { - char result[BUFSIZ]; - bytes_read = read(pipefds[0], result, sizeof result); - if (bytes_read > 0) - Buf_AddBytes(&buf, result, (size_t)bytes_read); - } while (bytes_read > 0 || - (bytes_read == -1 && errno == EINTR)); - if (bytes_read == -1) - savederr = errno; - - (void)close(pipefds[0]); /* Close the input side of the pipe. */ - - /* Wait for the process to exit. */ - while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0) - JobReapChild(pid, status, false); - - res_len = buf.len; - res = Buf_DoneData(&buf); - - if (savederr != 0) - *errfmt = "Couldn't read shell's output for \"%s\""; - - if (WIFSIGNALED(status)) - *errfmt = "\"%s\" exited on a signal"; - else if (WEXITSTATUS(status) != 0) - *errfmt = "\"%s\" returned non-zero status"; - - /* Convert newlines to spaces. A final newline is just stripped */ - if (res_len > 0 && res[res_len - 1] == '\n') - res[res_len - 1] = '\0'; - for (cp = res; *cp != '\0'; cp++) - if (*cp == '\n') - *cp = ' '; - break; + *error = str_concat3("Couldn't exec \"", cmd, "\""); + return bmake_strdup(""); } - return res; -bad: - return bmake_strdup(""); + + (void)close(pipefds[1]); /* No need for the writing half */ + + saved_errno = 0; + Buf_Init(&buf); + + do { + char result[BUFSIZ]; + bytes_read = read(pipefds[0], result, sizeof result); + if (bytes_read > 0) + Buf_AddBytes(&buf, result, (size_t)bytes_read); + } while (bytes_read > 0 || (bytes_read == -1 && errno == EINTR)); + if (bytes_read == -1) + saved_errno = errno; + + (void)close(pipefds[0]); /* Close the input side of the pipe. */ + + while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0) + JobReapChild(pid, status, false); + + if (Buf_EndsWith(&buf, '\n')) + buf.data[buf.len - 1] = '\0'; + + output = Buf_DoneData(&buf); + for (cp = output; *cp != '\0'; cp++) + if (*cp == '\n') + *cp = ' '; + + if (WIFSIGNALED(status)) + *error = str_concat3("\"", cmd, "\" exited on a signal"); + else if (WEXITSTATUS(status) != 0) + *error = str_concat3( + "\"", cmd, "\" returned non-zero status"); + else if (saved_errno != 0) + *error = str_concat3( + "Couldn't read shell's output for \"", cmd, "\""); + else + *error = NULL; + return output; } /* * Print a printf-style error message. * - * In default mode, this error message has no consequences, in particular it - * does not affect the exit status. Only in lint mode (-dL) it does. + * In default mode, this error message has no consequences, for compatibility + * reasons, in particular it does not affect the exit status. Only in lint + * mode (-dL) it does. */ void Error(const char *fmt, ...) { va_list ap; - FILE *err_file; + FILE *f; - err_file = opts.debug_file; - if (err_file == stdout) - err_file = stderr; + f = opts.debug_file; + if (f == stdout) + f = stderr; (void)fflush(stdout); + for (;;) { + fprintf(f, "%s: ", progname); va_start(ap, fmt); - fprintf(err_file, "%s: ", progname); - (void)vfprintf(err_file, fmt, ap); + (void)vfprintf(f, fmt, ap); va_end(ap); - (void)fprintf(err_file, "\n"); - (void)fflush(err_file); - if (err_file == stderr) + (void)fprintf(f, "\n"); + (void)fflush(f); + if (f == stderr) break; - err_file = stderr; + f = stderr; } main_errors++; } @@ -1938,8 +1855,9 @@ Fatal(const char *fmt, ...) va_end(ap); (void)fprintf(stderr, "\n"); (void)fflush(stderr); + PrintStackTrace(true); - PrintOnError(NULL, NULL); + PrintOnError(NULL, "\n"); if (DEBUG(GRAPH2) || DEBUG(GRAPH3)) Targ_PrintGraph(2); @@ -1956,15 +1874,15 @@ Punt(const char *fmt, ...) { va_list ap; - va_start(ap, fmt); (void)fflush(stdout); (void)fprintf(stderr, "%s: ", progname); + va_start(ap, fmt); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); (void)fflush(stderr); - PrintOnError(NULL, NULL); + PrintOnError(NULL, "\n"); DieHorribly(); } @@ -1994,23 +1912,19 @@ Finish(int errs) Fatal("%d error%s", errs, errs == 1 ? "" : "s"); } -/* - * eunlink -- - * Remove a file carefully, avoiding directories. - */ -int -eunlink(const char *file) +bool +unlink_file(const char *file) { struct stat st; if (lstat(file, &st) == -1) - return -1; + return false; if (S_ISDIR(st.st_mode)) { errno = EISDIR; - return -1; + return false; } - return unlink(file); + return unlink(file) == 0; } static void @@ -2020,6 +1934,7 @@ write_all(int fd, const void *data, size_t n) while (n > 0) { ssize_t written = write(fd, mem, n); + /* XXX: Should this EAGAIN be EINTR? */ if (written == -1 && errno == EAGAIN) continue; if (written == -1) @@ -2029,10 +1944,7 @@ write_all(int fd, const void *data, size_t n) } } -/* - * execDie -- - * Print why exec failed, avoiding stdio. - */ +/* Print why exec failed, avoiding stdio. */ void MAKE_ATTR_DEAD execDie(const char *af, const char *av) { @@ -2054,7 +1966,6 @@ execDie(const char *af, const char *av) _exit(1); } -/* purge any relative paths */ static void purge_relative_cached_realpaths(void) { @@ -2068,14 +1979,16 @@ purge_relative_cached_realpaths(void) if (he->key[0] != '/') { DEBUG1(DIR, "cached_realpath: purging %s\n", he->key); HashTable_DeleteEntry(&cached_realpaths, he); - /* XXX: What about the allocated he->value? Either - * free them or document why they cannot be freed. */ + /* + * XXX: What about the allocated he->value? Either + * free them or document why they cannot be freed. + */ } he = nhe; } } -char * +const char * cached_realpath(const char *pathname, char *resolved) { const char *rp; @@ -2160,9 +2073,7 @@ PrintOnError(GNode *gn, const char *msg) if (errorNode != NULL) return; /* we've been here! */ - if (msg != NULL) - printf("%s", msg); - printf("\n%s: stopped in %s\n", progname, curdir); + printf("%s%s: stopped in %s\n", msg, progname, curdir); /* we generally want to keep quiet if a sub-make died */ if (shouldDieQuietly(gn, -1)) @@ -2174,7 +2085,7 @@ PrintOnError(GNode *gn, const char *msg) { char *errorVarsValues; (void)Var_Subst("${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", - SCOPE_GLOBAL, VARE_WANTRES, &errorVarsValues); + SCOPE_GLOBAL, VARE_WANTRES, &errorVarsValues); /* TODO: handle errors */ printf("%s", errorVarsValues); free(errorVarsValues); @@ -2196,21 +2107,21 @@ void Main_ExportMAKEFLAGS(bool first) { static bool once = true; - const char *expr; - char *s; + char *flags; if (once != first) return; once = false; - expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}"; - (void)Var_Subst(expr, SCOPE_CMDLINE, VARE_WANTRES, &s); + (void)Var_Subst( + "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}", + SCOPE_CMDLINE, VARE_WANTRES, &flags); /* TODO: handle errors */ - if (s[0] != '\0') { + if (flags[0] != '\0') { #ifdef POSIX - setenv("MAKEFLAGS", s, 1); + setenv("MAKEFLAGS", flags, 1); #else - setenv("MAKE", s, 1); + setenv("MAKE", flags, 1); #endif } } @@ -2224,7 +2135,7 @@ getTmpdir(void) if (tmpdir != NULL) return tmpdir; - /* Honor $TMPDIR but only if it is valid. Ensure it ends with '/'. */ + /* Honor $TMPDIR if it is valid, strip a trailing '/'. */ (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP ":S,/$,,W}/", SCOPE_GLOBAL, VARE_WANTRES, &tmpdir); /* TODO: handle errors */ @@ -2253,20 +2164,21 @@ mkTempFile(const char *pattern, char *tfile, size_t tfile_sz) if (tmpdir == NULL) tmpdir = getTmpdir(); if (tfile == NULL) { - tfile = tbuf; - tfile_sz = sizeof tbuf; + tfile = tbuf; + tfile_sz = sizeof tbuf; } - if (pattern[0] == '/') { + + if (pattern[0] == '/') snprintf(tfile, tfile_sz, "%s", pattern); - } else { + else snprintf(tfile, tfile_sz, "%s%s", tmpdir, pattern); - } + if ((fd = mkstemp(tfile)) < 0) Punt("Could not create temporary file %s: %s", tfile, strerror(errno)); - if (tfile == tbuf) { + if (tfile == tbuf) unlink(tfile); /* we just want the descriptor */ - } + return fd; } diff --git a/make.1 b/make.1 index c8c3b470f4bd..02f2bb8a8e97 100644 --- a/make.1 +++ b/make.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.300 2021/12/12 20:45:48 sjg Exp $ +.\" $NetBSD: make.1,v 1.304 2022/01/29 20:54:58 sjg 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 December 12, 2021 +.Dd January 28, 2022 .Dt MAKE 1 .Os .Sh NAME @@ -691,10 +691,38 @@ Variables defined as part of the command line. Variables that are defined specific to a certain target. .El .Pp -Local variables are all built in and their values vary magically from -target to target. -It is not currently possible to define new local variables. -The seven local variables are as follows: +Local variables can be set on a dependency line, if +.Va .MAKE.TARGET_LOCAL_VARIABLES , +is not set to +.Ql false . +The rest of the line +(which will already have had Global variables expanded), +is the variable value. +For example: +.Bd -literal -offset indent +COMPILER_WRAPPERS+= ccache distcc icecc + +${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,} +.Ed +.Pp +Only the targets +.Ql ${OBJS} +will be impacted by that filter (in "meta" mode) and +simply enabling/disabling any of the wrappers will not render all +of those targets out-of-date. +.Pp +.Em NOTE : +target local variable assignments behave differently in that; +.Bl -tag -width Ds -offset indent +.It Ic \&+= +Only appends to a previous local assignment +for the same target and variable. +.It Ic \&:= +Is redundant with respect to Global variables, +which have already been expanded. +.El +.Pp +The seven built-in local variables are as follows: .Bl -tag -width ".ARCHIVE" -offset indent .It Va .ALLSRC The list of all sources for this target; also known as @@ -846,6 +874,11 @@ For example: would produce tokens like .Ql ---make[1234] target --- making it easier to track the degree of parallelism being achieved. +.It .MAKE.TARGET_LOCAL_VARIABLES +If set to +.Ql false , +apparent variable assignments in dependency lines are +treated as normal sources. .It Ev MAKEFLAGS The environment variable .Ql Ev MAKEFLAGS @@ -954,6 +987,12 @@ If a file that was generated outside of .Va .OBJDIR but within said bailiwick is missing, the current target is considered out-of-date. +.It Va .MAKE.META.CMP_FILTER +In "meta" mode, it can (very rarely!) be useful to filter command +lines before comparison. +This variable can be set to a set of modifiers that will be applied to +each line of the old and new command that differ, if the filtered +commands still differ, the target is considered out-of-date. .It Va .MAKE.META.CREATED In "meta" mode, this variable contains a list of all the meta files updated. diff --git a/make.c b/make.c index 2c1a243965f1..5fb8f8840772 100644 --- a/make.c +++ b/make.c @@ -1,4 +1,4 @@ -/* $NetBSD: make.c,v 1.248 2021/11/28 23:12:51 rillig Exp $ */ +/* $NetBSD: make.c,v 1.252 2022/01/09 15:48:30 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -104,7 +104,7 @@ #include "job.h" /* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: make.c,v 1.248 2021/11/28 23:12:51 rillig Exp $"); +MAKE_RCSID("$NetBSD: make.c,v 1.252 2022/01/09 15:48:30 rillig Exp $"); /* Sequence # to detect recursion. */ static unsigned int checked_seqno = 1; @@ -120,11 +120,11 @@ static GNodeList toBeMade = LST_INIT; void debug_printf(const char *fmt, ...) { - va_list args; + va_list ap; - va_start(args, fmt); - vfprintf(opts.debug_file, fmt, args); - va_end(args); + va_start(ap, fmt); + vfprintf(opts.debug_file, fmt, ap); + va_end(ap); } MAKE_ATTR_DEAD static void @@ -370,9 +370,8 @@ GNode_IsOODate(GNode *gn) } #ifdef USE_META - if (useMeta) { + if (useMeta) oodate = meta_oodate(gn, oodate); - } #endif /* @@ -599,8 +598,9 @@ Make_Recheck(GNode *gn) } #endif - /* XXX: The returned mtime may differ from gn->mtime. - * Intentionally? */ + /* + * XXX: The returned mtime may differ from gn->mtime. Intentionally? + */ return mtime; } @@ -952,7 +952,9 @@ MakeBuildChild(GNode *cn, GNodeListNode *toBeMadeNext) /* If this node is on the RHS of a .ORDER, check LHSs. */ if (IsWaitingForOrder(cn)) { - /* Can't build this (or anything else in this child list) yet */ + /* + * Can't build this (or anything else in this child list) yet + */ cn->made = DEFERRED; return false; /* but keep looking */ } @@ -1070,7 +1072,7 @@ MakeStartJobs(void) gn->made = BEINGMADE; if (GNode_IsOODate(gn)) { DEBUG0(MAKE, "out-of-date\n"); - if (opts.queryFlag) + if (opts.query) return true; GNode_SetLocalVars(gn); Job_Make(gn); @@ -1327,7 +1329,9 @@ add_wait_dependency(GNodeListNode *owln, GNode *wn) DEBUG3(MAKE, ".WAIT: add dependency %s%s -> %s\n", cn->name, cn->cohort_num, wn->name); - /* XXX: This pattern should be factored out, it repeats often */ + /* + * XXX: This pattern should be factored out, it repeats often + */ Lst_Append(&wn->children, cn); wn->unmade++; Lst_Append(&cn->parents, wn); @@ -1436,7 +1440,7 @@ Make_Run(GNodeList *targs) Targ_PrintGraph(1); } - if (opts.queryFlag) { + if (opts.query) { /* * We wouldn't do any work unless we could start some jobs * in the next loop... (we won't actually start any, of diff --git a/make.h b/make.h index 6f14c44eb067..091b9dc05648 100644 --- a/make.h +++ b/make.h @@ -1,4 +1,4 @@ -/* $NetBSD: make.h,v 1.270 2021/11/28 23:12:51 rillig Exp $ */ +/* $NetBSD: make.h,v 1.298 2022/02/05 00:26:21 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -74,7 +74,7 @@ /* * make.h -- - * The global definitions for pmake + * The global definitions for make */ #ifndef MAKE_MAKE_H @@ -110,9 +110,9 @@ #define MAKE_GNUC_PREREQ(x, y) \ ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \ (__GNUC__ > (x))) -#else /* defined(__GNUC__) */ +#else #define MAKE_GNUC_PREREQ(x, y) 0 -#endif /* defined(__GNUC__) */ +#endif #if MAKE_GNUC_PREREQ(2, 7) #define MAKE_ATTR_UNUSED __attribute__((__unused__)) @@ -135,7 +135,17 @@ #define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */ #endif +#if MAKE_GNUC_PREREQ(4, 0) +#define MAKE_ATTR_USE __attribute__((__warn_unused_result__)) +#else +#define MAKE_ATTR_USE /* delete */ +#endif + +#if __STDC__ >= 199901L || defined(lint) #define MAKE_INLINE static inline MAKE_ATTR_UNUSED +#else +#define MAKE_INLINE static MAKE_ATTR_UNUSED +#endif /* MAKE_STATIC marks a function that may or may not be inlined. */ #if defined(lint) @@ -206,26 +216,34 @@ typedef unsigned char bool; typedef enum GNodeMade { /* Not examined yet. */ UNMADE, - /* The node has been examined but is not yet ready since its - * dependencies have to be made first. */ + /* + * The node has been examined but is not yet ready since its + * dependencies have to be made first. + */ DEFERRED, /* The node is on the toBeMade list. */ REQUESTED, - /* The node is already being made. Trying to build a node in this - * state indicates a cycle in the graph. */ + /* + * The node is already being made. Trying to build a node in this + * state indicates a cycle in the graph. + */ BEINGMADE, /* Was out-of-date and has been made. */ MADE, /* Was already up-to-date, does not need to be made. */ UPTODATE, - /* An error occurred while it was being made. - * Used only in compat mode. */ + /* + * An error occurred while it was being made. Used only in compat + * mode. + */ ERROR, - /* The target was aborted due to an error making a dependency. - * Used only in compat mode. */ + /* + * The target was aborted due to an error making a dependency. Used + * only in compat mode. + */ ABORTED } GNodeMade; @@ -241,16 +259,22 @@ typedef enum GNodeMade { typedef enum GNodeType { OP_NONE = 0, - /* The dependency operator ':' is the most common one. The commands - * of this node are executed if any child is out-of-date. */ + /* + * The dependency operator ':' is the most common one. The commands + * of this node are executed if any child is out-of-date. + */ OP_DEPENDS = 1 << 0, - /* The dependency operator '!' always executes its commands, even if - * its children are up-to-date. */ + /* + * The dependency operator '!' always executes its commands, even if + * its children are up-to-date. + */ OP_FORCE = 1 << 1, - /* The dependency operator '::' behaves like ':', except that it + /* + * The dependency operator '::' behaves like ':', except that it * allows multiple dependency groups to be defined. Each of these - * groups is executed on its own, independently from the others. - * Each individual dependency group is called a cohort. */ + * groups is executed on its own, independently from the others. Each + * individual dependency group is called a cohort. + */ OP_DOUBLEDEP = 1 << 2, /* Matches the dependency operators ':', '!' and '::'. */ @@ -260,21 +284,29 @@ typedef enum GNodeType { OP_OPTIONAL = 1 << 3, /* Use associated commands for parents. */ OP_USE = 1 << 4, - /* Target is never out of date, but always execute commands anyway. - * Its time doesn't matter, so it has none...sort of. */ + /* + * Target is never out of date, but always execute commands anyway. + * Its time doesn't matter, so it has none...sort of. + */ OP_EXEC = 1 << 5, - /* Ignore non-zero exit status from shell commands when creating the - * node. */ + /* + * Ignore non-zero exit status from shell commands when creating the + * node. + */ OP_IGNORE = 1 << 6, /* Don't remove the target when interrupted. */ OP_PRECIOUS = 1 << 7, /* Don't echo commands when executed. */ OP_SILENT = 1 << 8, - /* Target is a recursive make so its commands should always be - * executed when it is out of date, regardless of the state of the - * -n or -t flags. */ + /* + * Target is a recursive make so its commands should always be + * executed when it is out of date, regardless of the state of the -n + * or -t flags. + */ OP_MAKE = 1 << 9, - /* Target is out-of-date only if any of its children was out-of-date. */ + /* + * Target is out-of-date only if any of its children was out-of-date. + */ OP_JOIN = 1 << 10, /* Assume the children of the node have been already made. */ OP_MADE = 1 << 11, @@ -282,20 +314,26 @@ typedef enum GNodeType { OP_SPECIAL = 1 << 12, /* Like .USE, only prepend commands. */ OP_USEBEFORE = 1 << 13, - /* The node is invisible to its parents. I.e. it doesn't show up in - * the parents' local variables (.IMPSRC, .ALLSRC). */ + /* + * The node is invisible to its parents. I.e. it doesn't show up in + * the parents' local variables (.IMPSRC, .ALLSRC). + */ OP_INVISIBLE = 1 << 14, - /* The node does not become the main target, even if it is the first - * target in the first makefile. */ + /* + * The node does not become the main target, even if it is the first + * target in the first makefile. + */ OP_NOTMAIN = 1 << 15, /* Not a file target; run always. */ OP_PHONY = 1 << 16, /* Don't search for the file in the path. */ OP_NOPATH = 1 << 17, - /* In a dependency line "target: source1 .WAIT source2", source1 is + /* + * In a dependency line "target: source1 .WAIT source2", source1 is * made first, including its children. Once that is finished, * source2 is made, including its children. The .WAIT keyword may - * appear more than once in a single dependency declaration. */ + * appear more than once in a single dependency declaration. + */ OP_WAIT = 1 << 18, /* .NOMETA do not create a .meta file */ OP_NOMETA = 1 << 19, @@ -313,28 +351,35 @@ typedef enum GNodeType { /* Target is a member of an archive */ /* XXX: How does this differ from OP_ARCHV? */ OP_MEMBER = 1 << 29, - /* The node is a library, - * its name has the form "-l" */ + /* + * The node is a library, its name has the form "-l". + */ OP_LIB = 1 << 28, - /* The node is an archive member, - * its name has the form "archive(member)" */ + /* + * The node is an archive member, its name has the form + * "archive(member)". + */ /* XXX: How does this differ from OP_MEMBER? */ OP_ARCHV = 1 << 27, - /* Target has all the commands it should. Used when parsing to catch + /* + * Target has all the commands it should. Used when parsing to catch * multiple command groups for a target. Only applies to the - * dependency operators ':' and '!', but not to '::'. */ + * dependency operators ':' and '!', but not to '::'. + */ OP_HAS_COMMANDS = 1 << 26, - /* The special command "..." has been seen. All further commands from - * this node will be saved on the .END node instead, to be executed at - * the very end. */ + /* + * The special command "..." has been seen. All further commands from + * this node will be saved on the .END node instead, to be executed + * at the very end. + */ OP_SAVE_CMDS = 1 << 25, - /* Already processed by Suff_FindDeps, to find dependencies from - * suffix transformation rules. */ + /* + * Already processed by Suff_FindDeps, to find dependencies from + * suffix transformation rules. + */ OP_DEPS_FOUND = 1 << 24, /* Node found while expanding .ALLSRC */ - OP_MARK = 1 << 23, - - OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM + OP_MARK = 1 << 23 } GNodeType; typedef struct GNodeFlags { @@ -377,14 +422,20 @@ typedef struct GNode { char *name; /* The unexpanded name of a .USE node */ char *uname; - /* The full pathname of the file belonging to the target. + /* + * The full pathname of the file belonging to the target. + * * XXX: What about .PHONY targets? These don't have an associated - * path. */ + * path. + */ char *path; - /* The type of operator used to define the sources (see the OP flags + /* + * 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. */ + * + * XXX: This looks like a wild mixture of type and flags. + */ GNodeType type; GNodeFlags flags; @@ -393,29 +444,39 @@ typedef struct GNode { /* The number of unmade children */ int unmade; - /* The modification time; 0 means the node does not have a - * corresponding file; see GNode_IsOODate. */ + /* + * The modification time; 0 means the node does not have a + * corresponding file; see GNode_IsOODate. + */ time_t mtime; struct GNode *youngestChild; - /* The GNodes for which this node is an implied source. May be empty. - * For example, when there is an inference rule for .c.o, the node for - * file.c has the node for file.o in this list. */ + /* + * The GNodes for which this node is an implied source. May be empty. + * For example, when there is an inference rule for .c.o, the node + * for file.c has the node for file.o in this list. + */ GNodeList implicitParents; - /* The nodes that depend on this one, or in other words, the nodes for - * which this is a source. */ + /* + * The nodes that depend on this one, or in other words, the nodes + * for which this is a source. + */ GNodeList parents; /* The nodes on which this one depends. */ GNodeList children; - /* .ORDER nodes we need made. The nodes that must be made (if they're + /* + * .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. */ + * datedness of this node. + */ GNodeList order_pred; - /* .ORDER nodes who need us. The nodes that must be made (if they're + /* + * .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. */ + * this node, in the normal sense. + */ GNodeList order_succ; /* @@ -427,8 +488,10 @@ typedef struct GNode { char cohort_num[8]; /* The number of unmade instances on the cohorts list */ int unmade_cohorts; - /* Pointer to the first instance of a '::' node; only set when on a - * cohorts list */ + /* + * Pointer to the first instance of a '::' node; only set when on a + * cohorts list + */ struct GNode *centurion; /* Last time (sequence number) we tried to make this node */ @@ -447,21 +510,24 @@ typedef struct GNode { /* The commands to be given to a shell to create this target. */ StringList commands; - /* Suffix for the node (determined by Suff_FindDeps and opaque to - * everyone but the Suff module) */ + /* + * Suffix for the node (determined by Suff_FindDeps and opaque to + * everyone but the Suff module) + */ struct Suffix *suffix; - /* Filename where the GNode got defined */ - /* XXX: What is the lifetime of this string? */ + /* Filename where the GNode got defined, unlimited lifetime */ const char *fname; - /* Line number where the GNode got defined */ - int lineno; + /* Line number where the GNode got defined, 1-based */ + unsigned lineno; } GNode; /* Error levels for diagnostics during parsing. */ typedef enum ParseErrorLevel { - /* Exit when the current top-level makefile has been parsed - * completely. */ + /* + * Exit when the current top-level makefile has been parsed + * completely. + */ PARSE_FATAL = 1, /* Print "warning"; may be upgraded to fatal by the -w option. */ PARSE_WARNING, @@ -472,20 +538,20 @@ typedef enum ParseErrorLevel { /* * Values returned by Cond_EvalLine and Cond_EvalCondition. */ -typedef enum CondEvalResult { - COND_PARSE, /* Parse the next lines */ - COND_SKIP, /* Skip the next lines */ - COND_INVALID /* Not a conditional statement */ -} CondEvalResult; +typedef enum CondResult { + CR_TRUE, /* Parse the next lines */ + CR_FALSE, /* Skip the next lines */ + CR_ERROR /* Unknown directive or parse error */ +} CondResult; /* Names of the variables that are "local" to a specific target. */ -#define TARGET "@" /* Target of dependency */ -#define OODATE "?" /* All out-of-date sources */ -#define ALLSRC ">" /* All sources */ -#define IMPSRC "<" /* Source implied by transformation */ -#define PREFIX "*" /* Common prefix */ -#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ -#define MEMBER "%" /* Member in "archive(member)" syntax */ +#define TARGET "@" /* Target of dependency */ +#define OODATE "?" /* All out-of-date sources */ +#define ALLSRC ">" /* All sources */ +#define IMPSRC "<" /* Source implied by transformation */ +#define PREFIX "*" /* Common prefix */ +#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ +#define MEMBER "%" /* Member in "archive(member)" syntax */ /* * Global Variables @@ -543,6 +609,7 @@ extern int makelevel; extern char *makeDependfile; /* If we replaced environ, this will be non-NULL. */ extern char **savedEnv; +extern GNode *mainNode; extern pid_t myPid; @@ -560,34 +627,32 @@ extern pid_t myPid; # define MAKE_LEVEL_ENV "MAKELEVEL" #endif -typedef enum DebugFlags { - DEBUG_NONE = 0, - DEBUG_ARCH = 1 << 0, - DEBUG_COND = 1 << 1, - DEBUG_CWD = 1 << 2, - DEBUG_DIR = 1 << 3, - DEBUG_ERROR = 1 << 4, - DEBUG_FOR = 1 << 5, - DEBUG_GRAPH1 = 1 << 6, - DEBUG_GRAPH2 = 1 << 7, - DEBUG_GRAPH3 = 1 << 8, - DEBUG_HASH = 1 << 9, - DEBUG_JOB = 1 << 10, - DEBUG_LOUD = 1 << 11, - DEBUG_MAKE = 1 << 12, - DEBUG_META = 1 << 13, - DEBUG_PARSE = 1 << 14, - DEBUG_SCRIPT = 1 << 15, - DEBUG_SHELL = 1 << 16, - DEBUG_SUFF = 1 << 17, - DEBUG_TARG = 1 << 18, - DEBUG_VAR = 1 << 19, - DEBUG_ALL = (1 << 20) - 1 +typedef struct DebugFlags { + bool DEBUG_ARCH:1; + bool DEBUG_COND:1; + bool DEBUG_CWD:1; + bool DEBUG_DIR:1; + bool DEBUG_ERROR:1; + bool DEBUG_FOR:1; + bool DEBUG_GRAPH1:1; + bool DEBUG_GRAPH2:1; + bool DEBUG_GRAPH3:1; + bool DEBUG_HASH:1; + bool DEBUG_JOB:1; + bool DEBUG_LOUD:1; + bool DEBUG_MAKE:1; + bool DEBUG_META:1; + bool DEBUG_PARSE:1; + bool DEBUG_SCRIPT:1; + bool DEBUG_SHELL:1; + bool DEBUG_SUFF:1; + bool DEBUG_TARG:1; + bool DEBUG_VAR:1; } DebugFlags; #define CONCAT(a, b) a##b -#define DEBUG(module) ((opts.debug & CONCAT(DEBUG_, module)) != 0) +#define DEBUG(module) (opts.debug.CONCAT(DEBUG_, module)) void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); @@ -597,8 +662,8 @@ void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); debug_printf args; \ } while (false) -#define DEBUG0(module, text) \ - DEBUG_IMPL(module, ("%s", text)) +#define DEBUG0(module, fmt) \ + DEBUG_IMPL(module, (fmt)) #define DEBUG1(module, fmt, arg1) \ DEBUG_IMPL(module, (fmt, arg1)) #define DEBUG2(module, fmt, arg1, arg2) \ @@ -621,17 +686,21 @@ typedef struct CmdOpts { /* -B: whether we are make compatible */ bool compatMake; - /* -d: debug control: There is one bit per module. It is up to the - * module what debug information to print. */ + /* + * -d: debug control: There is one bit per module. It is up to the + * module what debug information to print. + */ DebugFlags debug; /* -df: debug output is written here - default stderr */ FILE *debug_file; - /* -dL: lint mode + /* + * -dL: lint mode * * Runs make in strict mode, with additional checks and better error - * handling. */ + * handling. + */ bool strict; /* -dV: for the -V option, print unexpanded variable values */ @@ -646,12 +715,16 @@ typedef struct CmdOpts { /* -i: if true, ignore all errors from shell commands */ bool ignoreErrors; - /* -j: the maximum number of jobs that can run in parallel; - * this is coordinated with the submakes */ + /* + * -j: the maximum number of jobs that can run in parallel; this is + * coordinated with the submakes + */ int maxJobs; - /* -k: if true and an error occurs while making a node, continue - * making nodes that do not depend on the erroneous node */ + /* + * -k: if true and an error occurs while making a node, continue + * making nodes that do not depend on the erroneous node + */ bool keepgoing; /* -N: execute no commands from the targets */ @@ -664,17 +737,19 @@ typedef struct CmdOpts { * -q: if true, do not really make anything, just see if the targets * are out-of-date */ - bool queryFlag; + bool query; /* -r: raw mode, do not load the builtin rules. */ bool noBuiltins; /* -s: don't echo the shell commands before executing them */ - bool beSilent; + bool silent; - /* -t: touch the targets if they are out-of-date, but don't actually - * make them */ - bool touchFlag; + /* + * -t: touch the targets if they are out-of-date, but don't actually + * make them + */ + bool touch; /* -[Vv]: print expanded or unexpanded selected variables */ PrintVarsMode printVars; @@ -687,90 +762,364 @@ typedef struct CmdOpts { /* -w: print 'Entering' and 'Leaving' for submakes */ bool enterFlag; - /* -X: if true, do not export variables set on the command line to the - * environment. */ + /* + * -X: if true, do not export variables set on the command line to + * the environment. + */ bool varNoExportEnv; - /* The target names specified on the command line. - * Used to resolve .if make(...) statements. */ + /* + * The target names specified on the command line. Used to resolve + * .if make(...) statements. + */ StringList create; } CmdOpts; extern CmdOpts opts; -#include "nonints.h" +/* arch.c */ +void Arch_Init(void); +void Arch_End(void); +bool Arch_ParseArchive(char **, GNodeList *, GNode *); +void Arch_Touch(GNode *); +void Arch_TouchLib(GNode *); +void Arch_UpdateMTime(GNode *gn); +void Arch_UpdateMemberMTime(GNode *gn); +void Arch_FindLib(GNode *, SearchPath *); +bool Arch_LibOODate(GNode *) MAKE_ATTR_USE; +bool Arch_IsLib(GNode *) MAKE_ATTR_USE; + +/* compat.c */ +bool Compat_RunCommand(const char *, GNode *, StringListNode *); +void Compat_Run(GNodeList *); +void Compat_Make(GNode *, GNode *); + +/* cond.c */ +CondResult Cond_EvalCondition(const char *) MAKE_ATTR_USE; +CondResult Cond_EvalLine(const char *) MAKE_ATTR_USE; +void Cond_restore_depth(unsigned int); +unsigned int Cond_save_depth(void) MAKE_ATTR_USE; + +/* dir.c; see also dir.h */ + +MAKE_INLINE const char * MAKE_ATTR_USE +str_basename(const char *pathname) +{ + const char *lastSlash = strrchr(pathname, '/'); + return lastSlash != NULL ? lastSlash + 1 : pathname; +} + +MAKE_INLINE SearchPath * MAKE_ATTR_USE +SearchPath_New(void) +{ + SearchPath *path = bmake_malloc(sizeof *path); + Lst_Init(&path->dirs); + return path; +} + +void SearchPath_Free(SearchPath *); + +/* for.c */ +struct ForLoop; +int For_Eval(const char *) MAKE_ATTR_USE; +bool For_Accum(const char *, int *) MAKE_ATTR_USE; +void For_Run(unsigned, unsigned); +bool For_NextIteration(struct ForLoop *, Buffer *); +char *ForLoop_Details(struct ForLoop *); +void ForLoop_Free(struct ForLoop *); + +/* job.c */ +void JobReapChild(pid_t, int, bool); + +/* main.c */ +void Main_ParseArgLine(const char *); +char *Cmd_Exec(const char *, char **) MAKE_ATTR_USE; +void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); +void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; +void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; +void DieHorribly(void) MAKE_ATTR_DEAD; +void Finish(int) MAKE_ATTR_DEAD; +bool unlink_file(const char *) MAKE_ATTR_USE; +void execDie(const char *, const char *); +char *getTmpdir(void) MAKE_ATTR_USE; +bool ParseBoolean(const char *, bool) MAKE_ATTR_USE; +const char *cached_realpath(const char *, char *); +bool GetBooleanExpr(const char *, bool); + +/* parse.c */ +void Parse_Init(void); +void Parse_End(void); + +void PrintLocation(FILE *, bool, const char *, unsigned); +void PrintStackTrace(bool); +void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); +bool Parse_VarAssign(const char *, bool, GNode *) MAKE_ATTR_USE; +void Parse_AddIncludeDir(const char *); +void Parse_File(const char *, int); +void Parse_PushInput(const char *, unsigned, unsigned, Buffer, + struct ForLoop *); +void Parse_MainName(GNodeList *); +int Parse_NumErrors(void) MAKE_ATTR_USE; + + +/* suff.c */ +void Suff_Init(void); +void Suff_End(void); + +void Suff_ClearSuffixes(void); +bool Suff_IsTransform(const char *) MAKE_ATTR_USE; +GNode *Suff_AddTransform(const char *); +void Suff_EndTransform(GNode *); +void Suff_AddSuffix(const char *); +SearchPath *Suff_GetPath(const char *) MAKE_ATTR_USE; +void Suff_ExtendPaths(void); +void Suff_AddInclude(const char *); +void Suff_AddLib(const char *); +void Suff_FindDeps(GNode *); +SearchPath *Suff_FindPath(GNode *) MAKE_ATTR_USE; +void Suff_SetNull(const char *); +void Suff_PrintAll(void); +char *Suff_NamesStr(void) MAKE_ATTR_USE; + +/* targ.c */ +void Targ_Init(void); +void Targ_End(void); + +void Targ_Stats(void); +GNodeList *Targ_List(void) MAKE_ATTR_USE; +GNode *GNode_New(const char *) MAKE_ATTR_USE; +GNode *Targ_FindNode(const char *) MAKE_ATTR_USE; +GNode *Targ_GetNode(const char *) MAKE_ATTR_USE; +GNode *Targ_NewInternalNode(const char *) MAKE_ATTR_USE; +GNode *Targ_GetEndNode(void); +void Targ_FindList(GNodeList *, StringList *); +void Targ_PrintCmds(GNode *); +void Targ_PrintNode(GNode *, int); +void Targ_PrintNodes(GNodeList *, int); +const char *Targ_FmtTime(time_t) MAKE_ATTR_USE; +void Targ_PrintType(GNodeType); +void Targ_PrintGraph(int); +void Targ_Propagate(void); +const char *GNodeMade_Name(GNodeMade) MAKE_ATTR_USE; + +/* var.c */ +void Var_Init(void); +void Var_End(void); + +typedef enum VarEvalMode { + + /* + * Only parse the expression but don't evaluate any part of it. + * + * TODO: Document what Var_Parse and Var_Subst return in this mode. + * As of 2021-03-15, they return unspecified, inconsistent results. + */ + VARE_PARSE_ONLY, + + /* Parse and evaluate the expression. */ + VARE_WANTRES, + + /* + * Parse and evaluate the expression. It is an error if a + * subexpression evaluates to undefined. + */ + VARE_UNDEFERR, + + /* + * Parse and evaluate the expression. Keep '$$' as '$$' instead of + * reducing it to a single '$'. Subexpressions that evaluate to + * undefined expand to an empty string. + * + * Used in variable assignments using the ':=' operator. It allows + * multiple such assignments to be chained without accidentally + * expanding '$$file' to '$file' in the first assignment and + * interpreting it as '${f}' followed by 'ile' in the next assignment. + */ + VARE_EVAL_KEEP_DOLLAR, + + /* + * Parse and evaluate the expression. Keep undefined variables as-is + * instead of expanding them to an empty string. + * + * Example for a ':=' assignment: + * CFLAGS = $(.INCLUDES) + * CFLAGS := -I.. $(CFLAGS) + * # If .INCLUDES (an undocumented special variable, by the + * # way) is still undefined, the updated CFLAGS becomes + * # "-I.. $(.INCLUDES)". + */ + VARE_EVAL_KEEP_UNDEF, + + /* + * Parse and evaluate the expression. Keep '$$' as '$$' and preserve + * undefined subexpressions. + */ + VARE_KEEP_DOLLAR_UNDEF +} VarEvalMode; + +typedef enum VarSetFlags { + VAR_SET_NONE = 0, + + /* do not export */ + VAR_SET_NO_EXPORT = 1 << 0, + + /* + * Make the variable read-only. No further modification is possible, + * except for another call to Var_Set with the same flag. + */ + VAR_SET_READONLY = 1 << 1 +} VarSetFlags; + +/* The state of error handling returned by Var_Parse. */ +typedef enum VarParseResult { + + /* Both parsing and evaluation succeeded. */ + VPR_OK, + + /* Parsing or evaluating failed, with an error message. */ + VPR_ERR, + + /* + * Parsing succeeded, undefined expressions are allowed and the + * expression was still undefined after applying all modifiers. + * No error message is printed in this case. + * + * Some callers handle this case differently, so return this + * information to them, for now. + * + * TODO: Instead of having this special return value, rather ensure + * that VARE_EVAL_KEEP_UNDEF is processed properly. + */ + VPR_UNDEF + +} VarParseResult; + +typedef enum VarExportMode { + /* .export-env */ + VEM_ENV, + /* .export: Initial export or update an already exported variable. */ + VEM_PLAIN, + /* .export-literal: Do not expand the variable value. */ + VEM_LITERAL +} VarExportMode; + +void Var_Delete(GNode *, const char *); +void Var_Undef(const char *); +void Var_Set(GNode *, const char *, const char *); +void Var_SetExpand(GNode *, const char *, const char *); +void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags); +void Var_Append(GNode *, const char *, const char *); +void Var_AppendExpand(GNode *, const char *, const char *); +bool Var_Exists(GNode *, const char *) MAKE_ATTR_USE; +bool Var_ExistsExpand(GNode *, const char *) MAKE_ATTR_USE; +FStr Var_Value(GNode *, const char *) MAKE_ATTR_USE; +const char *GNode_ValueDirect(GNode *, const char *) MAKE_ATTR_USE; +VarParseResult Var_Parse(const char **, GNode *, VarEvalMode, FStr *); +VarParseResult Var_Subst(const char *, GNode *, VarEvalMode, char **); +void Var_Expand(FStr *, GNode *, VarEvalMode); +void Var_Stats(void); +void Var_Dump(GNode *); +void Var_ReexportVars(void); +void Var_Export(VarExportMode, const char *); +void Var_ExportVars(const char *); +void Var_UnExport(bool, const char *); + +void Global_Set(const char *, const char *); +void Global_Append(const char *, const char *); +void Global_Delete(const char *); + +/* util.c */ +typedef void (*SignalProc)(int); +SignalProc bmake_signal(int, SignalProc); + +/* make.c */ void GNode_UpdateYoungestChild(GNode *, GNode *); -bool GNode_IsOODate(GNode *); +bool GNode_IsOODate(GNode *) MAKE_ATTR_USE; void Make_ExpandUse(GNodeList *); -time_t Make_Recheck(GNode *); +time_t Make_Recheck(GNode *) MAKE_ATTR_USE; void Make_HandleUse(GNode *, GNode *); void Make_Update(GNode *); void GNode_SetLocalVars(GNode *); bool Make_Run(GNodeList *); -bool shouldDieQuietly(GNode *, int); +bool shouldDieQuietly(GNode *, int) MAKE_ATTR_USE; void PrintOnError(GNode *, const char *); void Main_ExportMAKEFLAGS(bool); bool Main_SetObjdir(bool, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); -int mkTempFile(const char *, char *, size_t); +int mkTempFile(const char *, char *, size_t) MAKE_ATTR_USE; int str2Lst_Append(StringList *, char *); void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); -bool GNode_ShouldExecute(GNode *gn); +bool GNode_ShouldExecute(GNode *gn) MAKE_ATTR_USE; /* See if the node was seen on the left-hand side of a dependency operator. */ -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE GNode_IsTarget(const GNode *gn) { return (gn->type & OP_OPMASK) != OP_NONE; } -MAKE_INLINE const char * +MAKE_INLINE const char * MAKE_ATTR_USE GNode_Path(const GNode *gn) { return gn->path != NULL ? gn->path : gn->name; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE GNode_IsWaitingFor(const GNode *gn) { return gn->flags.remake && gn->made <= REQUESTED; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE GNode_IsReady(const GNode *gn) { return gn->made > DEFERRED; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE GNode_IsDone(const GNode *gn) { return gn->made >= MADE; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE GNode_IsError(const GNode *gn) { return gn->made == ERROR || gn->made == ABORTED; } -MAKE_INLINE const char * +MAKE_INLINE bool MAKE_ATTR_USE +GNode_IsMainCandidate(const GNode *gn) +{ + return (gn->type & (OP_NOTMAIN | OP_USE | OP_USEBEFORE | + OP_EXEC | OP_TRANSFORM)) == 0; +} + +/* Return whether the target file should be preserved on interrupt. */ +MAKE_INLINE bool MAKE_ATTR_USE +GNode_IsPrecious(const GNode *gn) +{ + /* XXX: Why are '::' targets precious? */ + return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP); +} + +MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarTarget(GNode *gn) { return GNode_ValueDirect(gn, TARGET); } -MAKE_INLINE const char * +MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarOodate(GNode *gn) { return GNode_ValueDirect(gn, OODATE); } -MAKE_INLINE const char * +MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarAllsrc(GNode *gn) { return GNode_ValueDirect(gn, ALLSRC); } -MAKE_INLINE const char * +MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarImpsrc(GNode *gn) { return GNode_ValueDirect(gn, IMPSRC); } -MAKE_INLINE const char * +MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarPrefix(GNode *gn) { return GNode_ValueDirect(gn, PREFIX); } -MAKE_INLINE const char * +MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarArchive(GNode *gn) { return GNode_ValueDirect(gn, ARCHIVE); } -MAKE_INLINE const char * +MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarMember(GNode *gn) { return GNode_ValueDirect(gn, MEMBER); } -MAKE_INLINE void * +MAKE_INLINE void * MAKE_ATTR_USE UNCONST(const void *ptr) { void *ret; @@ -795,19 +1144,21 @@ UNCONST(const void *ptr) #define KILLPG(pid, sig) killpg((pid), (sig)) #endif -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE +ch_islower(char ch) { return islower((unsigned char)ch) != 0; } +MAKE_INLINE bool MAKE_ATTR_USE ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; } -MAKE_INLINE char +MAKE_INLINE char MAKE_ATTR_USE ch_tolower(char ch) { return (char)tolower((unsigned char)ch); } -MAKE_INLINE char +MAKE_INLINE char MAKE_ATTR_USE ch_toupper(char ch) { return (char)toupper((unsigned char)ch); } MAKE_INLINE void @@ -824,6 +1175,17 @@ cpp_skip_hspace(const char **pp) (*pp)++; } +MAKE_INLINE bool +cpp_skip_string(const char **pp, const char *s) +{ + const char *p = *pp; + while (*p == *s && *s != '\0') + p++, s++; + if (*s == '\0') + *pp = p; + return *s == '\0'; +} + MAKE_INLINE void pp_skip_whitespace(char **pp) { @@ -863,4 +1225,4 @@ pp_skip_hspace(char **pp) # define MAKE_RCSID(id) static volatile char rcsid[] = id #endif -#endif /* MAKE_MAKE_H */ +#endif diff --git a/make_malloc.c b/make_malloc.c index 42a69f43704f..ea347e0ec2ca 100644 --- a/make_malloc.c +++ b/make_malloc.c @@ -1,4 +1,4 @@ -/* $NetBSD: make_malloc.c,v 1.25 2021/01/19 20:51:46 rillig Exp $ */ +/* $NetBSD: make_malloc.c,v 1.26 2022/01/07 08:30:04 rillig Exp $ */ /* * Copyright (c) 2009 The NetBSD Foundation, Inc. @@ -30,7 +30,7 @@ #include "make.h" -MAKE_RCSID("$NetBSD: make_malloc.c,v 1.25 2021/01/19 20:51:46 rillig Exp $"); +MAKE_RCSID("$NetBSD: make_malloc.c,v 1.26 2022/01/07 08:30:04 rillig Exp $"); #ifndef USE_EMALLOC @@ -57,12 +57,12 @@ bmake_malloc(size_t len) char * bmake_strdup(const char *str) { - size_t len; + size_t size; char *p; - len = strlen(str) + 1; - p = bmake_malloc(len); - return memcpy(p, str, len); + size = strlen(str) + 1; + p = bmake_malloc(size); + return memcpy(p, str, size); } /* Allocate a string starting from str with exactly len characters. */ diff --git a/make_malloc.h b/make_malloc.h index 72f6c11fd882..b1cfe8487837 100644 --- a/make_malloc.h +++ b/make_malloc.h @@ -1,4 +1,4 @@ -/* $NetBSD: make_malloc.h,v 1.16 2021/01/19 20:51:46 rillig Exp $ */ +/* $NetBSD: make_malloc.h,v 1.18 2021/12/15 11:01:39 rillig Exp $ */ /* * Copyright (c) 2009 The NetBSD Foundation, Inc. @@ -27,10 +27,10 @@ */ #ifndef USE_EMALLOC -void *bmake_malloc(size_t); -void *bmake_realloc(void *, size_t); -char *bmake_strdup(const char *); -char *bmake_strldup(const char *, size_t); +void *bmake_malloc(size_t) MAKE_ATTR_USE; +void *bmake_realloc(void *, size_t) MAKE_ATTR_USE; +char *bmake_strdup(const char *) MAKE_ATTR_USE; +char *bmake_strldup(const char *, size_t) MAKE_ATTR_USE; #else #include #define bmake_malloc(n) emalloc(n) @@ -39,18 +39,4 @@ char *bmake_strldup(const char *, size_t); #define bmake_strldup(s, n) estrndup(s, n) #endif -char *bmake_strsedup(const char *, const char *); - -/* - * Thin wrapper around free(3) to avoid the extra function call in case - * p is NULL, to save a few machine instructions. - * - * The case of a NULL pointer happens especially often after Var_Value, - * since only environment variables need to be freed, but not others. - */ -MAKE_INLINE void -bmake_free(void *p) -{ - if (p != NULL) - free(p); -} +char *bmake_strsedup(const char *, const char *) MAKE_ATTR_USE; diff --git a/meta.c b/meta.c index 67c57647f2af..2d6ab3ab8a3d 100644 --- a/meta.c +++ b/meta.c @@ -1,4 +1,4 @@ -/* $NetBSD: meta.c,v 1.185 2021/11/27 22:04:02 rillig Exp $ */ +/* $NetBSD: meta.c,v 1.196 2022/02/04 23:22:19 rillig Exp $ */ /* * Implement 'meta' mode. @@ -69,6 +69,9 @@ static char *metaIgnorePathsStr; /* string storage for the list */ #ifndef MAKE_META_IGNORE_FILTER #define MAKE_META_IGNORE_FILTER ".MAKE.META.IGNORE_FILTER" #endif +#ifndef MAKE_META_CMP_FILTER +#define MAKE_META_CMP_FILTER ".MAKE.META.CMP_FILTER" +#endif bool useMeta = false; static bool useFilemon = false; @@ -80,6 +83,7 @@ static bool metaVerbose = false; static bool metaIgnoreCMDs = false; /* ignore CMDs in .meta files */ static bool metaIgnorePatterns = false; /* do we need to do pattern matches */ static bool metaIgnoreFilter = false; /* do we have more complex filtering? */ +static bool metaCmpFilter = false; /* do we have CMP_FILTER ? */ static bool metaCurdirOk = false; /* write .meta in .CURDIR Ok? */ static bool metaSilent = false; /* if we have a .meta be SILENT */ @@ -208,42 +212,25 @@ filemon_read(FILE *mfp, int fd) * we use this, to clean up ./ and ../ */ static void -eat_dots(char *buf, size_t bufsz, int dots) +eat_dots(char *buf) { - char *cp; - char *cp2; - const char *eat; - size_t eatlen; + char *p; - switch (dots) { - case 1: - eat = "/./"; - eatlen = 2; - break; - case 2: - eat = "/../"; - eatlen = 3; - break; - default: - return; - } + while ((p = strstr(buf, "/./")) != NULL) + memmove(p, p + 2, strlen(p + 2) + 1); - do { - cp = strstr(buf, eat); - if (cp != NULL) { - cp2 = cp + eatlen; - if (dots == 2 && cp > buf) { - do { - cp--; - } while (cp > buf && *cp != '/'); - } - if (*cp == '/') { - strlcpy(cp, cp2, bufsz - (size_t)(cp - buf)); - } else { - return; /* can't happen? */ - } + while ((p = strstr(buf, "/../")) != NULL) { + char *p2 = p + 3; + if (p > buf) { + do { + p--; + } while (p > buf && *p != '/'); } - } while (cp != NULL); + if (*p == '/') + memmove(p, p2, strlen(p2) + 1); + else + return; /* can't happen? */ + } } static char * @@ -287,8 +274,7 @@ meta_name(char *mname, size_t mnamelen, } else { snprintf(buf, sizeof buf, "%s/%s", cwd, tname); } - eat_dots(buf, sizeof buf, 1); /* ./ */ - eat_dots(buf, sizeof buf, 2); /* ../ */ + eat_dots(buf); tname = buf; } } @@ -330,15 +316,14 @@ is_submake(const char *cmd, GNode *gn) static const char *p_make = NULL; static size_t p_len; char *mp = NULL; - const char *cp, *cp2; + const char *cp2; bool rc = false; if (p_make == NULL) { p_make = Var_Value(gn, ".MAKE").str; p_len = strlen(p_make); } - cp = strchr(cmd, '$'); - if (cp != NULL) { + if (strchr(cmd, '$') != NULL) { (void)Var_Subst(cmd, gn, VARE_WANTRES, &mp); /* TODO: handle errors */ cmd = mp; @@ -385,13 +370,7 @@ printCMD(const char *ucmd, FILE *fp, GNode *gn) { FStr xcmd = FStr_InitRefer(ucmd); - if (strchr(ucmd, '$') != NULL) { - char *expanded; - (void)Var_Subst(ucmd, gn, VARE_WANTRES, &expanded); - /* TODO: handle errors */ - xcmd = FStr_InitOwn(expanded); - } - + Var_Expand(&xcmd, gn, VARE_WANTRES); fprintf(fp, "CMD %s\n", xcmd.str); FStr_Done(&xcmd); } @@ -601,7 +580,6 @@ meta_mode_init(const char *make_mode) { static bool once = false; const char *cp; - FStr value; useMeta = true; useFilemon = true; @@ -658,16 +636,9 @@ meta_mode_init(const char *make_mode) /* * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS} */ - value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_PATTERNS); - if (value.str != NULL) { - metaIgnorePatterns = true; - FStr_Done(&value); - } - value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_FILTER); - if (value.str != NULL) { - metaIgnoreFilter = true; - FStr_Done(&value); - } + metaIgnorePatterns = Var_Exists(SCOPE_GLOBAL, MAKE_META_IGNORE_PATTERNS); + metaIgnoreFilter = Var_Exists(SCOPE_GLOBAL, MAKE_META_IGNORE_FILTER); + metaCmpFilter = Var_Exists(SCOPE_GLOBAL, MAKE_META_CMP_FILTER); } /* @@ -1061,7 +1032,7 @@ meta_ignore(GNode *gn, const char *p) * Setting oodate true will have that effect. */ #define CHECK_VALID_META(p) if (!(p != NULL && *p != '\0')) { \ - warnx("%s: %d: malformed", fname, lineno); \ + warnx("%s: %u: malformed", fname, lineno); \ oodate = true; \ continue; \ } @@ -1084,6 +1055,39 @@ append_if_new(StringList *list, const char *str) Lst_Append(list, bmake_strdup(str)); } +static char * +meta_filter_cmd(Buffer *buf, GNode *gn, char *s) +{ + Buf_Clear(buf); + Buf_AddStr(buf, "${"); + Buf_AddStr(buf, s); + Buf_AddStr(buf, ":L:${" MAKE_META_CMP_FILTER ":ts:}}"); + Var_Subst(buf->data, gn, VARE_WANTRES, &s); + return s; +} + +static int +meta_cmd_cmp(GNode *gn, char *a, char *b, bool filter) +{ + static bool once = false; + static Buffer buf; + int rc; + + rc = strcmp(a, b); + if (rc == 0 || !filter) + return rc; + if (!once) { + once = true; + Buf_Init(&buf); + } + a = meta_filter_cmd(&buf, gn, a); + b = meta_filter_cmd(&buf, gn, b); + rc = strcmp(a, b); + free(a); + free(b); + return rc; +} + bool meta_oodate(GNode *gn, bool oodate) { @@ -1108,6 +1112,7 @@ meta_oodate(GNode *gn, bool oodate) bool needOODATE = false; StringList missingFiles; bool have_filemon = false; + bool cmp_filter; if (oodate) return oodate; /* we're done */ @@ -1139,7 +1144,7 @@ meta_oodate(GNode *gn, bool oodate) if ((fp = fopen(fname, "r")) != NULL) { static char *buf = NULL; static size_t bufsz; - int lineno = 0; + unsigned lineno = 0; int lastpid = 0; int pid; int x; @@ -1167,13 +1172,16 @@ meta_oodate(GNode *gn, bool oodate) /* we want to track all the .meta we read */ Global_Append(".MAKE.META.FILES", fname); + cmp_filter = metaCmpFilter ? metaCmpFilter : + Var_Exists(gn, MAKE_META_CMP_FILTER); + cmdNode = gn->commands.first; while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) { lineno++; if (buf[x - 1] == '\n') buf[x - 1] = '\0'; else { - warnx("%s: %d: line truncated at %u", fname, lineno, x); + warnx("%s: %u: line truncated at %u", fname, lineno, x); oodate = true; break; } @@ -1194,7 +1202,7 @@ meta_oodate(GNode *gn, bool oodate) /* Delimit the record type. */ p = buf; #ifdef DEBUG_META_MODE - DEBUG3(META, "%s: %d: %s\n", fname, lineno, buf); + DEBUG3(META, "%s: %u: %s\n", fname, lineno, buf); #endif strsep(&p, " "); if (have_filemon) { @@ -1240,8 +1248,8 @@ meta_oodate(GNode *gn, bool oodate) if (lastpid > 0) { /* We need to remember these. */ - Global_SetExpand(lcwd_vname, lcwd); - Global_SetExpand(ldir_vname, latestdir); + Global_Set(lcwd_vname, lcwd); + Global_Set(ldir_vname, latestdir); } snprintf(lcwd_vname, sizeof lcwd_vname, LCWD_VNAME_FMT, pid); snprintf(ldir_vname, sizeof ldir_vname, LDIR_VNAME_FMT, pid); @@ -1262,7 +1270,7 @@ meta_oodate(GNode *gn, bool oodate) continue; #ifdef DEBUG_META_MODE if (DEBUG(META)) - debug_printf("%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n", + debug_printf("%s: %u: %d: %c: cwd=%s lcwd=%s ldir=%s\n", fname, lineno, pid, buf[0], cwd, lcwd, latestdir); #endif @@ -1274,8 +1282,8 @@ meta_oodate(GNode *gn, bool oodate) /* Process according to record type. */ switch (buf[0]) { case 'X': /* eXit */ - Var_DeleteExpand(SCOPE_GLOBAL, lcwd_vname); - Var_DeleteExpand(SCOPE_GLOBAL, ldir_vname); + Var_Delete(SCOPE_GLOBAL, lcwd_vname); + Var_Delete(SCOPE_GLOBAL, ldir_vname); lastpid = 0; /* no need to save ldir_vname */ break; @@ -1287,13 +1295,13 @@ meta_oodate(GNode *gn, bool oodate) child = atoi(p); if (child > 0) { snprintf(cldir, sizeof cldir, LCWD_VNAME_FMT, child); - Global_SetExpand(cldir, lcwd); + Global_Set(cldir, lcwd); snprintf(cldir, sizeof cldir, LDIR_VNAME_FMT, child); - Global_SetExpand(cldir, latestdir); + Global_Set(cldir, latestdir); #ifdef DEBUG_META_MODE if (DEBUG(META)) debug_printf( - "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", + "%s: %u: %d: cwd=%s lcwd=%s ldir=%s\n", fname, lineno, child, cwd, lcwd, latestdir); #endif @@ -1305,10 +1313,10 @@ meta_oodate(GNode *gn, bool oodate) /* Update lcwd and latest directory. */ strlcpy(latestdir, p, sizeof latestdir); strlcpy(lcwd, p, sizeof lcwd); - Global_SetExpand(lcwd_vname, lcwd); - Global_SetExpand(ldir_vname, lcwd); + Global_Set(lcwd_vname, lcwd); + Global_Set(ldir_vname, lcwd); #ifdef DEBUG_META_MODE - DEBUG4(META, "%s: %d: cwd=%s ldir=%s\n", + DEBUG4(META, "%s: %u: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd); #endif break; @@ -1461,7 +1469,7 @@ meta_oodate(GNode *gn, bool oodate) for (sdp = sdirs; *sdp != NULL && !found; sdp++) { #ifdef DEBUG_META_MODE - DEBUG3(META, "%s: %d: looking for: %s\n", + DEBUG3(META, "%s: %u: looking for: %s\n", fname, lineno, *sdp); #endif if (cached_stat(*sdp, &cst) == 0) { @@ -1471,12 +1479,12 @@ meta_oodate(GNode *gn, bool oodate) } if (found) { #ifdef DEBUG_META_MODE - DEBUG3(META, "%s: %d: found: %s\n", + DEBUG3(META, "%s: %u: found: %s\n", fname, lineno, p); #endif if (!S_ISDIR(cst.cst_mode) && cst.cst_mtime > gn->mtime) { - DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n", + DEBUG3(META, "%s: %u: file '%s' is newer than the target...\n", fname, lineno, p); oodate = true; } else if (S_ISDIR(cst.cst_mode)) { @@ -1508,7 +1516,7 @@ meta_oodate(GNode *gn, bool oodate) * meta data file. */ if (cmdNode == NULL) { - DEBUG2(META, "%s: %d: there were more build commands in the meta data file than there are now...\n", + DEBUG2(META, "%s: %u: there were more build commands in the meta data file than there are now...\n", fname, lineno); oodate = true; } else { @@ -1525,7 +1533,7 @@ meta_oodate(GNode *gn, bool oodate) } if (hasOODATE) { needOODATE = true; - DEBUG2(META, "%s: %d: cannot compare command using .OODATE\n", + DEBUG2(META, "%s: %u: cannot compare command using .OODATE\n", fname, lineno); } (void)Var_Subst(cmd, gn, VARE_UNDEFERR, &cmd); @@ -1548,7 +1556,7 @@ meta_oodate(GNode *gn, bool oodate) x = n; lineno++; if (buf[x - 1] != '\n') { - warnx("%s: %d: line truncated at %u", fname, lineno, x); + warnx("%s: %u: line truncated at %u", fname, lineno, x); break; } cp = strchr(cp + 1, '\n'); @@ -1559,8 +1567,8 @@ meta_oodate(GNode *gn, bool oodate) if (p != NULL && !hasOODATE && !(gn->type & OP_NOMETA_CMP) && - (strcmp(p, cmd) != 0)) { - DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n", + (meta_cmd_cmp(gn, p, cmd, cmp_filter) != 0)) { + DEBUG4(META, "%s: %u: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); if (!metaIgnoreCMDs) oodate = true; @@ -1574,13 +1582,13 @@ meta_oodate(GNode *gn, bool oodate) * that weren't in the meta data file. */ if (!oodate && cmdNode != NULL) { - DEBUG2(META, "%s: %d: there are extra build commands now that weren't in the meta data file\n", + DEBUG2(META, "%s: %u: there are extra build commands now that weren't in the meta data file\n", fname, lineno); oodate = true; } CHECK_VALID_META(p); if (strcmp(p, cwd) != 0) { - DEBUG4(META, "%s: %d: the current working directory has changed from '%s' to '%s'\n", + DEBUG4(META, "%s: %u: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir); oodate = true; } diff --git a/meta.h b/meta.h index d4f02da4e78b..5d247422ef8c 100644 --- a/meta.h +++ b/meta.h @@ -1,4 +1,4 @@ -/* $NetBSD: meta.h,v 1.10 2021/04/03 11:08:40 rillig Exp $ */ +/* $NetBSD: meta.h,v 1.11 2021/12/15 09:53:41 rillig Exp $ */ /* * Things needed for 'meta' mode. @@ -46,13 +46,13 @@ void meta_mode_init(const char *); void meta_job_start(struct Job *, GNode *); void meta_job_child(struct Job *); void meta_job_parent(struct Job *, pid_t); -int meta_job_fd(struct Job *); -int meta_job_event(struct Job *); +int meta_job_fd(struct Job *) MAKE_ATTR_USE; +int meta_job_event(struct Job *) MAKE_ATTR_USE; void meta_job_error(struct Job *, GNode *, bool, int); void meta_job_output(struct Job *, char *, const char *); int meta_cmd_finish(void *); int meta_job_finish(struct Job *); -bool meta_oodate(GNode *, bool); +bool meta_oodate(GNode *, bool) MAKE_ATTR_USE; void meta_compat_start(void); void meta_compat_child(void); void meta_compat_parent(pid_t); diff --git a/metachar.h b/metachar.h index d6fd2299d43e..11711e876017 100644 --- a/metachar.h +++ b/metachar.h @@ -1,4 +1,4 @@ -/* $NetBSD: metachar.h,v 1.17 2021/06/21 18:54:41 rillig Exp $ */ +/* $NetBSD: metachar.h,v 1.20 2022/01/08 11:04:13 rillig Exp $ */ /* * Copyright (c) 2015 The NetBSD Foundation, Inc. @@ -35,18 +35,18 @@ extern const unsigned char _metachar[]; -MAKE_INLINE bool -is_shell_metachar(char c) +MAKE_INLINE bool MAKE_ATTR_USE +ch_is_shell_meta(char c) { return _metachar[c & 0x7f] != 0; } -MAKE_INLINE bool +MAKE_INLINE bool MAKE_ATTR_USE needshell(const char *cmd) { - while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=') + while (!ch_is_shell_meta(*cmd) && *cmd != ':' && *cmd != '=') cmd++; return *cmd != '\0'; } -#endif /* MAKE_METACHAR_H */ +#endif diff --git a/mk/ChangeLog b/mk/ChangeLog index 93bde20d754d..f196b086e7a1 100644 --- a/mk/ChangeLog +++ b/mk/ChangeLog @@ -1,3 +1,60 @@ +2022-02-04 Simon J Gerraty + + * install-mk (MK_VERSION): 20220204 + + * host-target.mk: use .MAKE.OS if available + +2022-02-02 Simon J Gerraty + + * install-mk (MK_VERSION): 20220202 + + * cc-wrap.mk: allow other entries in CC_WRAP_FILTER + We add our filter on extensions last, so prior filters + can apply to the whole value of .IMPSRC + +2022-02-01 Simon J Gerraty + + * cc-wrap.mk: take advantage of target local variables to + wrap compilers like CC CXX with wrappers like ccache distcc etc + +2022-01-28 Simon J Gerraty + + * meta2deps: we do not expect any trace data for setid apps + +2022-01-26 Simon J Gerraty + + * dirdeps.mk: ensure TARGET_SPEC and TARGET_SPEC_VARS are passed + to sub-make using DIRDEPS_CACHE + +2022-01-07 Simon J Gerraty + + * dirdeps.mk: use _cache_script to minimize the number of shells + forked when generating dirdeps.cache + +2022-01-02 Simon J Gerraty + + * install-mk (MK_VERSION): 20220101 + + * dirdeps.mk: initialize DEP_* and _debug_reldir earlier. + If initial DIRDEPS are from command line, create the target + _dirdeps_cmdline as an indication. + +2022-01-01 Simon J Gerraty + + * init.mk (_SKIP_BUILD): when doing DIRDEPS_BUILD + at top-level only some targets are allowed at level 0, + for leaf makefiles only the default (all) target is restricted + +2021-12-28 Simon J Gerraty + + * install-mk (MK_VERSION): 20211228 + + * meta2deps.py: filemon on Linux is not as reliable as we might + like, we do not want to update DIRDEPS if filemon output is + incomplete. Track pids that we 'E'xec and make sure we see an + e'X'it for each one. Throw an error if we are missing any 'X' + records. + 2021-12-12 Simon J Gerraty * sys.mk: simplify; include meta.sys.mk if MK_META_MODE is yes. diff --git a/mk/FILES b/mk/FILES index 91c4387e8ccb..de6259531e6e 100644 --- a/mk/FILES +++ b/mk/FILES @@ -6,6 +6,7 @@ auto.obj.mk autoconf.mk autodep.mk auto.dep.mk +cc-wrap.mk compiler.mk cython.mk dep.mk diff --git a/mk/cc-wrap.mk b/mk/cc-wrap.mk new file mode 100644 index 000000000000..7b9c6e0cb8c0 --- /dev/null +++ b/mk/cc-wrap.mk @@ -0,0 +1,61 @@ +# $Id: cc-wrap.mk,v 1.4 2022/02/02 17:41:56 sjg Exp $ +# +# @(#) Copyright (c) 2022, Simon J. Gerraty +# +# This file is provided in the hope that it will +# be of use. There is absolutely NO WARRANTY. +# Permission to copy, redistribute or otherwise +# use this file is hereby granted provided that +# the above copyright notice and this notice are +# left intact. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net +# + +.if ${MAKE_VERSION} >= 20220126 +# which targets are we interested in? +CC_WRAP_TARGETS ?= ${OBJS:U} ${POBJS:U} ${SOBJS:U} + +.if !empty(CC_WRAP_TARGETS) +# cleanup +# all the target assignments below are effectively := anyway +# so we might as well do this once +CC_WRAP_TARGETS := ${CC_WRAP_TARGETS:O:u} + +# what do we wrap? +CC_WRAP_LIST += CC CXX +CC_WRAP_LIST := ${CC_WRAP_LIST:O:u} + +# what might we wrap them with? +CC_WRAPPERS += ccache distcc icecc +CC_WRAPPERS := ${CC_WRAPPERS:O:u} +.for w in ${CC_WRAPPERS} +${w:tu} ?= $w +.endfor + +# we do not want to make all these targets out-of-date +# just because one of the above wrappers are enabled/disabled +${CC_WRAP_TARGETS}: .MAKE.META.CMP_FILTER = ${CC_WRAPPERS:tu@W@${$W}@:S,^,N,} + +# some object src types we should not wrap +CC_WRAP_SKIP_EXTS += s + +# We add the sequence we care about - excluding CC_WRAP_SKIP_EXTS +# but prior filters can apply to full value of .IMPSRC +CC_WRAP_FILTER += E:tl:${CC_WRAP_SKIP_EXTS:${M_ListToSkip}} +CC_WRAP_FILTER := ${CC_WRAP_FILTER:ts:} + +# last one enabled wins! +.for W in ${CC_WRAPPERS:tu} +.if ${MK_$W:U} == "yes" +.for C in ${CC_WRAP_LIST} +# we have to protect the check of .IMPSRC from Global expansion +${CC_WRAP_TARGETS}: $C = $${"$${.IMPSRC:${CC_WRAP_FILTER}}":?${$W}:} ${$C} +.endfor +.endif +.endfor + +.endif +.endif + diff --git a/mk/dirdeps.mk b/mk/dirdeps.mk index 5e735f236b9f..ee31e47c76c5 100644 --- a/mk/dirdeps.mk +++ b/mk/dirdeps.mk @@ -1,6 +1,6 @@ -# $Id: dirdeps.mk,v 1.147 2021/12/14 02:09:53 sjg Exp $ +# $Id: dirdeps.mk,v 1.151 2022/01/28 01:13:14 sjg Exp $ -# Copyright (c) 2010-2021, Simon J. Gerraty +# Copyright (c) 2010-2022, Simon J. Gerraty # Copyright (c) 2010-2018, Juniper Networks, Inc. # All rights reserved. # @@ -134,6 +134,16 @@ # A list of MACHINEs the current directory should not be # built for. # +# DIRDEPS_EXPORT_VARS (DEP_EXPORT_VARS) +# It is discouraged, but sometimes necessary for a +# Makefile.depend file to influence the environment. +# Doing this is correctly (especially if using DIRDEPS_CACHE) is +# tricky so a Makefile.depend file can set DIRDEPS_EXPORT_VARS +# and dirdeps.mk will do the deed: +# +# MK_UEFI = yes +# DIRDEPS_EXPORT_VARS = MK_UEFI +# # _build_xtra_dirs # local.dirdeps.mk can add targets to this variable. # They will be hooked into the build, but independent of @@ -161,6 +171,8 @@ _DIRDEP_USE_LEVEL?= 0 .if ${.MAKE.LEVEL} == ${_DIRDEP_USE_LEVEL} # only the first instance is interested in all this +# the first time we are included the _DIRDEP_USE target will not be defined +# we can use this as a clue to do initialization and other one time things. .if !target(_DIRDEP_USE) # do some setup we only need once @@ -184,6 +196,8 @@ TARGET_MACHINE := ${MACHINE} .endif # disable DIRDEPS_CACHE as it does not like this trick MK_DIRDEPS_CACHE = no +# incase anyone needs to know +_dirdeps_cmdline: .endif # make sure we get the behavior we expect @@ -257,12 +271,36 @@ _machine_dependfiles := ${.MAKE.DEPENDFILE_PREFERENCE:T:M*${MACHINE}*} .endif .endif - # this is how we identify non-machine specific dependfiles N_notmachine := ${.MAKE.DEPENDFILE_PREFERENCE:E:N*${MACHINE}*:${M_ListToSkip}} +# this gets reset for each dirdep we check +DEP_RELDIR ?= ${RELDIR} + +# remember the initial value of DEP_RELDIR - we test for it below. +_DEP_RELDIR := ${DEP_RELDIR} + +# this can cause lots of output! +# set to a set of glob expressions that might match RELDIR +DEBUG_DIRDEPS ?= no + +# make sure this target exists +dirdeps: beforedirdeps .WAIT +beforedirdeps: + .endif # !target(_DIRDEP_USE) +.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != "" +_debug_reldir = 1 +.else +_debug_reldir = 0 +.endif +.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend depend:L:M$x}@} != "" +_debug_search = 1 +.else +_debug_search = 0 +.endif + # First off, we want to know what ${MACHINE} to build for. # This can be complicated if we are using a mixture of ${MACHINE} specific # and non-specific Makefile.depend* @@ -297,26 +335,6 @@ DEP_MACHINE := ${_DEP_TARGET_SPEC} _build_all_dirs = _build_xtra_dirs = -# the first time we are included the _DIRDEP_USE target will not be defined -# we can use this as a clue to do initialization and other one time things. -.if !target(_DIRDEP_USE) -# make sure this target exists -dirdeps: beforedirdeps .WAIT -beforedirdeps: - -# We normally expect to be included by Makefile.depend.* -# which sets the DEP_* macros below. -DEP_RELDIR ?= ${RELDIR} - -# this can cause lots of output! -# set to a set of glob expressions that might match RELDIR -DEBUG_DIRDEPS ?= no - -# remember the initial value of DEP_RELDIR - we test for it below. -_DEP_RELDIR := ${DEP_RELDIR} - -.endif - # DIRDEPS_CACHE can be very handy for debugging. # Also if repeatedly building the same target, # we can avoid the overhead of re-computing the tree dependencies. @@ -329,16 +347,6 @@ BUILD_DIRDEPS ?= yes DIRDEPS_CACHE ?= ${_OBJDIR:tA}/dirdeps.cache${_TARGETS:U${.TARGETS}:Nall:O:u:ts-:S,/,_,g:S,^,.,:N.} .endif -.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != "" -_debug_reldir = 1 -.else -_debug_reldir = 0 -.endif -.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend:L:M$x}@} != "" -_debug_search = 1 -.else -_debug_search = 0 -.endif # pickup customizations # as below you can use !target(_DIRDEP_USE) to protect things @@ -532,7 +540,10 @@ BUILD_DIRDEPS = no dirdeps: dirdeps-cached dirdeps-cached: ${DIRDEPS_CACHE} .MAKE @echo "${TRACER}Using ${DIRDEPS_CACHE}" - @MAKELEVEL=${.MAKE.LEVEL} ${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \ + @MAKELEVEL=${.MAKE.LEVEL} \ + TARGET_SPEC=${TARGET_SPEC} \ + ${TARGET_SPEC_VARS:@v@$v=${$v}@} \ + ${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \ dirdeps MK_DIRDEPS_CACHE=no BUILD_DIRDEPS=no # leaf makefiles rarely work for building DIRDEPS_CACHE @@ -701,15 +712,18 @@ _build_all_dirs := ${_build_all_dirs:O:u} .if ${.MAKEFLAGS:M-V${_V_READ_DIRDEPS:U}} == "" .if !empty(_build_all_dirs) .if ${BUILD_DIRDEPS_CACHE} == "yes" -x!= echo; { echo; echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}'; } >&3 +# we use _cache_script to minimize the number of times we fork the shell +_cache_script = echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}'; # guard against _new_dirdeps being too big for a single command line _new_dirdeps := ${_build_all_dirs:@x@${target($x):?:$x}@:S,^${SRCTOP}/,,} _cache_xtra_deps := ${_build_xtra_dirs:S,^${SRCTOP}/,,} .export _cache_xtra_deps _new_dirdeps -.if !empty(DEP_EXPORT_VARS) +.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS) # Discouraged, but there are always exceptions. # Handle it here rather than explain how. -x!= echo; { echo; ${DEP_EXPORT_VARS:@v@echo '$v=${$v}';@} echo '.export ${DEP_EXPORT_VARS}'; echo; } >&3 +DIRDEPS_EXPORT_VARS ?= ${DEP_EXPORT_VARS} +_cache_xvars := echo; ${DIRDEPS_EXPORT_VARS:@v@echo '$v = ${$v}';@} echo '.export ${DIRDEPS_EXPORT_VARS}'; echo; +_cache_script += ${_cache_xvars} .endif .else # this makes it all happen @@ -721,16 +735,17 @@ ${_build_all_dirs}: _DIRDEP_USE .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs:S,^${SRCTOP}/,,} .endif -.if !empty(DEP_EXPORT_VARS) -.export ${DEP_EXPORT_VARS} -DEP_EXPORT_VARS= +.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS) +.export ${DIRDEPS_EXPORT_VARS} ${DEP_EXPORT_VARS} +DIRDEPS_EXPORT_VARS = +DEP_EXPORT_VARS = .endif # this builds the dependency graph .for m in ${_machines} .if ${BUILD_DIRDEPS_CACHE} == "yes" && !empty(_build_dirs) _cache_deps = -x!= echo; { echo; echo 'DIRDEPS.${_this_dir}.$m = \'; } >&3 +_cache_script += echo; echo 'DIRDEPS.${_this_dir}.$m = \'; .endif # it would be nice to do :N${.TARGET} .if !empty(__qual_depdirs) @@ -753,10 +768,10 @@ ${_this_dir}.$m: ${_build_dirs:M*.$q} _cache_deps += ${_build_dirs:M*.$m:N${_this_dir}.$m:S,^${SRCTOP}/,,} .if !empty(_cache_deps) .export _cache_deps -x!= echo; for x in $$_cache_deps; do echo " _{SRCTOP}/$$x \\"; done >&3 +_cache_script += for x in $$_cache_deps; do echo " _{SRCTOP}/$$x \\"; done; .endif # anything in _{build,env}_xtra_dirs is hooked to dirdeps: only -x!= echo; { echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \ +x!= echo; { echo; ${_cache_script} echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \ echo; echo 'dirdeps: ${_this_dir}.$m \'; \ for x in $$_cache_xtra_deps; do echo " _{SRCTOP}/$$x \\"; done; \ echo; for x in $$_new_dirdeps; do echo "_{SRCTOP}/$$x: _DIRDEP_USE"; done; } >&3 diff --git a/mk/host-target.mk b/mk/host-target.mk index 3e6094f16730..8d906e431779 100644 --- a/mk/host-target.mk +++ b/mk/host-target.mk @@ -1,9 +1,10 @@ # RCSid: -# $Id: host-target.mk,v 1.13 2020/08/05 23:32:08 sjg Exp $ +# $Id: host-target.mk,v 1.14 2022/02/04 18:05:22 sjg Exp $ # Host platform information; may be overridden .if !defined(_HOST_OSNAME) -_HOST_OSNAME != uname -s +# use .MAKE.OS if available +_HOST_OSNAME := ${.MAKE.OS:U${uname -s:L:sh}} .export _HOST_OSNAME .endif .if !defined(_HOST_OSREL) @@ -15,7 +16,7 @@ _HOST_MACHINE != uname -m .export _HOST_MACHINE .endif .if !defined(_HOST_ARCH) -# for NetBSD prefer $MACHINE (amd64 rather than x86_64) +# for Darwin and NetBSD prefer $MACHINE (amd64 rather than x86_64) .if ${_HOST_OSNAME:NDarwin:NNetBSD} == "" _HOST_ARCH := ${_HOST_MACHINE} .else diff --git a/mk/init.mk b/mk/init.mk index 356c8501367e..b740378cd69a 100644 --- a/mk/init.mk +++ b/mk/init.mk @@ -1,4 +1,4 @@ -# $Id: init.mk,v 1.26 2021/12/08 05:56:50 sjg Exp $ +# $Id: init.mk,v 1.27 2022/01/01 17:32:18 sjg Exp $ # # @(#) Copyright (c) 2002, Simon J. Gerraty # @@ -66,13 +66,17 @@ CXX_PIC?= ${CC_PIC} PROFFLAGS?= -DGPROF -DPROF .if ${.MAKE.LEVEL:U1} == 0 && ${MK_DIRDEPS_BUILD:Uno} == "yes" -# targets that are ok at level 0 +.if ${RELDIR} == "." +# top-level targets that are ok at level 0 DIRDEPS_BUILD_LEVEL0_TARGETS += clean* destroy* M_ListToSkip?= O:u:S,^,N,:ts: .if ${.TARGETS:Uall:${DIRDEPS_BUILD_LEVEL0_TARGETS:${M_ListToSkip}}} != "" # this tells lib.mk and prog.mk to not actually build anything _SKIP_BUILD = not building at level 0 .endif +.elif ${.TARGETS:U:Nall} == "" +_SKIP_BUILD = not building at level 0 +.endif .endif .if !defined(.PARSEDIR) diff --git a/mk/install-mk b/mk/install-mk index 21d00ae919a5..d309f116f4ff 100755 --- a/mk/install-mk +++ b/mk/install-mk @@ -55,7 +55,7 @@ # Simon J. Gerraty # RCSid: -# $Id: install-mk,v 1.206 2021/12/11 18:57:41 sjg Exp $ +# $Id: install-mk,v 1.213 2022/02/05 01:39:12 sjg Exp $ # # @(#) Copyright (c) 1994 Simon J. Gerraty # @@ -70,7 +70,7 @@ # sjg@crufty.net # -MK_VERSION=20211212 +MK_VERSION=20220204 OWNER= GROUP= MODE=444 diff --git a/mk/meta2deps.py b/mk/meta2deps.py index 76ac59465b9b..bc6975182429 100755 --- a/mk/meta2deps.py +++ b/mk/meta2deps.py @@ -37,7 +37,7 @@ """ RCSid: - $Id: meta2deps.py,v 1.40 2021/12/13 19:32:46 sjg Exp $ + $Id: meta2deps.py,v 1.44 2022/01/29 02:42:01 sjg Exp $ Copyright (c) 2011-2020, Simon J. Gerraty Copyright (c) 2011-2017, Juniper Networks, Inc. @@ -66,7 +66,10 @@ """ -import os, re, sys +import os +import re +import sys +import stat def resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): """ @@ -244,6 +247,7 @@ def __init__(self, name, conf={}): self.curdir = conf.get('CURDIR') self.reldir = conf.get('RELDIR') self.dpdeps = conf.get('DPDEPS') + self.pids = {} self.line = 0 if not self.conf: @@ -449,7 +453,7 @@ def parse(self, name=None, file=None): if self.curdir: self.seenit(self.curdir) # we ignore this - interesting = 'CEFLRV' + interesting = 'CEFLRVX' for line in f: self.line += 1 # ignore anything we don't care about @@ -505,6 +509,13 @@ def parse(self, name=None, file=None): print("cwd=", cwd, file=self.debug_out) continue + if w[0] == 'X': + try: + del self.pids[pid] + except KeyError: + pass + continue + if w[2] in self.seen: if self.debug > 2: print("seen:", w[2], file=self.debug_out) @@ -518,11 +529,30 @@ def parse(self, name=None, file=None): continue elif w[0] in 'ERWS': path = w[2] - if path == '.': + if w[0] == 'E': + self.pids[pid] = path + elif path == '.': continue self.parse_path(path, cwd, w[0], w) assert(version > 0) + setid_pids = [] + # self.pids should be empty! + for pid,path in self.pids.items(): + try: + # no guarantee that path is still valid + if os.stat(path).st_mode & (stat.S_ISUID|stat.S_ISGID): + # we do not expect anything after Exec + setid_pids.append(pid) + continue + except: + # we do not care why the above fails, + # we do not want to miss the ERROR below. + pass + print("ERROR: missing eXit for {} pid {}".format(path, pid)) + for pid in setid_pids: + del self.pids[pid] + assert(len(self.pids) == 0) if not file: f.close() diff --git a/mk/meta2deps.sh b/mk/meta2deps.sh index 64add8542571..d6a74e71e044 100755 --- a/mk/meta2deps.sh +++ b/mk/meta2deps.sh @@ -77,7 +77,7 @@ # RCSid: -# $Id: meta2deps.sh,v 1.15 2020/11/08 06:31:08 sjg Exp $ +# $Id: meta2deps.sh,v 1.18 2022/01/28 21:17:43 sjg Exp $ # Copyright (c) 2010-2013, Juniper Networks, Inc. # All rights reserved. @@ -241,8 +241,8 @@ meta2deps() { ;; *) cat /dev/null "$@";; esac 2> /dev/null | - sed -e 's,^CWD,C C,;/^[CREFLMV] /!d' -e "s,',,g" | - $_excludes | ( version=no + sed -e 's,^CWD,C C,;/^[CREFLMVX] /!d' -e "s,',,g" | + $_excludes | ( version=no epids= xpids= while read op pid path junk do : op=$op pid=$pid path=$path @@ -273,8 +273,9 @@ meta2deps() { ;; esac + : op=$op path=$path case "$op,$path" in - V,*) version=$path; continue;; + V,*) version=$pid; continue;; W,*srcrel|*.dirdep) continue;; C,*) case "$path" in @@ -291,6 +292,15 @@ meta2deps() { continue ;; *) dir=${path%/*} + case "$op" in + E) # setid apps get no tracing so we won't see eXit + case `'ls' -l $path 2> /dev/null | sed 's, .*,,'` in + *s*) ;; + *) epids="$epids $pid";; + esac + ;; + X) xpids="$xpids $pid"; continue;; + esac case "$path" in $src_re|$obj_re) ;; /*/stage/*) ;; @@ -380,9 +390,18 @@ meta2deps() { echo $dir;; esac done > $tf.dirdep + : version=$version case "$version" in 0) error "no filemon data";; - esac ) || exit 1 + esac + for p in $epids + do + : p=$p + case " $xpids " in + *" $p "*) ;; + *) error "missing eXit for pid $p";; + esac + done ) || exit 1 _nl=echo for f in $tf.dirdep $tf.qual $tf.srcdep do diff --git a/mk/mk-files.txt b/mk/mk-files.txt index 282f9b87a63c..ca0a2868fd8f 100644 --- a/mk/mk-files.txt +++ b/mk/mk-files.txt @@ -143,6 +143,8 @@ examples/sys.clean-env.mk host-target.mk Is used to set macros like ``HOST_TARGET``, ``HOST_OS`` and ``host_os`` which are used to find the next step. + Note: since 20130303 bmake provides ``.MAKE.OS`` set to + the equivalent of ``HOST_OS``. sys/\*.mk Platform specific additions, such as ``Darwin.mk`` or ``SunOS.mk`` @@ -159,27 +161,40 @@ local.sys.mk The above arrangement makes it easy for the mk files to be part of a src tree on an NFS volume and to allow building on multiple platforms. +options.mk +---------- + +Inspired by FreeBSD's ``bsd.own.mk`` but more flexible. +FreeBSD now have similar functionality in ``bsd.mkopt.mk``. + +It allows users to express their intent with respect to options +``MK_*`` by setting ``WITH_*`` or ``WITHOUT_*``. + +Note: ``WITHOUT_*`` wins if both are set, and makefiles can set +``NO_*`` to say they cannot handle that option, or even ``MK_*`` if +they really need to. + lib.mk ------ This file is used to build a number of different libraries from the same SRCS. -lib${LIB}.a +``lib${LIB}.a`` An archive lib of ``.o`` files, this is the default -lib${LIB}_p.a +``lib${LIB}_p.a`` A profiled lib of ``.po`` files. Still an archive lib, but all the objects are built with profiling in mind - hence the different extension. - It is skipped if ``MKPROFILE`` is "no". + It is skipped if ``MK_PROFILE`` is "no". -lib${LIB}_pic.a +``lib${LIB}_pic.a`` An archive of ``.so`` objects compiled for relocation. On NetBSD this is the input to ``lib${LIB}.${LD_so}``, it is - skipped if ``MKPICLIB`` is "no". + skipped if ``MK_PIC`` or ``MK_PICLIB`` are "no". -lib${LIB}.${LD_so} +``lib${LIB}.${LD_so}`` A shared library. The value of ``LD_so`` is very platform specific. For example:: @@ -190,7 +205,7 @@ lib${LIB}.${LD_so} libsslfd.1.dylib This library will only be built if ``SHLIB_MAJOR`` has - a value, and ``MKPIC`` is not set to "no". + a value, and ``MK_PIC`` is not set to "no". There is a lot of platform specific tweaking in ``lib.mk``, largely the result of the original distributions trying to avoid interfering with @@ -202,7 +217,7 @@ libnames.mk This is included by both ``prog.mk`` and ``lib.mk`` and tries to include ``*.libnames.mk`` of which: -local.libnames.mk +``local.libnames.mk`` does not exist unless you create it. It is a handy way for you to customize without touching the distributed files. For example, on a test machine I needed to build openssl but @@ -217,7 +232,7 @@ local.libnames.mk The makefile created an openssl dir in ``${OBJ_libcrypto}`` to gather all the headers. dpadd.mk_ did the rest. -host.libnames.mk +``host.libnames.mk`` contains logic to find any libs named in ``HOST_LIBS`` in ``HOST_LIBDIRS``. @@ -256,15 +271,15 @@ else in various ways:: Any library (referenced by its full path) in any of the above, is added to ``DPMAGIC_LIBS`` with the following results, for each lib *foo*. -SRC_libfoo +``SRC_libfoo`` Is set to indicate where the src for libfoo is. By default it is derived from ``LIBFOO`` by replacing ``${OBJTOP}`` with ``${SRCTOP}``. -OBJ_libfoo +``OBJ_libfoo`` Not very exciting, is just the dir where libfoo lives. -INCLUDES_libfoo +``INCLUDES_libfoo`` What to add to ``CFLAGS`` to find the public headers. The default varies. If ``${SRC_libfoo}/h`` exists, it is assumed to be the home of all public headers and thus the default is @@ -273,7 +288,7 @@ INCLUDES_libfoo Otherwise we make no assumptions and the default is ``-I${SRC_libfoo} -I${OBJ_libfoo}`` -LDADD_libfoo +``LDADD_libfoo`` This only applies to libs reference via ``DPLIBS``. The default is ``-lfoo``, ``LDADD_*`` provides a hook to instantiate other linker flags at the appropriate point @@ -303,13 +318,16 @@ object files from the src tree. This is also the source of much confusion to some. Traditionally one had to do a separate ``make obj`` pass through the -tree. If ``MKOBJDIRS`` is "auto", we include auto.obj.mk_. +tree. If ``MK_AUTO_OBJ`` is set we include auto.obj.mk_. + +In fact if ``MKOBJDIRS`` is set to "auto", `sys.mk`_ will set +``MK_AUTO_OBJ=yes`` and include auto.obj.mk_ since it is best done early. auto.obj.mk ----------- This leverages the ``.OBJDIR`` target introduced some years ago to -NetBSD make, to automatically create the desired object dir. +NetBSD make, to automatically create and use the desired object dir. subdir.mk --------- @@ -334,6 +352,8 @@ you can suppress that - or enhance it by setting ``ECHO_DIR``:: # print time stamps ECHO_DIR=echo @ `date "+%s [%Y-%m-%d %T] "` +I prefer to use `dirdeps.mk`_ which makes ``subdir.mk`` irrelevant. + links.mk -------- @@ -367,9 +387,12 @@ dep.mk Deals with collecting dependencies. Another useful feature of BSD make is the separation of this sort of information into a ``.depend`` -file. ``MKDEP`` needs to point to a suitable tool (like mkdeps.sh_) +file. ``MKDEP_CMD`` needs to point to a suitable tool (like mkdeps.sh_) -If ``USE_AUTODEP_MK`` is "yes" includes autodep.mk_ +If ``MK_AUTODEP`` is "yes" it sets ``MKDEP_MK`` to autodep.mk_ by default. + +``MKDEP_MK`` can also be set to `auto.dep.mk`_ which is more efficient +but does not support an explicit ``depend`` target. autodep.mk ---------- @@ -397,19 +420,9 @@ to avoid possible conflicts during parallel builds. This precludes the use of suffix rules to drive ``make depend``, so dep.mk_ handles that if specifically requested. -options.mk ----------- - -Inspired by FreeBSD's ``bsd.own.mk`` more flexible. -FreeBSD now have similar functionality in ``bsd.mkopt.mk``. - -It allows users to express their intent with respect to options -``MK_*`` by setting ``WITH_*`` or ``WITHOUT_*``. - -Note: ``WITHOUT_*`` wins if both are set, and makefiles can set -``NO_*`` to say they cannot handle that option, or even ``MK_*`` if -they really need to. - +If ``bmake`` is 20160218 or newer, ``auto.dep.mk`` uses ``.dinclude`` +to includes the ``*.d`` files directly thus avoiding the need to +create a ``.depend`` file from them. own.mk ------ @@ -424,7 +437,7 @@ ldorder.mk Leverages ``bmake`` to compute optimal link order for libraries. This works nicely and makes refactoring a breeze - so long as you -have not (or few) cicular dependencies between libraries. +have no (or few) cicular dependencies between libraries. man.mk ------ @@ -463,6 +476,22 @@ Logic to build Python C interface modules using Cython_ .. _Cython: http://www.cython.org/ +cc-wrap.mk +---------- + +This makefile leverages two new features in bmake 20220126 and later. + +First is the ablity to set target local variables (GNU make has done +this for ages). + +The second (only intersting if using `meta mode`_) +allows filtering commands before comparison with previous run to +decide if a target is out-of-date. + +In the past, making use of compiler wrappers like ``ccache``, +``distcc`` or the newer ``icecc`` could get quite ugly. +Using ``cc-wrap.mk`` it could not be simpler. + Meta mode ========= @@ -496,5 +525,5 @@ where you unpacked the tar file, you can:: .. _mk.tar.gz: http://www.crufty.net/ftp/pub/sjg/mk.tar.gz :Author: sjg@crufty.net -:Revision: $Id: mk-files.txt,v 1.20 2020/08/19 17:51:53 sjg Exp $ +:Revision: $Id: mk-files.txt,v 1.21 2022/02/04 19:01:05 sjg Exp $ :Copyright: Crufty.NET diff --git a/mk/sys.clean-env.mk b/mk/sys.clean-env.mk index 88d32cb3c6e9..85e829c0a8f2 100644 --- a/mk/sys.clean-env.mk +++ b/mk/sys.clean-env.mk @@ -1,4 +1,4 @@ -# $Id: sys.clean-env.mk,v 1.23 2020/08/19 17:51:53 sjg Exp $ +# $Id: sys.clean-env.mk,v 1.24 2022/01/15 17:34:42 sjg Exp $ # # @(#) Copyright (c) 2009, Simon J. Gerraty # @@ -52,7 +52,7 @@ MAKE_ENV_SAVE_PREFIX_LIST += \ # This could be a list of vars or patterns to explicitly exclude. -MAKE_ENV_SAVE_EXCLUDE_LIST ?= _ +MAKE_ENV_SAVE_EXCLUDE_LIST += _ # This is the actual list that we will save # HOME is probably something worth clobbering eg. @@ -115,7 +115,7 @@ MAKEOBJDIR = $${.CURDIR:S,${_srctop},$${OBJTOP},} $v := ${$v} .endfor .else -# we cannot use the '$$' trick, anymore +# we cannot rely on the '$$' trick (depending on .MAKE.SAVE_DOLLARS) # but we can export a literal (unexpanded) value SRCTOP := ${_srctop} OBJROOT := ${_objroot} diff --git a/nonints.h b/nonints.h deleted file mode 100644 index 8583e50270fd..000000000000 --- a/nonints.h +++ /dev/null @@ -1,348 +0,0 @@ -/* $NetBSD: nonints.h,v 1.217 2021/12/12 20:45:48 sjg Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94 - */ - -/* - * Copyright (c) 1989 by Berkeley Softworks - * All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94 - */ - -/* arch.c */ -void Arch_Init(void); -void Arch_End(void); - -bool Arch_ParseArchive(char **, GNodeList *, GNode *); -void Arch_Touch(GNode *); -void Arch_TouchLib(GNode *); -void Arch_UpdateMTime(GNode *gn); -void Arch_UpdateMemberMTime(GNode *gn); -void Arch_FindLib(GNode *, SearchPath *); -bool Arch_LibOODate(GNode *); -bool Arch_IsLib(GNode *); - -/* compat.c */ -int Compat_RunCommand(const char *, GNode *, StringListNode *); -void Compat_Run(GNodeList *); -void Compat_Make(GNode *, GNode *); - -/* cond.c */ -CondEvalResult Cond_EvalCondition(const char *, bool *); -CondEvalResult Cond_EvalLine(const char *); -void Cond_restore_depth(unsigned int); -unsigned int Cond_save_depth(void); - -/* dir.c; see also dir.h */ - -MAKE_INLINE const char * -str_basename(const char *pathname) -{ - const char *lastSlash = strrchr(pathname, '/'); - return lastSlash != NULL ? lastSlash + 1 : pathname; -} - -MAKE_INLINE SearchPath * -SearchPath_New(void) -{ - SearchPath *path = bmake_malloc(sizeof *path); - Lst_Init(&path->dirs); - return path; -} - -void SearchPath_Free(SearchPath *); - -/* for.c */ -int For_Eval(const char *); -bool For_Accum(const char *); -void For_Run(int); - -/* job.c */ -#ifdef WAIT_T -void JobReapChild(pid_t, WAIT_T, bool); -#endif - -/* main.c */ -bool GetBooleanExpr(const char *, bool); -void Main_ParseArgLine(const char *); -char *Cmd_Exec(const char *, const char **); -void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); -void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; -void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; -void DieHorribly(void) MAKE_ATTR_DEAD; -void Finish(int) MAKE_ATTR_DEAD; -int eunlink(const char *); -void execDie(const char *, const char *); -char *getTmpdir(void); -bool ParseBoolean(const char *, bool); -char *cached_realpath(const char *, char *); - -/* parse.c */ -void Parse_Init(void); -void Parse_End(void); - -typedef enum VarAssignOp { - VAR_NORMAL, /* = */ - VAR_SUBST, /* := */ - VAR_SHELL, /* != or :sh= */ - VAR_APPEND, /* += */ - VAR_DEFAULT /* ?= */ -} VarAssignOp; - -typedef struct VarAssign { - char *varname; /* unexpanded */ - VarAssignOp op; - const char *value; /* unexpanded */ -} VarAssign; - -typedef char *(*ReadMoreProc)(void *, size_t *); - -void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); -bool Parse_IsVar(const char *, VarAssign *out_var); -void Parse_Var(VarAssign *, GNode *); -void Parse_AddIncludeDir(const char *); -void Parse_File(const char *, int); -void Parse_PushInput(const char *, int, int, ReadMoreProc, void *); -void Parse_MainName(GNodeList *); -int Parse_NumErrors(void); - - -#ifndef HAVE_STRLCPY -/* strlcpy.c */ -size_t strlcpy(char *, const char *, size_t); -#endif - -/* suff.c */ -void Suff_Init(void); -void Suff_End(void); - -void Suff_ClearSuffixes(void); -bool Suff_IsTransform(const char *); -GNode *Suff_AddTransform(const char *); -void Suff_EndTransform(GNode *); -void Suff_AddSuffix(const char *, GNode **); -SearchPath *Suff_GetPath(const char *); -void Suff_ExtendPaths(void); -void Suff_AddInclude(const char *); -void Suff_AddLib(const char *); -void Suff_FindDeps(GNode *); -SearchPath *Suff_FindPath(GNode *); -void Suff_SetNull(const char *); -void Suff_PrintAll(void); -const char *Suff_NamesStr(void); - -/* targ.c */ -void Targ_Init(void); -void Targ_End(void); - -void Targ_Stats(void); -GNodeList *Targ_List(void); -GNode *GNode_New(const char *); -GNode *Targ_FindNode(const char *); -GNode *Targ_GetNode(const char *); -GNode *Targ_NewInternalNode(const char *); -GNode *Targ_GetEndNode(void); -void Targ_FindList(GNodeList *, StringList *); -bool Targ_Precious(const GNode *); -void Targ_SetMain(GNode *); -void Targ_PrintCmds(GNode *); -void Targ_PrintNode(GNode *, int); -void Targ_PrintNodes(GNodeList *, int); -const char *Targ_FmtTime(time_t); -void Targ_PrintType(GNodeType); -void Targ_PrintGraph(int); -void Targ_Propagate(void); -const char *GNodeMade_Name(GNodeMade); - -/* var.c */ -void Var_Init(void); -void Var_End(void); - -typedef enum VarEvalMode { - - /* - * Only parse the expression but don't evaluate any part of it. - * - * TODO: Document what Var_Parse and Var_Subst return in this mode. - * As of 2021-03-15, they return unspecified, inconsistent results. - */ - VARE_PARSE_ONLY, - - /* Parse and evaluate the expression. */ - VARE_WANTRES, - - /* - * Parse and evaluate the expression. It is an error if a - * subexpression evaluates to undefined. - */ - VARE_UNDEFERR, - - /* - * Parse and evaluate the expression. Keep '$$' as '$$' instead of - * reducing it to a single '$'. Subexpressions that evaluate to - * undefined expand to an empty string. - * - * Used in variable assignments using the ':=' operator. It allows - * multiple such assignments to be chained without accidentally - * expanding '$$file' to '$file' in the first assignment and - * interpreting it as '${f}' followed by 'ile' in the next assignment. - */ - VARE_EVAL_KEEP_DOLLAR, - - /* - * Parse and evaluate the expression. Keep undefined variables as-is - * instead of expanding them to an empty string. - * - * Example for a ':=' assignment: - * CFLAGS = $(.INCLUDES) - * CFLAGS := -I.. $(CFLAGS) - * # If .INCLUDES (an undocumented special variable, by the - * # way) is still undefined, the updated CFLAGS becomes - * # "-I.. $(.INCLUDES)". - */ - VARE_EVAL_KEEP_UNDEF, - - /* - * Parse and evaluate the expression. Keep '$$' as '$$' and preserve - * undefined subexpressions. - */ - VARE_KEEP_DOLLAR_UNDEF -} VarEvalMode; - -typedef enum VarSetFlags { - VAR_SET_NONE = 0, - - /* do not export */ - VAR_SET_NO_EXPORT = 1 << 0, - - /* Make the variable read-only. No further modification is possible, - * except for another call to Var_Set with the same flag. */ - VAR_SET_READONLY = 1 << 1 -} VarSetFlags; - -/* The state of error handling returned by Var_Parse. */ -typedef enum VarParseResult { - - /* Both parsing and evaluation succeeded. */ - VPR_OK, - - /* Parsing or evaluating failed, with an error message. */ - VPR_ERR, - - /* - * Parsing succeeded, undefined expressions are allowed and the - * expression was still undefined after applying all modifiers. - * No error message is printed in this case. - * - * Some callers handle this case differently, so return this - * information to them, for now. - * - * TODO: Instead of having this special return value, rather ensure - * that VARE_EVAL_KEEP_UNDEF is processed properly. - */ - VPR_UNDEF - -} VarParseResult; - -typedef enum VarExportMode { - /* .export-env */ - VEM_ENV, - /* .export: Initial export or update an already exported variable. */ - VEM_PLAIN, - /* .export-literal: Do not expand the variable value. */ - VEM_LITERAL -} VarExportMode; - -void Var_Delete(GNode *, const char *); -void Var_DeleteExpand(GNode *, const char *); -void Var_Undef(const char *); -void Var_Set(GNode *, const char *, const char *); -void Var_SetExpand(GNode *, const char *, const char *); -void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags); -void Var_SetExpandWithFlags(GNode *, const char *, const char *, VarSetFlags); -void Var_Append(GNode *, const char *, const char *); -void Var_AppendExpand(GNode *, const char *, const char *); -bool Var_Exists(GNode *, const char *); -bool Var_ExistsExpand(GNode *, const char *); -FStr Var_Value(GNode *, const char *); -const char *GNode_ValueDirect(GNode *, const char *); -VarParseResult Var_Parse(const char **, GNode *, VarEvalMode, FStr *); -VarParseResult Var_Subst(const char *, GNode *, VarEvalMode, char **); -void Var_Stats(void); -void Var_Dump(GNode *); -void Var_ReexportVars(void); -void Var_Export(VarExportMode, const char *); -void Var_ExportVars(const char *); -void Var_UnExport(bool, const char *); - -void Global_Set(const char *, const char *); -void Global_SetExpand(const char *, const char *); -void Global_Append(const char *, const char *); -void Global_Delete(const char *); - -/* util.c */ -typedef void (*SignalProc)(int); -SignalProc bmake_signal(int, SignalProc); diff --git a/parse.c b/parse.c index 6a310c74ba5c..3faeafe1c739 100644 --- a/parse.c +++ b/parse.c @@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.574 2021/12/12 15:44:41 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.662 2022/02/05 00:37:19 sjg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -74,10 +74,6 @@ * Parse_File is the main entry point and controls most of the other * functions in this module. * - * The directories for the .include "..." directive are kept in - * 'parseIncPath', while those for .include <...> are kept in 'sysIncPath'. - * The targets currently being defined are kept in 'targets'. - * * Interface: * Parse_Init Initialize the module * @@ -86,15 +82,16 @@ * Parse_File Parse a top-level makefile. Included files are * handled by IncludeFile instead. * - * Parse_IsVar Return true if the given line is a variable - * 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_VarAssign + * Try to parse the given line as a variable assignment. + * Used by MainParseArgs to determine if an argument is + * a target or a variable assignment. Used internally + * for pretty much the same thing. * * Parse_Error Report a parse error, a warning or an informational * message. * - * Parse_MainName Returns a list of the main target to create. + * Parse_MainName Returns a list of the single main target to create. */ #include @@ -124,40 +121,32 @@ #include "pathnames.h" /* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: parse.c,v 1.574 2021/12/12 15:44:41 rillig Exp $"); - -/* types and constants */ +MAKE_RCSID("$NetBSD: parse.c,v 1.662 2022/02/05 00:37:19 sjg Exp $"); /* - * Structure for a file being read ("included file") + * A file being read. */ -typedef struct IFile { - char *fname; /* name of file (relative? absolute?) */ - bool fromForLoop; /* simulated .include by the .for loop */ - int lineno; /* current line number in file */ - int first_lineno; /* line number of start of text */ +typedef struct IncludedFile { + FStr name; /* absolute or relative to the cwd */ + unsigned lineno; /* 1-based */ + unsigned readLines; /* the number of physical lines that have + * been read from the file */ + unsigned forHeadLineno; /* 1-based */ + unsigned forBodyReadLines; /* the number of physical lines that have + * been read from the file above the body of + * the .for loop */ unsigned int cond_depth; /* 'if' nesting when file opened */ - bool depending; /* state of doing_depend on EOF */ + bool depending; /* state of doing_depend on EOF */ - /* - * The buffer from which the file's content is read. The buffer - * always ends with '\n', the buffer is not null-terminated, that is, - * buf_end[0] is already out of bounds. - */ - char *buf_freeIt; + Buffer buf; /* the file's content or the body of the .for + * loop; always ends with '\n' */ char *buf_ptr; /* next char to be read */ char *buf_end; /* buf_end[-1] == '\n' */ - /* Function to read more data, with a single opaque argument. */ - ReadMoreProc readMore; - void *readMoreArg; + struct ForLoop *forLoop; +} IncludedFile; - struct loadedfile *lf; /* loadedfile object, if any */ -} IFile; - -/* - * Tokens for target attributes - */ +/* Special attributes for target nodes. */ typedef enum ParseSpecial { SP_ATTRIBUTE, /* Generic attribute */ SP_BEGIN, /* .BEGIN */ @@ -169,8 +158,7 @@ typedef enum ParseSpecial { SP_INCLUDES, /* .INCLUDES; not mentioned in the manual page */ SP_INTERRUPT, /* .INTERRUPT */ SP_LIBS, /* .LIBS; not mentioned in the manual page */ - /* .MAIN and we don't have anything user-specified to make */ - SP_MAIN, + SP_MAIN, /* .MAIN and no user-specified targets to make */ SP_META, /* .META */ SP_MFLAGS, /* .MFLAGS or .MAKEFLAGS */ SP_NOMETA, /* .NOMETA */ @@ -199,15 +187,29 @@ typedef enum ParseSpecial { typedef List SearchPathList; typedef ListNode SearchPathListNode; -/* result data */ + +typedef enum VarAssignOp { + VAR_NORMAL, /* = */ + VAR_APPEND, /* += */ + VAR_DEFAULT, /* ?= */ + VAR_SUBST, /* := */ + VAR_SHELL /* != or :sh= */ +} VarAssignOp; + +typedef struct VarAssign { + char *varname; /* unexpanded */ + VarAssignOp op; + const char *value; /* unexpanded */ +} VarAssign; + +static bool Parse_IsVar(const char *, VarAssign *); +static void Parse_Var(VarAssign *, GNode *); /* - * The main target to create. This is the first target on the first - * dependency line in the first makefile. + * The target to be made if no targets are specified in the command line. + * This is the first target defined in any of the makefiles. */ -static GNode *mainNode; - -/* eval state */ +GNode *mainNode; /* * During parsing, the targets from the left-hand side of the currently @@ -230,19 +232,12 @@ static StringList targCmds = LST_INIT; /* * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER - * seen, then set to each successive source on the line. + * is seen, then set to each successive source on the line. */ static GNode *order_pred; -/* parser state */ - -/* number of fatal errors */ static int parseErrors = 0; -/* - * Variables for doing includes - */ - /* * The include chain of makefiles. At index 0 is the top-level makefile from * the command line, followed by the included files or .for loops, up to and @@ -250,28 +245,12 @@ static int parseErrors = 0; * * See PrintStackTrace for how to interpret the data. */ -static Vector /* of IFile */ includes; +static Vector /* of IncludedFile */ includes; -static IFile * -GetInclude(size_t i) -{ - return Vector_Get(&includes, i); -} - -/* The file that is currently being read. */ -static IFile * -CurFile(void) -{ - return GetInclude(includes.len - 1); -} - -/* include paths */ SearchPath *parseIncPath; /* directories for "..." includes */ SearchPath *sysIncPath; /* directories for <...> includes */ SearchPath *defSysIncPath; /* default for sysIncPath */ -/* parser tables */ - /* * The parseKeywords table is searched using binary search when deciding * if a target or source is special. The 'spec' field is the ParseSpecial @@ -280,9 +259,9 @@ SearchPath *defSysIncPath; /* default for sysIncPath */ * keyword is used as a source ("0" if the keyword isn't special as a source) */ static const struct { - const char name[17]; /* Name of keyword */ - ParseSpecial spec; /* Type when used as a target */ - GNodeType op; /* Operator when used as a source */ + const char name[17]; + ParseSpecial special; /* when used as a target */ + GNodeType targetAttr; /* when used as a source */ } parseKeywords[] = { { ".BEGIN", SP_BEGIN, OP_NONE }, { ".DEFAULT", SP_DEFAULT, OP_NONE }, @@ -330,120 +309,36 @@ static const struct { { ".WAIT", SP_WAIT, OP_NONE }, }; -/* file loader */ -struct loadedfile { - char *buf; /* contents buffer */ - size_t len; /* length of contents */ - bool used; /* XXX: have we used the data yet */ -}; - -/* XXX: What is the lifetime of the path? Who manages the memory? */ -static struct loadedfile * -loadedfile_create(char *buf, size_t buflen) +static IncludedFile * +GetInclude(size_t i) { - struct loadedfile *lf; - - lf = bmake_malloc(sizeof *lf); - lf->buf = buf; - lf->len = buflen; - lf->used = false; - return lf; + return Vector_Get(&includes, i); } -static void -loadedfile_destroy(struct loadedfile *lf) +/* The file that is currently being read. */ +static IncludedFile * +CurFile(void) { - free(lf->buf); - free(lf); + return GetInclude(includes.len - 1); } -/* - * readMore() operation for loadedfile, as needed by the weird and twisted - * logic below. Once that's cleaned up, we can get rid of lf->used. - */ -static char * -loadedfile_readMore(void *x, size_t *len) -{ - struct loadedfile *lf = x; - - if (lf->used) - return NULL; - - lf->used = true; - *len = lf->len; - return lf->buf; -} - -/* - * Try to get the size of a file. - */ -static bool -load_getsize(int fd, size_t *ret) -{ - struct stat st; - - if (fstat(fd, &st) < 0) - return false; - - if (!S_ISREG(st.st_mode)) - return false; - - /* - * st_size is an off_t, which is 64 bits signed; *ret is - * size_t, which might be 32 bits unsigned or 64 bits - * unsigned. Rather than being elaborate, just punt on - * files that are more than 1 GiB. We should never - * see a makefile that size in practice. - * - * While we're at it reject negative sizes too, just in case. - */ - if (st.st_size < 0 || st.st_size > 0x3fffffff) - return false; - - *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. - */ -static struct loadedfile * +static Buffer loadfile(const char *path, int fd) { ssize_t n; Buffer buf; - size_t filesize; + size_t bufSize; + struct stat st; - - if (path == NULL) { - assert(fd == -1); - fd = STDIN_FILENO; - } - - if (load_getsize(fd, &filesize)) { - /* - * Avoid resizing the buffer later for no reason. - * - * At the same time leave space for adding a final '\n', - * just in case it is missing in the file. - */ - filesize++; - } else - filesize = 1024; - Buf_InitSize(&buf, filesize); + bufSize = fstat(fd, &st) == 0 && S_ISREG(st.st_mode) && + st.st_size > 0 && st.st_size < 1024 * 1024 * 1024 + ? (size_t)st.st_size : 1024; + Buf_InitSize(&buf, bufSize); for (;;) { - assert(buf.len <= buf.cap); if (buf.len == buf.cap) { - if (buf.cap > 0x1fffffff) { - errno = EFBIG; + if (buf.cap >= 512 * 1024 * 1024) { Error("%s: file too large", path); exit(2); /* Not 1 so -q can distinguish error */ } @@ -465,79 +360,57 @@ loadfile(const char *path, int fd) if (!Buf_EndsWith(&buf, '\n')) Buf_AddByte(&buf, '\n'); - if (path != NULL) - close(fd); - - { - struct loadedfile *lf = loadedfile_create(buf.data, buf.len); - Buf_DoneData(&buf); - return lf; - } + return buf; /* may not be null-terminated */ } -static void -PrintStackTrace(void) +/* + * Print the current chain of .include and .for directives. In Parse_Fatal + * or other functions that already print the location, includingInnermost + * would be redundant, but in other cases like Error or Fatal it needs to be + * included. + */ +void +PrintStackTrace(bool includingInnermost) { - const IFile *entries; + const IncludedFile *entries; size_t i, n; - if (!(DEBUG(PARSE))) - return; - entries = GetInclude(0); n = includes.len; if (n == 0) return; - n--; /* This entry is already in the diagnostic. */ - /* - * For the IFiles with fromForLoop, lineno seems to be sorted - * backwards. This is because lineno is the number of completely - * parsed lines, which for a .for loop is right after the - * corresponding .endfor. The intuitive line number comes from - * first_lineno instead, which points at the start of the .for loop. - * - * To make the stack trace intuitive, the entry below each chain of - * .for loop entries must be ignored completely since neither its - * lineno nor its first_lineno is useful. Instead, the topmost of - * each chain of .for loop entries needs to be printed twice, once - * with its first_lineno and once with its lineno. - */ + if (!includingInnermost && entries[n - 1].forLoop == NULL) + n--; /* already in the diagnostic */ for (i = n; i-- > 0;) { - const IFile *entry = entries + i; - const char *fname = entry->fname; - bool printLineno; + const IncludedFile *entry = entries + i; + const char *fname = entry->name.str; char dirbuf[MAXPATHLEN + 1]; if (fname[0] != '/' && strcmp(fname, "(stdin)") != 0) fname = realpath(fname, dirbuf); - printLineno = !entry->fromForLoop; - if (i + 1 < n && entries[i + 1].fromForLoop == printLineno) - printLineno = entry->fromForLoop; - - if (printLineno) - debug_printf("\tin .include from %s:%d\n", - fname, entry->lineno); - if (entry->fromForLoop) - debug_printf("\tin .for loop from %s:%d\n", - fname, entry->first_lineno); + if (entry->forLoop != NULL) { + char *details = ForLoop_Details(entry->forLoop); + debug_printf("\tin .for loop from %s:%u with %s\n", + fname, entry->forHeadLineno, details); + free(details); + } else if (i + 1 < n && entries[i + 1].forLoop != NULL) { + /* entry->lineno is not a useful line number */ + } else + debug_printf("\tin %s:%u\n", fname, entry->lineno); } } /* Check if the current character is escaped on the current line. */ static bool -ParseIsEscaped(const char *line, const char *c) +IsEscaped(const char *line, const char *p) { - bool active = false; - for (;;) { - if (line == c) - return active; - if (*--c != '\\') - return active; - active = !active; - } + bool escaped = false; + while (p > line && *--p == '\\') + escaped = !escaped; + return escaped; } /* @@ -547,8 +420,8 @@ ParseIsEscaped(const char *line, const char *c) static void RememberLocation(GNode *gn) { - IFile *curFile = CurFile(); - gn->fname = curFile->fname; + IncludedFile *curFile = CurFile(); + gn->fname = Str_Intern(curFile->name.str); gn->lineno = curFile->lineno; } @@ -557,12 +430,12 @@ RememberLocation(GNode *gn) * Return the index of the keyword, or -1 if it isn't there. */ static int -ParseFindKeyword(const char *str) +FindKeyword(const char *str) { int start = 0; int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; - do { + while (start <= end) { int curr = start + (end - start) / 2; int diff = strcmp(str, parseKeywords[curr].name); @@ -572,25 +445,22 @@ ParseFindKeyword(const char *str) end = curr - 1; else start = curr + 1; - } while (start <= end); + } return -1; } -static void -PrintLocation(FILE *f, const char *fname, size_t lineno) +void +PrintLocation(FILE *f, bool useVars, const char *fname, unsigned lineno) { char dirbuf[MAXPATHLEN + 1]; FStr dir, base; - if (*fname == '/' || strcmp(fname, "(stdin)") == 0) { - (void)fprintf(f, "\"%s\" line %u: ", fname, (unsigned)lineno); + if (!useVars || fname[0] == '/' || strcmp(fname, "(stdin)") == 0) { + (void)fprintf(f, "\"%s\" line %u: ", fname, lineno); return; } - /* Find out which makefile is the culprit. - * We try ${.PARSEDIR} and apply realpath(3) if not absolute. */ - dir = Var_Value(SCOPE_GLOBAL, ".PARSEDIR"); if (dir.str == NULL) dir.str = "."; @@ -601,15 +471,14 @@ PrintLocation(FILE *f, const char *fname, size_t lineno) if (base.str == NULL) base.str = str_basename(fname); - (void)fprintf(f, "\"%s/%s\" line %u: ", - dir.str, base.str, (unsigned)lineno); + (void)fprintf(f, "\"%s/%s\" line %u: ", dir.str, base.str, lineno); FStr_Done(&base); FStr_Done(&dir); } -static void -ParseVErrorInternal(FILE *f, const char *fname, size_t lineno, +static void MAKE_ATTR_PRINTFLIKE(6, 0) +ParseVErrorInternal(FILE *f, bool useVars, const char *fname, unsigned lineno, ParseErrorLevel type, const char *fmt, va_list ap) { static bool fatal_warning_error_printed = false; @@ -617,42 +486,42 @@ ParseVErrorInternal(FILE *f, const char *fname, size_t lineno, (void)fprintf(f, "%s: ", progname); if (fname != NULL) - PrintLocation(f, fname, lineno); + PrintLocation(f, useVars, fname, lineno); if (type == PARSE_WARNING) (void)fprintf(f, "warning: "); (void)vfprintf(f, fmt, ap); (void)fprintf(f, "\n"); (void)fflush(f); - if (type == PARSE_INFO) - goto print_stack_trace; - if (type == PARSE_WARNING && !opts.parseWarnFatal) - goto print_stack_trace; - parseErrors++; - if (type == PARSE_WARNING && !fatal_warning_error_printed) { - Error("parsing warnings being treated as errors"); - fatal_warning_error_printed = true; + if (type == PARSE_FATAL) + parseErrors++; + if (type == PARSE_WARNING && opts.parseWarnFatal) { + if (!fatal_warning_error_printed) { + Error("parsing warnings being treated as errors"); + fatal_warning_error_printed = true; + } + parseErrors++; } -print_stack_trace: - PrintStackTrace(); + if (DEBUG(PARSE)) + PrintStackTrace(false); } -static void -ParseErrorInternal(const char *fname, size_t lineno, +static void MAKE_ATTR_PRINTFLIKE(4, 5) +ParseErrorInternal(const char *fname, unsigned lineno, ParseErrorLevel type, const char *fmt, ...) { va_list ap; (void)fflush(stdout); va_start(ap, fmt); - ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); + ParseVErrorInternal(stderr, false, fname, lineno, type, fmt, ap); va_end(ap); - if (opts.debug_file != stderr && opts.debug_file != stdout) { + if (opts.debug_file != stdout && opts.debug_file != stderr) { va_start(ap, fmt); - ParseVErrorInternal(opts.debug_file, fname, lineno, type, - fmt, ap); + ParseVErrorInternal(opts.debug_file, false, fname, lineno, + type, fmt, ap); va_end(ap); } } @@ -670,37 +539,37 @@ Parse_Error(ParseErrorLevel type, const char *fmt, ...) { va_list ap; const char *fname; - size_t lineno; + unsigned lineno; if (includes.len == 0) { fname = NULL; lineno = 0; } else { - IFile *curFile = CurFile(); - fname = curFile->fname; - lineno = (size_t)curFile->lineno; + IncludedFile *curFile = CurFile(); + fname = curFile->name.str; + lineno = curFile->lineno; } - va_start(ap, fmt); (void)fflush(stdout); - ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); + va_start(ap, fmt); + ParseVErrorInternal(stderr, true, fname, lineno, type, fmt, ap); va_end(ap); - if (opts.debug_file != stderr && opts.debug_file != stdout) { + if (opts.debug_file != stdout && opts.debug_file != stderr) { va_start(ap, fmt); - ParseVErrorInternal(opts.debug_file, fname, lineno, type, - fmt, ap); + ParseVErrorInternal(opts.debug_file, true, fname, lineno, + type, fmt, ap); va_end(ap); } } /* - * Parse and handle an .info, .warning or .error directive. - * For an .error directive, immediately exit. + * Handle an .info, .warning or .error directive. For an .error directive, + * exit immediately. */ static void -ParseMessage(ParseErrorLevel level, const char *levelName, const char *umsg) +HandleMessage(ParseErrorLevel level, const char *levelName, const char *umsg) { char *xmsg; @@ -717,17 +586,15 @@ ParseMessage(ParseErrorLevel level, const char *levelName, const char *umsg) free(xmsg); if (level == PARSE_FATAL) { - PrintOnError(NULL, NULL); + PrintOnError(NULL, "\n"); exit(1); } } /* - * Add the child to the parent's children. - * - * Additionally, add the parent to the child's parents, but only if the - * target is not special. An example for such a special target is .END, - * which does not need to be informed once the child target has been made. + * Add the child to the parent's children, and for non-special targets, vice + * versa. Special targets such as .END do not need to be informed once the + * child target has been made. */ static void LinkSource(GNode *pgn, GNode *cgn, bool isSpecial) @@ -743,8 +610,8 @@ LinkSource(GNode *pgn, GNode *cgn, bool isSpecial) Lst_Append(&cgn->parents, pgn); if (DEBUG(PARSE)) { - debug_printf("# %s: added child %s - %s\n", - __func__, pgn->name, cgn->name); + debug_printf("# LinkSource: added child %s - %s\n", + pgn->name, cgn->name); Targ_PrintNode(pgn, 0); Targ_PrintNode(cgn, 0); } @@ -843,9 +710,9 @@ ApplyDependencyOperator(GNodeType op) * We give each .WAIT node a unique name (mainly for diagnostics). */ static void -ParseDependencySourceWait(bool isSpecial) +ApplyDependencySourceWait(bool isSpecial) { - static int wait_number = 0; + static unsigned wait_number = 0; char wait_src[16]; GNode *gn; @@ -859,41 +726,41 @@ ParseDependencySourceWait(bool isSpecial) } static bool -ParseDependencySourceKeyword(const char *src, ParseSpecial specType) +ApplyDependencySourceKeyword(const char *src, ParseSpecial special) { int keywd; - GNodeType op; + GNodeType targetAttr; if (*src != '.' || !ch_isupper(src[1])) return false; - keywd = ParseFindKeyword(src); + keywd = FindKeyword(src); if (keywd == -1) return false; - op = parseKeywords[keywd].op; - if (op != OP_NONE) { - ApplyDependencyOperator(op); + targetAttr = parseKeywords[keywd].targetAttr; + if (targetAttr != OP_NONE) { + ApplyDependencyOperator(targetAttr); return true; } - if (parseKeywords[keywd].spec == SP_WAIT) { - ParseDependencySourceWait(specType != SP_NOT); + if (parseKeywords[keywd].special == SP_WAIT) { + ApplyDependencySourceWait(special != SP_NOT); return true; } return false; } +/* + * In a line like ".MAIN: source1 source2", add all sources to the list of + * things to create, but only if the user didn't specify a target on the + * command line and .MAIN occurs for the first time. + * + * See HandleDependencyTargetSpecial, branch SP_MAIN. + * See unit-tests/cond-func-make-main.mk. + */ static void -ParseDependencySourceMain(const char *src) +ApplyDependencySourceMain(const char *src) { - /* - * In a line like ".MAIN: source1 source2", add all sources to the - * list of things to create, but only if the user didn't specify a - * target on the command line and .MAIN occurs for the first time. - * - * See ParseDependencyTargetSpecial, branch SP_MAIN. - * See unit-tests/cond-func-make-main.mk. - */ Lst_Append(&opts.create, bmake_strdup(src)); /* * Add the name to the .TARGETS variable as well, so the user can @@ -903,7 +770,7 @@ ParseDependencySourceMain(const char *src) } static void -ParseDependencySourceOrder(const char *src) +ApplyDependencySourceOrder(const char *src) { GNode *gn; /* @@ -917,8 +784,9 @@ ParseDependencySourceOrder(const char *src) 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); + debug_printf( + "# .ORDER forces '%s' to be made before '%s'\n", + order_pred->name, gn->name); Targ_PrintNode(order_pred, 0); Targ_PrintNode(gn, 0); } @@ -929,56 +797,42 @@ ParseDependencySourceOrder(const char *src) order_pred = gn; } +/* The source is not an attribute, so find/create a node for it. */ static void -ParseDependencySourceOther(const char *src, GNodeType tOp, - ParseSpecial specType) +ApplyDependencySourceOther(const char *src, GNodeType targetAttr, + ParseSpecial special) { GNode *gn; - /* - * The source is not an attribute, so find/create a node for it. - * After that, apply any operator to it from a special target or - * link it to its parents, as appropriate. - * - * In the case of a source that was the object of a '::' operator, - * the attribute is applied to all of its instances (as kept in - * the 'cohorts' list of the node) or all the cohorts are linked - * to all the targets. - */ - - /* Find/create the 'src' node and attach to all targets */ gn = Targ_GetNode(src); if (doing_depend) RememberLocation(gn); - if (tOp != OP_NONE) - gn->type |= tOp; + if (targetAttr != OP_NONE) + gn->type |= targetAttr; else - LinkToTargets(gn, specType != SP_NOT); + LinkToTargets(gn, special != 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 + * attribute (such as .SILENT) and if so, apply it to all targets. Otherwise * 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 + * Otherwise, make the source a child of the targets. */ static void -ParseDependencySource(GNodeType tOp, const char *src, ParseSpecial specType) +ApplyDependencySource(GNodeType targetAttr, const char *src, + ParseSpecial special) { - if (ParseDependencySourceKeyword(src, specType)) + if (ApplyDependencySourceKeyword(src, special)) return; - if (specType == SP_MAIN) - ParseDependencySourceMain(src); - else if (specType == SP_ORDER) - ParseDependencySourceOrder(src); + if (special == SP_MAIN) + ApplyDependencySourceMain(src); + else if (special == SP_ORDER) + ApplyDependencySourceOrder(src); else - ParseDependencySourceOther(src, tOp, specType); + ApplyDependencySourceOther(src, targetAttr, special); } /* @@ -987,7 +841,7 @@ ParseDependencySource(GNodeType tOp, const char *src, ParseSpecial specType) * actually a real target (i.e. isn't a .USE or .EXEC rule) to be made. */ static void -FindMainTarget(void) +MaybeUpdateMainTarget(void) { GNodeListNode *ln; @@ -996,32 +850,24 @@ FindMainTarget(void) for (ln = targets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; - if (!(gn->type & OP_NOTARGET)) { + if (GNode_IsMainCandidate(gn)) { DEBUG1(MAKE, "Setting main node to \"%s\"\n", gn->name); mainNode = gn; - Targ_SetMain(gn); return; } } } -/* - * We got to the end of the line while we were still looking at targets. - * - * Ending a dependency line without an operator is a Bozo no-no. As a - * heuristic, this is also often triggered by undetected conflicts from - * cvs/rcs merges. - */ static void -ParseErrorNoDependency(const char *lstart) +InvalidLineType(const char *line) { - if ((strncmp(lstart, "<<<<<<", 6) == 0) || - (strncmp(lstart, "======", 6) == 0) || - (strncmp(lstart, ">>>>>>", 6) == 0)) + 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; + else if (line[0] == '.') { + const char *dirstart = line + 1; const char *dirend; cpp_skip_whitespace(&dirstart); dirend = dirstart; @@ -1034,39 +880,35 @@ ParseErrorNoDependency(const char *lstart) } static void -ParseDependencyTargetWord(const char **pp, const char *lstart) +ParseDependencyTargetWord(char **pp, const char *lstart) { const char *cp = *pp; while (*cp != '\0') { if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' || *cp == '(') && - !ParseIsEscaped(lstart, cp)) + !IsEscaped(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. + * otherwise). * * There should be no errors in this, as they would * have been discovered in the initial Var_Subst and * we wouldn't be here. */ - const char *nested_p = cp; - FStr nested_val; + FStr val; - (void)Var_Parse(&nested_p, SCOPE_CMDLINE, - VARE_PARSE_ONLY, &nested_val); - /* TODO: handle errors */ - FStr_Done(&nested_val); - cp += nested_p - cp; + (void)Var_Parse(&cp, SCOPE_CMDLINE, + VARE_PARSE_ONLY, &val); + FStr_Done(&val); } else cp++; } - *pp = cp; + *pp += cp - *pp; } /* @@ -1075,11 +917,11 @@ ParseDependencyTargetWord(const char **pp, const char *lstart) * See the tests deptgt-*.mk. */ static void -ParseDependencyTargetSpecial(ParseSpecial *inout_specType, - const char *targetName, - SearchPathList **inout_paths) +HandleDependencyTargetSpecial(const char *targetName, + ParseSpecial *inout_special, + SearchPathList **inout_paths) { - switch (*inout_specType) { + switch (*inout_special) { case SP_PATH: if (*inout_paths == NULL) *inout_paths = Lst_New(); @@ -1091,7 +933,7 @@ ParseDependencyTargetSpecial(ParseSpecial *inout_specType, * .MAIN node. */ if (!Lst_IsEmpty(&opts.create)) - *inout_specType = SP_NOT; + *inout_special = SP_NOT; break; case SP_BEGIN: case SP_END: @@ -1136,13 +978,9 @@ ParseDependencyTargetSpecial(ParseSpecial *inout_specType, } } -/* - * .PATH has to be handled specially. - * Call on the suffix module to give us a path to modify. - */ static bool -ParseDependencyTargetPath(const char *suffixName, - SearchPathList **inout_paths) +HandleDependencyTargetPath(const char *suffixName, + SearchPathList **inout_paths) { SearchPath *path; @@ -1160,13 +998,12 @@ ParseDependencyTargetPath(const char *suffixName, return true; } -/* - * See if it's a special target and if so set specType to match it. - */ +/* See if it's a special target and if so set inout_special to match it. */ static bool -ParseDependencyTarget(const char *targetName, - ParseSpecial *inout_specType, - GNodeType *out_tOp, SearchPathList **inout_paths) +HandleDependencyTarget(const char *targetName, + ParseSpecial *inout_special, + GNodeType *inout_targetAttr, + SearchPathList **inout_paths) { int keywd; @@ -1177,55 +1014,42 @@ ParseDependencyTarget(const char *targetName, * See if the target is a special target that must have it * or its sources handled specially. */ - keywd = ParseFindKeyword(targetName); + keywd = FindKeyword(targetName); if (keywd != -1) { - if (*inout_specType == SP_PATH && - parseKeywords[keywd].spec != SP_PATH) { + if (*inout_special == SP_PATH && + parseKeywords[keywd].special != SP_PATH) { Parse_Error(PARSE_FATAL, "Mismatched special targets"); return false; } - *inout_specType = parseKeywords[keywd].spec; - *out_tOp = parseKeywords[keywd].op; + *inout_special = parseKeywords[keywd].special; + *inout_targetAttr = parseKeywords[keywd].targetAttr; - ParseDependencyTargetSpecial(inout_specType, targetName, + HandleDependencyTargetSpecial(targetName, inout_special, inout_paths); } else if (strncmp(targetName, ".PATH", 5) == 0) { - *inout_specType = SP_PATH; - if (!ParseDependencyTargetPath(targetName + 5, inout_paths)) + *inout_special = SP_PATH; + if (!HandleDependencyTargetPath(targetName + 5, inout_paths)) return false; } return true; } static void -ParseDependencyTargetMundane(char *targetName, StringList *curTargs) +HandleDependencyTargetMundane(char *targetName) { + StringList targetNames = LST_INIT; + if (Dir_HasWildcards(targetName)) { - /* - * Targets are to be sought only in the current directory, - * so create an empty path for the thing. Note we need to - * use Dir_Destroy in the destruction of the path as the - * Dir module could have added a directory to the path... - */ SearchPath *emptyPath = SearchPath_New(); - - SearchPath_Expand(emptyPath, targetName, curTargs); - + SearchPath_Expand(emptyPath, targetName, &targetNames); SearchPath_Free(emptyPath); - } else { - /* - * No wildcards, but we want to avoid code duplication, - * so create a list with the word on it. - */ - Lst_Append(curTargs, targetName); - } + } else + Lst_Append(&targetNames, targetName); - /* Apply the targets. */ - - while (!Lst_IsEmpty(curTargs)) { - char *targName = Lst_Dequeue(curTargs); + while (!Lst_IsEmpty(&targetNames)) { + char *targName = Lst_Dequeue(&targetNames); GNode *gn = Suff_IsTransform(targName) ? Suff_AddTransform(targName) : Targ_GetNode(targName); @@ -1237,33 +1061,28 @@ ParseDependencyTargetMundane(char *targetName, StringList *curTargs) } static void -ParseDependencyTargetExtraWarn(char **pp, const char *lstart) +SkipExtraTargets(char **pp, const char *lstart) { bool warning = false; - char *cp = *pp; + const char *p = *pp; - while (*cp != '\0') { - if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':')) + while (*p != '\0') { + if (!IsEscaped(lstart, p) && (*p == '!' || *p == ':')) break; - if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t')) + if (IsEscaped(lstart, p) || (*p != ' ' && *p != '\t')) warning = true; - cp++; + p++; } if (warning) Parse_Error(PARSE_WARNING, "Extra target ignored"); - *pp = cp; + *pp += p - *pp; } static void -ParseDependencyCheckSpec(ParseSpecial specType) +CheckSpecialMundaneMixture(ParseSpecial special) { - switch (specType) { - default: - Parse_Error(PARSE_WARNING, - "Special and mundane targets don't mix. " - "Mundane ones ignored"); - break; + switch (special) { case SP_DEFAULT: case SP_STALE: case SP_BEGIN: @@ -1275,7 +1094,14 @@ ParseDependencyCheckSpec(ParseSpecial specType) * shouldn't be empty. */ case SP_NOT: - /* Nothing special here -- targets can be empty if it wants. */ + /* + * Nothing special here -- targets can be empty if it wants. + */ + break; + default: + Parse_Error(PARSE_WARNING, + "Special and mundane targets don't mix. " + "Mundane ones ignored"); break; } } @@ -1284,34 +1110,15 @@ ParseDependencyCheckSpec(ParseSpecial specType) * In a dependency line like 'targets: sources' or 'targets! sources', parse * the operator ':', '::' or '!' from between the targets and the sources. */ -static bool -ParseDependencyOp(char **pp, const char *lstart, GNodeType *out_op) +static GNodeType +ParseDependencyOp(char **pp) { - 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; - } + if (**pp == '!') + return (*pp)++, OP_FORCE; + if ((*pp)[1] == ':') + return *pp += 2, OP_DOUBLEDEP; + else + return (*pp)++, OP_DEPENDS; } static void @@ -1326,19 +1133,11 @@ ClearPaths(SearchPathList *paths) Dir_SetPATH(); } -/* - * Several special targets take different actions if present with no - * sources: - * a .SUFFIXES line with no sources clears out all old suffixes - * a .PRECIOUS line makes all targets precious - * a .IGNORE line ignores errors for all targets - * a .SILENT line creates silence when making all targets - * a .PATH removes all directories from the search path(s). - */ +/* Handle a "dependency" line like '.SPECIAL:' without any sources. */ static void -ParseDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) +HandleDependencySourcesEmpty(ParseSpecial special, SearchPathList *paths) { - switch (specType) { + switch (special) { case SP_SUFFIXES: Suff_ClearSuffixes(); break; @@ -1349,7 +1148,7 @@ ParseDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) opts.ignoreErrors = true; break; case SP_SILENT: - opts.beSilent = true; + opts.silent = true; break; case SP_PATH: ClearPaths(paths); @@ -1375,39 +1174,17 @@ AddToPaths(const char *dir, SearchPathList *paths) } /* - * 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. + * 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. */ static void -ParseDependencySourceSpecial(ParseSpecial specType, char *word, +ParseDependencySourceSpecial(ParseSpecial special, const char *word, SearchPathList *paths) { - switch (specType) { + switch (special) { case SP_SUFFIXES: - Suff_AddSuffix(word, &mainNode); + Suff_AddSuffix(word); break; case SP_PATH: AddToPaths(word, paths); @@ -1430,121 +1207,92 @@ ParseDependencySourceSpecial(ParseSpecial specType, char *word, } static bool -ParseDependencyTargets(char **inout_cp, - char **inout_line, - const char *lstart, - ParseSpecial *inout_specType, - GNodeType *inout_tOp, - SearchPathList **inout_paths, - StringList *curTargs) +ApplyDependencyTarget(char *name, char *nameEnd, ParseSpecial *inout_special, + GNodeType *inout_targetAttr, + SearchPathList **inout_paths) { - char *cp; - char *tgt = *inout_line; - char savec; - const char *p; + char savec = *nameEnd; + *nameEnd = '\0'; + + if (!HandleDependencyTarget(name, inout_special, + inout_targetAttr, inout_paths)) + return false; + + if (*inout_special == SP_NOT && *name != '\0') + HandleDependencyTargetMundane(name); + else if (*inout_special == SP_PATH && *name != '.' && *name != '\0') + Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", name); + + *nameEnd = savec; + return true; +} + +static bool +ParseDependencyTargets(char **inout_cp, + const char *lstart, + ParseSpecial *inout_special, + GNodeType *inout_targetAttr, + SearchPathList **inout_paths) +{ + char *cp = *inout_cp; for (;;) { - /* - * Here LINE points to the beginning of the next word, and - * LSTART points to the actual beginning of the line. - */ + char *tgt = cp; - /* Find the end of the next word. */ - cp = tgt; - p = cp; - ParseDependencyTargetWord(&p, lstart); - cp += p - cp; + 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). + * name of one or more files inside an archive. */ - if (!ParseIsEscaped(lstart, cp) && *cp == '(') { - /* - * Archives must be handled specially to make sure the - * OP_ARCHV flag is set in their 'type' field, for one - * thing, and because things like "archive(file1.o - * file2.o file3.o)" are permissible. - * - * Arch_ParseArchive will set 'line' to be the first - * non-blank after the archive-spec. It creates/finds - * nodes for the members and places them on the given - * list, returning true if all went well and false if - * there was an error in the specification. On error, - * line should remain untouched. - */ - if (!Arch_ParseArchive(&tgt, targets, SCOPE_CMDLINE)) { + if (!IsEscaped(lstart, cp) && *cp == '(') { + cp = tgt; + if (!Arch_ParseArchive(&cp, targets, SCOPE_CMDLINE)) { Parse_Error(PARSE_FATAL, "Error in archive specification: \"%s\"", tgt); return false; } - - cp = tgt; continue; } if (*cp == '\0') { - ParseErrorNoDependency(lstart); + InvalidLineType(lstart); return false; } - /* Insert a null terminator. */ - savec = *cp; - *cp = '\0'; - - if (!ParseDependencyTarget(tgt, inout_specType, inout_tOp, - inout_paths)) + if (!ApplyDependencyTarget(tgt, cp, inout_special, + inout_targetAttr, inout_paths)) return false; - /* - * Have word in line. Get or create its node and stick it at - * the end of the targets list - */ - if (*inout_specType == SP_NOT && *tgt != '\0') - ParseDependencyTargetMundane(tgt, curTargs); - else if (*inout_specType == SP_PATH && *tgt != '.' && - *tgt != '\0') - Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", - tgt); - - /* Don't need the inserted null terminator any more. */ - *cp = savec; - - /* - * If it is a special type and not .PATH, it's the only target - * we allow on this line. - */ - if (*inout_specType != SP_NOT && *inout_specType != SP_PATH) - ParseDependencyTargetExtraWarn(&cp, lstart); + if (*inout_special != SP_NOT && *inout_special != SP_PATH) + SkipExtraTargets(&cp, lstart); else pp_skip_whitespace(&cp); - tgt = cp; - if (*tgt == '\0') + if (*cp == '\0') break; - if ((*tgt == '!' || *tgt == ':') && - !ParseIsEscaped(lstart, tgt)) + if ((*cp == '!' || *cp == ':') && !IsEscaped(lstart, cp)) break; } *inout_cp = cp; - *inout_line = tgt; return true; } static void -ParseDependencySourcesSpecial(char *start, char *end, - ParseSpecial specType, SearchPathList *paths) +ParseDependencySourcesSpecial(char *start, + ParseSpecial special, SearchPathList *paths) { char savec; while (*start != '\0') { + char *end = start; while (*end != '\0' && !ch_isspace(*end)) end++; savec = *end; *end = '\0'; - ParseDependencySourceSpecial(specType, start, paths); + ParseDependencySourceSpecial(special, start, paths); *end = savec; if (savec != '\0') end++; @@ -1553,11 +1301,42 @@ ParseDependencySourcesSpecial(char *start, char *end, } } +static void +LinkVarToTargets(VarAssign *var) +{ + GNodeListNode *ln; + + for (ln = targets->first; ln != NULL; ln = ln->next) + Parse_Var(var, ln->datum); +} + static bool -ParseDependencySourcesMundane(char *start, char *end, - ParseSpecial specType, GNodeType tOp) +ParseDependencySourcesMundane(char *start, + ParseSpecial special, GNodeType targetAttr) { while (*start != '\0') { + char *end = start; + VarAssign var; + + /* + * Check for local variable assignment, + * rest of the line is the value. + */ + if (Parse_IsVar(start, &var)) { + /* + * Check if this makefile has disabled + * setting local variables. + */ + bool target_vars = GetBooleanExpr( + "${.MAKE.TARGET_LOCAL_VARIABLES}", true); + + if (target_vars) + LinkVarToTargets(&var); + free(var.varname); + if (target_vars) + return true; + } + /* * The targets take real sources, so we must beware of archive * specifications (i.e. things with left parentheses in them) @@ -1588,7 +1367,8 @@ ParseDependencySourcesMundane(char *start, char *end, while (!Lst_IsEmpty(&sources)) { GNode *gn = Lst_Dequeue(&sources); - ParseDependencySource(tOp, gn->name, specType); + ApplyDependencySource(targetAttr, gn->name, + special); } Lst_Done(&sources); end = start; @@ -1598,7 +1378,7 @@ ParseDependencySourcesMundane(char *start, char *end, end++; } - ParseDependencySource(tOp, start, specType); + ApplyDependencySource(targetAttr, start, special); } pp_skip_whitespace(&end); start = end; @@ -1607,54 +1387,49 @@ ParseDependencySourcesMundane(char *start, char *end, } /* - * In a dependency line like 'targets: sources', parse the sources. + * From a dependency line like 'targets: sources', parse the sources. * * See the tests depsrc-*.mk. */ static void -ParseDependencySources(char *line, char *cp, GNodeType tOp, - ParseSpecial specType, SearchPathList **inout_paths) +ParseDependencySources(char *p, GNodeType targetAttr, + ParseSpecial special, SearchPathList **inout_paths) { - if (line[0] == '\0') { - ParseDependencySourcesEmpty(specType, *inout_paths); - } else if (specType == SP_MFLAGS) { - Main_ParseArgLine(line); - /* - * Set the initial character to a null-character so the loop - * to get sources won't get anything. - */ - *line = '\0'; - } else if (specType == SP_SHELL) { - if (!Job_ParseShell(line)) { + if (*p == '\0') { + HandleDependencySourcesEmpty(special, *inout_paths); + } else if (special == SP_MFLAGS) { + Main_ParseArgLine(p); + return; + } else if (special == SP_SHELL) { + if (!Job_ParseShell(p)) { Parse_Error(PARSE_FATAL, "improper shell specification"); return; } - *line = '\0'; - } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL || - specType == SP_DELETE_ON_ERROR) { - *line = '\0'; + return; + } else if (special == SP_NOTPARALLEL || special == SP_SINGLESHELL || + special == SP_DELETE_ON_ERROR) { + return; } /* Now go for the sources. */ - if (specType == SP_SUFFIXES || specType == SP_PATH || - specType == SP_INCLUDES || specType == SP_LIBS || - specType == SP_NULL || specType == SP_OBJDIR) { - ParseDependencySourcesSpecial(line, cp, specType, - *inout_paths); + if (special == SP_SUFFIXES || special == SP_PATH || + special == SP_INCLUDES || special == SP_LIBS || + special == SP_NULL || special == SP_OBJDIR) { + ParseDependencySourcesSpecial(p, special, *inout_paths); if (*inout_paths != NULL) { Lst_Free(*inout_paths); *inout_paths = NULL; } - if (specType == SP_PATH) + if (special == SP_PATH) Dir_SetPATH(); } else { assert(*inout_paths == NULL); - if (!ParseDependencySourcesMundane(line, cp, specType, tOp)) + if (!ParseDependencySourcesMundane(p, special, targetAttr)) return; } - FindMainTarget(); + MaybeUpdateMainTarget(); } /* @@ -1665,8 +1440,7 @@ ParseDependencySources(char *line, char *cp, GNodeType tOp, * 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 ParseOp function. + * which is where the nodes found for the targets are kept. * * 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, @@ -1674,109 +1448,61 @@ ParseDependencySources(char *line, char *cp, GNodeType tOp, * 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. + * specially, see ParseSpecial. * - * 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. + * Transformation rules such as '.c.o' are also handled here, see + * Suff_AddTransform. * * Upon return, the value of the line is unspecified. */ static void ParseDependency(char *line) { - char *cp; /* our current position */ - GNodeType op; /* the operator on the line */ - SearchPathList *paths; /* search paths to alter when parsing - * a list of .PATH targets */ - GNodeType tOp; /* operator from special target */ - /* target names to be found and added to the targets list */ - StringList curTargs = LST_INIT; - char *lstart = line; - - /* - * specType contains the SPECial TYPE of the current target. It is - * SP_NOT if the target is unspecial. If it *is* special, however, the - * children are linked as children of the parent but not vice versa. - */ - ParseSpecial specType = SP_NOT; + char *p; + SearchPathList *paths; /* search paths to alter when parsing a list + * of .PATH targets */ + GNodeType targetAttr; /* from special sources */ + ParseSpecial special; /* in special targets, the children are + * linked as children of the parent but not + * vice versa */ DEBUG1(PARSE, "ParseDependency(%s)\n", line); - tOp = OP_NONE; - + p = line; paths = NULL; + targetAttr = OP_NONE; + special = SP_NOT; - /* - * First, grind through the targets. - */ - /* XXX: don't use 'line' as an iterator variable */ - if (!ParseDependencyTargets(&cp, &line, lstart, &specType, &tOp, - &paths, &curTargs)) + if (!ParseDependencyTargets(&p, line, &special, &targetAttr, &paths)) goto out; - /* - * Don't need the list of target names anymore. - * The targets themselves are now in the global variable 'targets'. - */ - Lst_Done(&curTargs); - Lst_Init(&curTargs); - if (!Lst_IsEmpty(targets)) - ParseDependencyCheckSpec(specType); + CheckSpecialMundaneMixture(special); - /* - * Have now parsed all the target names. Must parse the operator next. - */ - if (!ParseDependencyOp(&cp, lstart, &op)) - goto out; + ApplyDependencyOperator(ParseDependencyOp(&p)); - /* - * Apply the operator to the target. This is how we remember which - * operator a target was defined with. It fails if the operator - * used isn't consistent across all references. - */ - ApplyDependencyOperator(op); + pp_skip_whitespace(&p); - /* - * Onward to the sources. - * - * LINE will now point to the first source word, if any, or the - * end of the string if not. - */ - pp_skip_whitespace(&cp); - line = cp; /* XXX: 'line' is an inappropriate name */ - - ParseDependencySources(line, cp, tOp, specType, &paths); + ParseDependencySources(p, targetAttr, special, &paths); out: if (paths != NULL) Lst_Free(paths); - Lst_Done(&curTargs); } -typedef struct VarAssignParsed { - const char *nameStart; /* unexpanded */ - const char *nameEnd; /* before operator adjustment */ - const char *eq; /* the '=' of the assignment operator */ -} VarAssignParsed; - /* * Determine the assignment operator and adjust the end of the variable * name accordingly. */ -static void -AdjustVarassignOp(const VarAssignParsed *pvar, const char *value, - VarAssign *out_var) +static VarAssign +AdjustVarassignOp(const char *name, const char *nameEnd, const char *op, + const char *value) { - const char *op = pvar->eq; - const char *const name = pvar->nameStart; VarAssignOp type; + VarAssign va; if (op > name && op[-1] == '+') { - type = VAR_APPEND; op--; + type = VAR_APPEND; } else if (op > name && op[-1] == '?') { op--; @@ -1796,20 +1522,17 @@ AdjustVarassignOp(const VarAssignParsed *pvar, const char *value, while (op > name && ch_isspace(op[-1])) op--; - if (op >= name + 3 && op[-3] == ':' && op[-2] == 's' && - op[-1] == 'h') { - type = VAR_SHELL; + if (op - name >= 3 && memcmp(op - 3, ":sh", 3) == 0) { op -= 3; + type = VAR_SHELL; } #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; - } + va.varname = bmake_strsedup(name, nameEnd < op ? nameEnd : op); + va.op = type; + va.value = value; + return va; } /* @@ -1825,11 +1548,10 @@ AdjustVarassignOp(const VarAssignParsed *pvar, const char *value, * * Used for both lines in a file and command line arguments. */ -bool +static bool Parse_IsVar(const char *p, VarAssign *out_var) { - VarAssignParsed pvar; - const char *firstSpace = NULL; + const char *nameStart, *nameEnd, *firstSpace, *eq; int level = 0; cpp_skip_hspace(&p); /* Skip to variable name */ @@ -1837,14 +1559,12 @@ Parse_IsVar(const char *p, VarAssign *out_var) /* * 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 + * ':sh' modifier. Of these two (nameEnd and eq), the earlier one * determines the actual end of the variable name. */ - pvar.nameStart = p; -#ifdef CLEANUP - pvar.nameEnd = NULL; - pvar.eq = NULL; -#endif + + nameStart = p; + firstSpace = NULL; /* * Scan for one of the assignment operators outside a variable @@ -1864,36 +1584,34 @@ Parse_IsVar(const char *p, VarAssign *out_var) if (level != 0) continue; - if (ch == ' ' || ch == '\t') - if (firstSpace == NULL) - firstSpace = p - 1; + if ((ch == ' ' || ch == '\t') && firstSpace == NULL) + firstSpace = p - 1; while (ch == ' ' || ch == '\t') ch = *p++; + if (ch == '\0') + return false; #ifdef SUNSHCMD if (ch == ':' && p[0] == 's' && p[1] == 'h') { p += 2; continue; } #endif - if (ch == '=') { - pvar.eq = p - 1; - pvar.nameEnd = firstSpace != NULL ? firstSpace : p - 1; - cpp_skip_whitespace(&p); - AdjustVarassignOp(&pvar, p, out_var); - return true; - } - if (*p == '=' && - (ch == '+' || ch == ':' || ch == '?' || ch == '!')) { - pvar.eq = p; - pvar.nameEnd = firstSpace != NULL ? firstSpace : p; - p++; - cpp_skip_whitespace(&p); - AdjustVarassignOp(&pvar, p, out_var); - return true; - } - if (firstSpace != NULL) + if (ch == '=') + eq = p - 1; + else if (*p == '=' && + (ch == '+' || ch == ':' || ch == '?' || ch == '!')) + eq = p; + else if (firstSpace != NULL) return false; + else + continue; + + nameEnd = firstSpace != NULL ? firstSpace : eq; + p = eq + 1; + cpp_skip_whitespace(&p); + *out_var = AdjustVarassignOp(nameStart, nameEnd, eq, p); + return true; } return false; @@ -1917,6 +1635,7 @@ VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *scope) } } +/* Perform a variable assignment that uses the operator ':='. */ static void VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, FStr *out_avalue) @@ -1929,6 +1648,8 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, * * TODO: Add a test that demonstrates why this code is needed, * apart from making the debug log longer. + * + * XXX: The variable name is expanded up to 3 times. */ if (!Var_ExistsExpand(scope, name)) Var_SetExpand(scope, name, ""); @@ -1941,30 +1662,25 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, *out_avalue = FStr_InitOwn(evalue); } +/* Perform a variable assignment that uses the operator '!='. */ static void VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope, FStr *out_avalue) { FStr cmd; - const char *errfmt; - char *cmdOut; + char *output, *error; cmd = FStr_InitRefer(uvalue); - if (strchr(cmd.str, '$') != NULL) { - char *expanded; - (void)Var_Subst(cmd.str, SCOPE_CMDLINE, VARE_UNDEFERR, - &expanded); - /* TODO: handle errors */ - cmd = FStr_InitOwn(expanded); + Var_Expand(&cmd, SCOPE_CMDLINE, VARE_UNDEFERR); + + output = Cmd_Exec(cmd.str, &error); + Var_SetExpand(scope, name, output); + *out_avalue = FStr_InitOwn(output); + if (error != NULL) { + Parse_Error(PARSE_WARNING, "%s", error); + free(error); } - cmdOut = Cmd_Exec(cmd.str, &errfmt); - Var_SetExpand(scope, name, cmdOut); - *out_avalue = FStr_InitOwn(cmdOut); - - if (errfmt != NULL) - Parse_Error(PARSE_WARNING, errfmt, cmd.str); - FStr_Done(&cmd); } @@ -1992,6 +1708,7 @@ VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, else if (op == VAR_SHELL) VarAssign_EvalShell(name, uvalue, scope, &avalue); else { + /* XXX: The variable name is expanded up to 2 times. */ if (op == VAR_DEFAULT && Var_ExistsExpand(scope, name)) return false; @@ -2007,7 +1724,7 @@ static void VarAssignSpecial(const char *name, const char *avalue) { if (strcmp(name, MAKEOVERRIDES) == 0) - Main_ExportMAKEFLAGS(false); /* re-export MAKEFLAGS */ + Main_ExportMAKEFLAGS(false); /* re-export MAKEFLAGS */ else if (strcmp(name, ".CURDIR") == 0) { /* * Someone is being (too?) clever... @@ -2022,19 +1739,17 @@ VarAssignSpecial(const char *name, const char *avalue) Var_ExportVars(avalue); } -/* Perform the variable variable assignment in the given scope. */ -void +/* Perform the variable assignment in the given scope. */ +static void Parse_Var(VarAssign *var, GNode *scope) { - FStr avalue; /* actual value (maybe expanded) */ + FStr avalue; /* actual value (maybe expanded) */ VarCheckSyntax(var->op, var->value, scope); if (VarAssign_Eval(var->varname, var->op, var->value, scope, &avalue)) { VarAssignSpecial(var->varname, avalue.str); FStr_Done(&avalue); } - - free(var->varname); } @@ -2052,7 +1767,7 @@ MaybeSubMake(const char *cmd) char endc; /* XXX: What if progname != "make"? */ - if (p[0] == 'm' && p[1] == 'a' && p[2] == 'k' && p[3] == 'e') + if (strncmp(p, "make", 4) == 0) if (start == cmd || !ch_isalnum(p[-1])) if (!ch_isalnum(p[4])) return true; @@ -2072,9 +1787,8 @@ MaybeSubMake(const char *cmd) if (*p == '.') /* Accept either ${.MAKE} or ${MAKE}. */ p++; - if (p[0] == 'M' && p[1] == 'A' && p[2] == 'K' && p[3] == 'E') - if (p[4] == endc) - return true; + if (strncmp(p, "MAKE", 4) == 0 && p[4] == endc) + return true; } return false; } @@ -2086,7 +1800,7 @@ MaybeSubMake(const char *cmd) * be that. */ static void -ParseAddCmd(GNode *gn, char *cmd) +GNode_AddCommand(GNode *gn, char *cmd) { /* Add to last (ie current) cohort for :: targets */ if ((gn->type & OP_DOUBLEDEP) && gn->cohorts.last != NULL) @@ -2104,13 +1818,13 @@ ParseAddCmd(GNode *gn, char *cmd) Lst_Append(&gn->commands, cmd); Parse_Error(PARSE_WARNING, "overriding commands for target \"%s\"; " - "previous commands defined at %s: %d ignored", + "previous commands defined at %s: %u ignored", gn->name, gn->fname, gn->lineno); #else Parse_Error(PARSE_WARNING, "duplicate script for target \"%s\" ignored", gn->name); - ParseErrorInternal(gn->fname, (size_t)gn->lineno, PARSE_WARNING, + ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING, "using previous script for \"%s\" defined here", gn->name); #endif @@ -2140,7 +1854,7 @@ Parse_AddIncludeDir(const char *dir) static void IncludeFile(const char *file, bool isSystem, bool depinc, bool silent) { - struct loadedfile *lf; + Buffer buf; char *fullname; /* full pathname of file */ char *newName; char *slash, *incdir; @@ -2158,7 +1872,7 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent) * we can locate the file. */ - incdir = bmake_strdup(CurFile()->fname); + incdir = bmake_strdup(CurFile()->name.str); slash = strrchr(incdir, '/'); if (slash != NULL) { *slash = '\0'; @@ -2233,15 +1947,13 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent) return; } - /* load it */ - lf = loadfile(fullname, fd); + buf = loadfile(fullname, fd); + (void)close(fd); - /* Start reading from this file next */ - Parse_PushInput(fullname, 0, -1, loadedfile_readMore, lf); - CurFile()->lf = lf; + Parse_PushInput(fullname, 1, 0, buf, NULL); if (depinc) doing_depend = depinc; /* only turn it on */ - /* TODO: consider free(fullname); */ + free(fullname); } /* @@ -2285,13 +1997,7 @@ ParseInclude(char *directive) *p = '\0'; - if (strchr(file.str, '$') != NULL) { - char *xfile; - Var_Subst(file.str, SCOPE_CMDLINE, VARE_WANTRES, &xfile); - /* TODO: handle errors */ - file = FStr_InitOwn(xfile); - } - + Var_Expand(&file, SCOPE_CMDLINE, VARE_WANTRES); IncludeFile(file.str, endc == '>', directive[0] == 'd', silent); FStr_Done(&file); } @@ -2318,8 +2024,8 @@ SetFilenameVars(const char *filename, const char *dirvar, const char *filevar) Global_Set(dirvar, dirname.str); Global_Set(filevar, basename); - DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n", - __func__, dirvar, dirname.str, filevar, basename); + DEBUG4(PARSE, "SetFilenameVars: ${%s} = `%s' ${%s} = `%s'\n", + dirvar, dirname.str, filevar, basename); FStr_Done(&dirname); } @@ -2333,17 +2039,17 @@ static const char * GetActuallyIncludingFile(void) { size_t i; - const IFile *incs = GetInclude(0); + const IncludedFile *incs = GetInclude(0); for (i = includes.len; i >= 2; i--) - if (!incs[i - 1].fromForLoop) - return incs[i - 2].fname; + if (incs[i - 1].forLoop == NULL) + return incs[i - 2].name.str; return NULL; } /* Set .PARSEDIR, .PARSEFILE, .INCLUDEDFROMDIR and .INCLUDEDFROMFILE. */ static void -ParseSetParseFile(const char *filename) +SetParseFile(const char *filename) { const char *including; @@ -2364,17 +2070,16 @@ StrContainsWord(const char *str, const char *word) { size_t strLen = strlen(str); size_t wordLen = strlen(word); - const char *p, *end; + const char *p; if (strLen < wordLen) - return false; /* str is too short to contain word */ + return false; - end = str + strLen - wordLen; for (p = str; p != NULL; p = strchr(p, ' ')) { if (*p == ' ') p++; - if (p > end) - return false; /* cannot contain word */ + if (p > str + strLen - wordLen) + return false; if (memcmp(p, word, wordLen) == 0 && (p[wordLen] == '\0' || p[wordLen] == ' ')) @@ -2406,67 +2111,45 @@ VarContainsWord(const char *varname, const char *word) * of makefiles that have been loaded. */ static void -ParseTrackInput(const char *name) +TrackInput(const char *name) { if (!VarContainsWord(MAKE_MAKEFILES, name)) Global_Append(MAKE_MAKEFILES, name); } -/* - * Start parsing from the given source. - * - * The given file is added to the includes stack. - */ +/* Parse from the given buffer, later return to the current file. */ void -Parse_PushInput(const char *name, int lineno, int fd, - ReadMoreProc readMore, void *readMoreArg) +Parse_PushInput(const char *name, unsigned lineno, unsigned readLines, + Buffer buf, struct ForLoop *forLoop) { - IFile *curFile; - char *buf; - size_t len; - bool fromForLoop = name == NULL; + IncludedFile *curFile; - if (fromForLoop) - name = CurFile()->fname; + if (forLoop != NULL) + name = CurFile()->name.str; else - ParseTrackInput(name); + TrackInput(name); - DEBUG3(PARSE, "Parse_PushInput: %s %s, line %d\n", - readMore == loadedfile_readMore ? "file" : ".for loop in", - name, lineno); - - if (fd == -1 && readMore == NULL) - /* sanity */ - return; + DEBUG3(PARSE, "Parse_PushInput: %s %s, line %u\n", + forLoop != NULL ? ".for loop in": "file", name, lineno); curFile = Vector_Push(&includes); - curFile->fname = bmake_strdup(name); - curFile->fromForLoop = fromForLoop; + curFile->name = FStr_InitOwn(bmake_strdup(name)); curFile->lineno = lineno; - curFile->first_lineno = lineno; - curFile->readMore = readMore; - curFile->readMoreArg = readMoreArg; - curFile->lf = NULL; + curFile->readLines = readLines; + curFile->forHeadLineno = lineno; + curFile->forBodyReadLines = readLines; + curFile->buf = buf; curFile->depending = doing_depend; /* restore this on EOF */ + curFile->forLoop = forLoop; - assert(readMore != NULL); - - /* Get first block of input data */ - buf = curFile->readMore(curFile->readMoreArg, &len); - if (buf == NULL) { - /* Was all a waste of time ... */ - if (curFile->fname != NULL) - free(curFile->fname); - free(curFile); - return; - } - curFile->buf_freeIt = buf; - curFile->buf_ptr = buf; - curFile->buf_end = buf + len; + if (forLoop != NULL && !For_NextIteration(forLoop, &curFile->buf)) + abort(); /* see For_Run */ + curFile->buf_ptr = curFile->buf.data; + curFile->buf_end = curFile->buf.data + curFile->buf.len; curFile->cond_depth = Cond_save_depth(); - ParseSetParseFile(name); + SetParseFile(name); } /* Check if the directive is an include directive. */ @@ -2518,22 +2201,13 @@ ParseTraditionalInclude(char *line) char *file = line + (silent ? 8 : 7); char *all_files; - DEBUG2(PARSE, "%s: %s\n", __func__, file); + DEBUG1(PARSE, "ParseTraditionalInclude: %s\n", file); pp_skip_whitespace(&file); - /* - * Substitute for any variables in the file name before trying to - * find the thing. - */ (void)Var_Subst(file, SCOPE_CMDLINE, VARE_WANTRES, &all_files); /* TODO: handle errors */ - if (*file == '\0') { - Parse_Error(PARSE_FATAL, "Filename missing from \"include\""); - goto out; - } - for (file = all_files; !done; file = cp + 1) { /* Skip to end of line or next whitespace */ for (cp = file; *cp != '\0' && !ch_isspace(*cp); cp++) @@ -2546,7 +2220,7 @@ ParseTraditionalInclude(char *line) IncludeFile(file, false, false, silent); } -out: + free(all_files); } #endif @@ -2559,7 +2233,7 @@ ParseGmakeExport(char *line) char *variable = line + 6; char *value; - DEBUG2(PARSE, "%s: %s\n", __func__, variable); + DEBUG1(PARSE, "ParseGmakeExport: %s\n", variable); pp_skip_whitespace(&variable); @@ -2596,33 +2270,27 @@ ParseGmakeExport(char *line) static bool ParseEOF(void) { - char *ptr; - size_t len; - IFile *curFile = CurFile(); + IncludedFile *curFile = CurFile(); - assert(curFile->readMore != NULL); - - doing_depend = curFile->depending; /* restore this */ - /* get next input buffer, if any */ - ptr = curFile->readMore(curFile->readMoreArg, &len); - curFile->buf_ptr = ptr; - curFile->buf_freeIt = ptr; - curFile->buf_end = ptr == NULL ? NULL : ptr + len; - curFile->lineno = curFile->first_lineno; - if (ptr != NULL) - return true; /* Iterate again */ - - /* 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; + doing_depend = curFile->depending; + if (curFile->forLoop != NULL && + For_NextIteration(curFile->forLoop, &curFile->buf)) { + curFile->buf_ptr = curFile->buf.data; + curFile->buf_end = curFile->buf.data + curFile->buf.len; + curFile->readLines = curFile->forBodyReadLines; + return true; } - /* Dispose of curFile info */ - /* Leak curFile->fname because all the GNodes have pointers to it. */ - free(curFile->buf_freeIt); + /* + * Ensure the makefile (or .for loop) didn't have mismatched + * conditionals. + */ + Cond_restore_depth(curFile->cond_depth); + + FStr_Done(&curFile->name); + Buf_Done(&curFile->buf); + if (curFile->forLoop != NULL) + ForLoop_Free(curFile->forLoop); Vector_Pop(&includes); if (includes.len == 0) { @@ -2635,10 +2303,10 @@ ParseEOF(void) } curFile = CurFile(); - DEBUG2(PARSE, "ParseEOF: returning to file %s, line %d\n", - curFile->fname, curFile->lineno); + DEBUG2(PARSE, "ParseEOF: returning to file %s, line %u\n", + curFile->name.str, curFile->readLines + 1); - ParseSetParseFile(curFile->fname); + SetParseFile(curFile->name.str); return true; } @@ -2654,18 +2322,18 @@ typedef enum ParseRawLineResult { * the line is not null-terminated. */ static ParseRawLineResult -ParseRawLine(IFile *curFile, char **out_line, char **out_line_end, - char **out_firstBackslash, char **out_firstComment) +ParseRawLine(IncludedFile *curFile, char **out_line, char **out_line_end, + char **out_firstBackslash, char **out_commentLineEnd) { char *line = curFile->buf_ptr; char *buf_end = curFile->buf_end; char *p = line; char *line_end = line; char *firstBackslash = NULL; - char *firstComment = NULL; + char *commentLineEnd = NULL; ParseRawLineResult res = PRLR_LINE; - curFile->lineno++; + curFile->readLines++; for (;;) { char ch; @@ -2676,8 +2344,7 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end, } ch = *p; - if (ch == '\0' || - (ch == '\\' && p + 1 < buf_end && p[1] == '\0')) { + if (ch == '\0' || (ch == '\\' && p[1] == '\0')) { Parse_Error(PARSE_FATAL, "Zero byte read from file"); return PRLR_ERROR; } @@ -2687,7 +2354,7 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end, if (firstBackslash == NULL) firstBackslash = p; if (p[1] == '\n') { - curFile->lineno++; + curFile->readLines++; if (p + 2 == buf_end) { line_end = p; *line_end = '\n'; @@ -2705,9 +2372,9 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end, * Remember the first '#' for comment stripping, unless * the previous char was '[', as in the modifier ':[#]'. */ - if (ch == '#' && firstComment == NULL && + if (ch == '#' && commentLineEnd == NULL && !(p > line && p[-1] == '[')) - firstComment = line_end; + commentLineEnd = line_end; p++; if (ch == '\n') @@ -2718,11 +2385,11 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end, line_end = p; } - *out_line = line; curFile->buf_ptr = p; + *out_line = line; *out_line_end = line_end; *out_firstBackslash = firstBackslash; - *out_firstComment = firstComment; + *out_commentLineEnd = commentLineEnd; return res; } @@ -2733,7 +2400,7 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end, static void UnescapeBackslash(char *line, char *start) { - char *src = start; + const char *src = start; char *dst = start; char *spaceStart = line; @@ -2748,35 +2415,24 @@ UnescapeBackslash(char *line, char *start) ch = *src++; if (ch == '\0') { - /* Delete '\\' at end of buffer */ + /* Delete '\\' at the end of the buffer. */ dst--; break; } - /* Delete '\\' from before '#' on non-command lines */ - if (ch == '#' && line[0] != '\t') { + /* Delete '\\' from before '#' on non-command lines. */ + if (ch == '#' && line[0] != '\t') *dst++ = ch; - continue; - } - - if (ch != '\n') { - /* Leave '\\' in buffer for later */ + else if (ch == '\n') { + cpp_skip_hspace(&src); + *dst++ = ' '; + } else { + /* Leave '\\' in the buffer for later. */ *dst++ = '\\'; - /* - * Make sure we don't delete an escaped ' ' from the - * line end. - */ - spaceStart = dst + 1; *dst++ = ch; - continue; + /* Keep an escaped ' ' at the line end. */ + spaceStart = dst; } - - /* - * Escaped '\n' -- replace following whitespace with a single - * ' '. - */ - pp_skip_hspace(&src); - *dst++ = ' '; } /* Delete any trailing spaces - eg from empty continuations */ @@ -2785,13 +2441,13 @@ UnescapeBackslash(char *line, char *start) *dst = '\0'; } -typedef enum GetLineMode { +typedef enum LineKind { /* * Return the next line that is neither empty nor a comment. * Backslash line continuations are folded into a single space. * A trailing comment, if any, is discarded. */ - GLM_NONEMPTY, + LK_NONEMPTY, /* * Return the next line, even if it is empty or a comment. @@ -2800,7 +2456,7 @@ typedef enum GetLineMode { * Used in .for loops to collect the body of the loop while waiting * for the corresponding .endfor. */ - GLM_FOR_BODY, + LK_FOR_BODY, /* * Return the next line that starts with a dot. @@ -2810,29 +2466,34 @@ typedef enum GetLineMode { * Used in .if directives to skip over irrelevant branches while * waiting for the corresponding .endif. */ - GLM_DOT -} GetLineMode; + LK_DOT +} LineKind; -/* Return the next "interesting" logical line from the current file. */ +/* + * Return the next "interesting" logical line from the current file. The + * returned string will be freed at the end of including the file. + */ static char * -ParseGetLine(GetLineMode mode) +ReadLowLevelLine(LineKind kind) { - IFile *curFile = CurFile(); + IncludedFile *curFile = CurFile(); + ParseRawLineResult res; char *line; char *line_end; char *firstBackslash; - char *firstComment; + char *commentLineEnd; for (;;) { - ParseRawLineResult res = ParseRawLine(curFile, - &line, &line_end, &firstBackslash, &firstComment); + curFile->lineno = curFile->readLines + 1; + res = ParseRawLine(curFile, + &line, &line_end, &firstBackslash, &commentLineEnd); if (res == PRLR_ERROR) return NULL; - if (line_end == line || firstComment == line) { + if (line == line_end || line == commentLineEnd) { if (res == PRLR_EOF) return NULL; - if (mode != GLM_FOR_BODY) + if (kind != LK_FOR_BODY) continue; } @@ -2840,59 +2501,53 @@ ParseGetLine(GetLineMode mode) assert(ch_isspace(*line_end)); *line_end = '\0'; - if (mode == GLM_FOR_BODY) + if (kind == LK_FOR_BODY) return line; /* Don't join the physical lines. */ - if (mode == GLM_DOT && line[0] != '.') + if (kind == LK_DOT && line[0] != '.') continue; break; } - /* Brutally ignore anything after a non-escaped '#' in non-commands. */ - if (firstComment != NULL && line[0] != '\t') - *firstComment = '\0'; - - /* If we didn't see a '\\' then the in-situ data is fine. */ - if (firstBackslash == NULL) - return line; - - /* Remove escapes from '\n' and '#' */ - UnescapeBackslash(line, firstBackslash); - + if (commentLineEnd != NULL && line[0] != '\t') + *commentLineEnd = '\0'; + if (firstBackslash != NULL) + UnescapeBackslash(line, firstBackslash); return line; } static bool -ParseSkippedBranches(void) +SkipIrrelevantBranches(void) { - char *line; + const char *line; - while ((line = ParseGetLine(GLM_DOT)) != NULL) { - if (Cond_EvalLine(line) == COND_PARSE) - break; + while ((line = ReadLowLevelLine(LK_DOT)) != NULL) { + if (Cond_EvalLine(line) == CR_TRUE) + return true; /* - * TODO: Check for typos in .elif directives - * such as .elsif or .elseif. + * TODO: Check for typos in .elif directives such as .elsif + * or .elseif. * - * This check will probably duplicate some of - * the code in ParseLine. Most of the code - * there cannot apply, only ParseVarassign and - * ParseDependencyLine can, and to prevent code - * duplication, these would need to be called - * with a flag called onlyCheckSyntax. + * This check will probably duplicate some of the code in + * ParseLine. Most of the code there cannot apply, only + * ParseVarassign and ParseDependencyLine can, and to prevent + * code duplication, these would need to be called with a + * flag called onlyCheckSyntax. * * See directive-elif.mk for details. */ } - return line != NULL; + return false; } static bool ParseForLoop(const char *line) { int rval; - int firstLineno; + unsigned forHeadLineno; + unsigned bodyReadLines; + int forLevel; rval = For_Eval(line); if (rval == 0) @@ -2900,21 +2555,22 @@ ParseForLoop(const char *line) if (rval < 0) return true; /* Syntax error - error printed, ignore line */ - firstLineno = CurFile()->lineno; + forHeadLineno = CurFile()->lineno; + bodyReadLines = CurFile()->readLines; - /* Accumulate loop lines until matching .endfor */ + /* Accumulate the loop body until the matching '.endfor'. */ + forLevel = 1; do { - line = ParseGetLine(GLM_FOR_BODY); + line = ReadLowLevelLine(LK_FOR_BODY); if (line == NULL) { Parse_Error(PARSE_FATAL, "Unexpected end of file in .for loop"); break; } - } while (For_Accum(line)); + } while (For_Accum(line, &forLevel)); - For_Run(firstLineno); /* Stash each iteration as a new 'input file' */ - - return true; /* Read next line from for-loop buffer */ + For_Run(forHeadLineno, bodyReadLines); + return true; } /* @@ -2924,35 +2580,30 @@ ParseForLoop(const char *line) * leaving only variable assignments, other directives, dependency lines * and shell commands to the caller. * - * Results: - * A line without its newline and without any trailing whitespace, - * or NULL. + * Return a line without trailing whitespace, or NULL for EOF. The returned + * string will be freed at the end of including the file. */ static char * -ParseReadLine(void) +ReadHighLevelLine(void) { char *line; for (;;) { - line = ParseGetLine(GLM_NONEMPTY); + line = ReadLowLevelLine(LK_NONEMPTY); if (line == NULL) return NULL; if (line[0] != '.') return line; - /* - * The line might be a conditional. Ask the conditional module - * about it and act accordingly - */ switch (Cond_EvalLine(line)) { - case COND_SKIP: - if (!ParseSkippedBranches()) + case CR_FALSE: /* May also mean a syntax error. */ + if (!SkipIrrelevantBranches()) return NULL; continue; - case COND_PARSE: + case CR_TRUE: continue; - case COND_INVALID: /* Not a conditional line */ + case CR_ERROR: /* Not a conditional line */ if (ParseForLoop(line)) continue; break; @@ -3007,7 +2658,7 @@ ParseLine_ShellCommand(const char *p) for (ln = targets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; - ParseAddCmd(gn, cmd); + GNode_AddCommand(gn, cmd); } #ifdef CLEANUP Lst_Append(&targCmds, cmd); @@ -3033,7 +2684,7 @@ ParseDirective(char *line) } dir.start = cp; - while (ch_isalpha(*cp) || *cp == '-') + while (ch_islower(*cp) || *cp == '-') cp++; dir.end = cp; @@ -3043,47 +2694,40 @@ ParseDirective(char *line) pp_skip_whitespace(&cp); arg = cp; - if (Substring_Equals(dir, "undef")) { + if (Substring_Equals(dir, "undef")) Var_Undef(arg); - return true; - } else if (Substring_Equals(dir, "export")) { + else if (Substring_Equals(dir, "export")) Var_Export(VEM_PLAIN, arg); - return true; - } else if (Substring_Equals(dir, "export-env")) { + else if (Substring_Equals(dir, "export-env")) Var_Export(VEM_ENV, arg); - return true; - } else if (Substring_Equals(dir, "export-literal")) { + else if (Substring_Equals(dir, "export-literal")) Var_Export(VEM_LITERAL, arg); - return true; - } else if (Substring_Equals(dir, "unexport")) { + else if (Substring_Equals(dir, "unexport")) Var_UnExport(false, arg); - return true; - } else if (Substring_Equals(dir, "unexport-env")) { + else if (Substring_Equals(dir, "unexport-env")) Var_UnExport(true, arg); - return true; - } else if (Substring_Equals(dir, "info")) { - ParseMessage(PARSE_INFO, "info", arg); - return true; - } else if (Substring_Equals(dir, "warning")) { - ParseMessage(PARSE_WARNING, "warning", arg); - return true; - } else if (Substring_Equals(dir, "error")) { - ParseMessage(PARSE_FATAL, "error", arg); - return true; - } - return false; + else if (Substring_Equals(dir, "info")) + HandleMessage(PARSE_INFO, "info", arg); + else if (Substring_Equals(dir, "warning")) + HandleMessage(PARSE_WARNING, "warning", arg); + else if (Substring_Equals(dir, "error")) + HandleMessage(PARSE_FATAL, "error", arg); + else + return false; + return true; } -static bool -ParseVarassign(const char *line) +bool +Parse_VarAssign(const char *line, bool finishDependencyGroup, GNode *scope) { VarAssign var; if (!Parse_IsVar(line, &var)) return false; - - FinishDependencyGroup(); - Parse_Var(&var, SCOPE_GLOBAL); + if (finishDependencyGroup) + FinishDependencyGroup(); + Parse_Var(&var, scope); + free(var.varname); return true; } @@ -3109,7 +2753,7 @@ FindSemicolon(char *p) } /* - * dependency -> target... op [source...] [';' command] + * dependency -> [target...] op [source...] [';' command] * op -> ':' | '::' | '!' */ static void @@ -3122,7 +2766,7 @@ ParseDependencyLine(char *line) /* * For some reason - probably to make the parser impossible - * a ';' can be used to separate commands from dependencies. - * Attempt to avoid ';' inside substitution patterns. + * Attempt to skip over ';' inside substitution patterns. */ { char *semicolon = FindSemicolon(line); @@ -3150,20 +2794,22 @@ ParseDependencyLine(char *line) * in which the middle is interpreted as a source, not a target. */ - /* In lint mode, allow undefined variables to appear in - * dependency lines. + /* + * In lint mode, allow undefined variables to appear in dependency + * lines. * - * Ideally, only the right-hand side would allow undefined - * variables since it is common to have optional dependencies. - * Having undefined variables on the left-hand side is more - * unusual though. Since both sides are expanded in a single - * pass, there is not much choice what to do here. + * Ideally, only the right-hand side would allow undefined variables + * since it is common to have optional dependencies. Having undefined + * variables on the left-hand side is more unusual though. Since + * both sides are expanded in a single pass, there is not much choice + * what to do here. * - * In normal mode, it does not matter whether undefined - * variables are allowed or not since as of 2020-09-14, - * Var_Parse does not print any parse errors in such a case. - * It simply returns the special empty string var_Error, - * which cannot be detected in the result of Var_Subst. */ + * 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. + */ emode = opts.strict ? VARE_WANTRES : VARE_UNDEFERR; (void)Var_Subst(line, SCOPE_CMDLINE, emode, &expanded_line); /* TODO: handle errors */ @@ -3219,7 +2865,7 @@ ParseLine(char *line) } #endif - if (ParseVarassign(line)) + if (Parse_VarAssign(line, true, SCOPE_GLOBAL)) return; FinishDependencyGroup(); @@ -3230,30 +2876,24 @@ ParseLine(char *line) /* * Parse a top-level makefile, incorporating its content into the global * dependency graph. - * - * Input: - * name The name of the file being read - * fd The open file to parse; will be closed at the end */ void Parse_File(const char *name, int fd) { - char *line; /* the line we're working on */ - struct loadedfile *lf; + char *line; + Buffer buf; - lf = loadfile(name, fd); + buf = loadfile(name, fd != -1 ? fd : STDIN_FILENO); + if (fd != -1) + (void)close(fd); assert(targets == NULL); - if (name == NULL) - name = "(stdin)"; - - Parse_PushInput(name, 0, -1, loadedfile_readMore, lf); - CurFile()->lf = lf; + Parse_PushInput(name, 1, 0, buf, NULL); do { - while ((line = ParseReadLine()) != NULL) { - DEBUG2(PARSE, "ParseReadLine (%d): '%s'\n", + while ((line = ReadHighLevelLine()) != NULL) { + DEBUG2(PARSE, "Parsing line %u: %s\n", CurFile()->lineno, line); ParseLine(line); } @@ -3265,9 +2905,9 @@ Parse_File(const char *name, int fd) if (parseErrors != 0) { (void)fflush(stdout); (void)fprintf(stderr, - "%s: Fatal errors encountered -- cannot continue", + "%s: Fatal errors encountered -- cannot continue\n", progname); - PrintOnError(NULL, NULL); + PrintOnError(NULL, ""); exit(1); } } @@ -3280,7 +2920,7 @@ Parse_Init(void) parseIncPath = SearchPath_New(); sysIncPath = SearchPath_New(); defSysIncPath = SearchPath_New(); - Vector_Init(&includes, sizeof(IFile)); + Vector_Init(&includes, sizeof(IncludedFile)); } /* Clean up the parsing module. */ diff --git a/str.c b/str.c index 11e7595dd867..5c529c894300 100644 --- a/str.c +++ b/str.c @@ -1,4 +1,4 @@ -/* $NetBSD: str.c,v 1.86 2021/06/21 16:59:18 rillig Exp $ */ +/* $NetBSD: str.c,v 1.88 2021/12/15 10:57:01 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -71,7 +71,11 @@ #include "make.h" /* "@(#)str.c 5.8 (Berkeley) 6/1/90" */ -MAKE_RCSID("$NetBSD: str.c,v 1.86 2021/06/21 16:59:18 rillig Exp $"); +MAKE_RCSID("$NetBSD: str.c,v 1.88 2021/12/15 10:57:01 rillig Exp $"); + + +static HashTable interned_strings; + /* Return the concatenation of s1 and s2, freshly allocated. */ char * @@ -395,3 +399,24 @@ Str_Match(const char *str, const char *pat) str++; } } + +void +Str_Intern_Init(void) +{ + HashTable_Init(&interned_strings); +} + +void +Str_Intern_End(void) +{ +#ifdef CLEANUP + HashTable_Done(&interned_strings); +#endif +} + +/* Return a canonical instance of str, with unlimited lifetime. */ +const char * +Str_Intern(const char *str) +{ + return HashTable_CreateEntry(&interned_strings, str, NULL)->key; +} diff --git a/str.h b/str.h index bd56a2981785..7a4047cb27c3 100644 --- a/str.h +++ b/str.h @@ -1,4 +1,4 @@ -/* $NetBSD: str.h,v 1.12 2021/12/12 13:43:47 rillig Exp $ */ +/* $NetBSD: str.h,v 1.15 2021/12/15 10:57:01 rillig Exp $ */ /* Copyright (c) 2021 Roland Illig @@ -39,12 +39,6 @@ typedef struct FStr { void *freeIt; } FStr; -/* A modifiable string that may need to be freed after use. */ -typedef struct MFStr { - char *str; - void *freeIt; -} MFStr; - /* A read-only range of a character array, NOT null-terminated. */ typedef struct Substring { const char *start; @@ -111,40 +105,6 @@ FStr_Done(FStr *fstr) } -MAKE_INLINE MFStr -MFStr_Init(char *str, void *freeIt) -{ - MFStr mfstr; - mfstr.str = str; - mfstr.freeIt = freeIt; - return mfstr; -} - -/* Return a string that is the sole owner of str. */ -MAKE_INLINE MFStr -MFStr_InitOwn(char *str) -{ - return MFStr_Init(str, str); -} - -/* Return a string that refers to the shared str. */ -MAKE_INLINE MFStr -MFStr_InitRefer(char *str) -{ - return MFStr_Init(str, NULL); -} - -MAKE_INLINE void -MFStr_Done(MFStr *mfstr) -{ - free(mfstr->freeIt); -#ifdef CLEANUP - mfstr->str = NULL; - mfstr->freeIt = NULL; -#endif -} - - MAKE_STATIC Substring Substring_Init(const char *start, const char *end) { @@ -383,3 +343,7 @@ char *str_concat2(const char *, const char *); char *str_concat3(const char *, const char *, const char *); bool Str_Match(const char *, const char *); + +void Str_Intern_Init(void); +void Str_Intern_End(void); +const char *Str_Intern(const char *); diff --git a/suff.c b/suff.c index ab7ac6184173..430dec6bafd2 100644 --- a/suff.c +++ b/suff.c @@ -1,4 +1,4 @@ -/* $NetBSD: suff.c,v 1.357 2021/12/12 20:45:48 sjg Exp $ */ +/* $NetBSD: suff.c,v 1.364 2022/01/07 20:54:45 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -115,7 +115,7 @@ #include "dir.h" /* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ -MAKE_RCSID("$NetBSD: suff.c,v 1.357 2021/12/12 20:45:48 sjg Exp $"); +MAKE_RCSID("$NetBSD: suff.c,v 1.364 2022/01/07 20:54:45 rillig Exp $"); typedef List SuffixList; typedef ListNode SuffixListNode; @@ -207,20 +207,26 @@ typedef struct Suffix { typedef struct Candidate { /* The file or node to look for. */ char *file; - /* The prefix from which file was formed. - * Its memory is shared among all candidates. */ + /* + * The prefix from which file was formed. Its memory is shared among + * all candidates. + */ char *prefix; /* The suffix on the file. */ Suffix *suff; - /* The candidate that can be made from this, - * or NULL for the top-level candidate. */ + /* + * The candidate that can be made from this, or NULL for the + * top-level candidate. + */ struct Candidate *parent; /* The node describing the file. */ GNode *node; - /* Count of existing children, only used for memory management, so we - * don't free this candidate too early or too late. */ + /* + * Count of existing children, only used for memory management, so we + * don't free this candidate too early or too late. + */ int numChildren; #ifdef DEBUG_SRC CandidateList childrenList; @@ -240,7 +246,7 @@ typedef struct CandidateSearcher { /* TODO: Document the difference between nullSuff and emptySuff. */ -/* The NULL suffix for this run */ +/* The NULL suffix is used when a file has no known suffix */ static Suffix *nullSuff; /* The empty suffix required for POSIX single-suffix transformation rules */ static Suffix *emptySuff; @@ -692,7 +698,9 @@ RebuildGraph(GNode *transform, Suffix *suff) size_t nameLen = strlen(name); const char *toName; - /* See if it is a transformation from this suffix to another suffix. */ + /* + * See if it is a transformation from this suffix to another suffix. + */ toName = StrTrimPrefix(suff->name, name); if (toName != NULL) { Suffix *to = FindSuffixByName(toName); @@ -702,7 +710,9 @@ RebuildGraph(GNode *transform, Suffix *suff) } } - /* See if it is a transformation from another suffix to this suffix. */ + /* + * See if it is a transformation from another suffix to this suffix. + */ toName = Suffix_TrimSuffix(suff, nameLen, name + nameLen); if (toName != NULL) { Suffix *from = FindSuffixByNameLen(name, @@ -724,17 +734,15 @@ RebuildGraph(GNode *transform, Suffix *suff) * true iff a new main target has been selected. */ static bool -UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, - bool *inout_removedMain) +UpdateTarget(GNode *target, Suffix *suff, bool *inout_removedMain) { Suffix *srcSuff, *targSuff; char *ptr; - if (*inout_main == NULL && *inout_removedMain && - !(target->type & OP_NOTARGET)) { + if (mainNode == NULL && *inout_removedMain && + GNode_IsMainCandidate(target)) { DEBUG1(MAKE, "Setting main node to \"%s\"\n", target->name); - *inout_main = target; - Targ_SetMain(target); + mainNode = target; /* * XXX: Why could it be a good idea to return true here? * The main task of this function is to turn ordinary nodes @@ -772,13 +780,12 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, return false; if (ParseTransform(target->name, &srcSuff, &targSuff)) { - if (*inout_main == target) { + if (mainNode == target) { DEBUG1(MAKE, "Setting main node from \"%s\" back to null\n", target->name); *inout_removedMain = true; - *inout_main = NULL; - Targ_SetMain(NULL); + mainNode = NULL; } Lst_Done(&target->children); Lst_Init(&target->children); @@ -802,14 +809,14 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, * suffix rules. */ static void -UpdateTargets(GNode **inout_main, Suffix *suff) +UpdateTargets(Suffix *suff) { bool removedMain = false; GNodeListNode *ln; for (ln = Targ_List()->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; - if (UpdateTarget(gn, inout_main, suff, &removedMain)) + if (UpdateTarget(gn, suff, &removedMain)) break; } } @@ -828,7 +835,7 @@ UpdateTargets(GNode **inout_main, Suffix *suff) * name the name of the suffix to add */ void -Suff_AddSuffix(const char *name, GNode **inout_main) +Suff_AddSuffix(const char *name) { GNodeListNode *ln; @@ -840,7 +847,7 @@ Suff_AddSuffix(const char *name, GNode **inout_main) Lst_Append(&sufflist, suff); DEBUG1(SUFF, "Adding suffix \"%s\"\n", suff->name); - UpdateTargets(inout_main, suff); + UpdateTargets(suff); /* * Look for any existing transformations from or to this suffix. @@ -1197,7 +1204,9 @@ FindCmds(Candidate *targ, CandidateSearcher *cs) base = str_basename(sgn->name); if (strncmp(base, targ->prefix, prefLen) != 0) continue; - /* The node matches the prefix, see if it has a known suffix. */ + /* + * The node matches the prefix, see if it has a known suffix. + */ suff = FindSuffixByName(base + prefLen); if (suff == NULL) continue; @@ -1254,7 +1263,7 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn) DEBUG1(SUFF, "%s...", cp); gn = Targ_GetNode(cp); - /* Add gn to the parents child list before the original child */ + /* Insert gn before the original child. */ Lst_InsertBefore(&pgn->children, cln, gn); Lst_Append(&gn->parents, pgn); pgn->unmade++; @@ -1767,8 +1776,7 @@ FindDepsRegularPath(GNode *gn, Candidate *targ) free(gn->path); gn->path = Dir_FindFile(gn->name, - (targ == NULL ? &dirSearchPath : - targ->suff->searchPath)); + targ == NULL ? &dirSearchPath : targ->suff->searchPath); if (gn->path == NULL) return; @@ -2178,7 +2186,7 @@ Suff_PrintAll(void) } } -const char * +char * Suff_NamesStr(void) { Buffer buf; diff --git a/targ.c b/targ.c index beb038fa1a4b..52c5531845d8 100644 --- a/targ.c +++ b/targ.c @@ -1,4 +1,4 @@ -/* $NetBSD: targ.c,v 1.173 2021/11/28 19:51:06 rillig Exp $ */ +/* $NetBSD: targ.c,v 1.176 2022/01/07 20:50:35 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -78,10 +78,8 @@ * * Targ_List Return the list of all targets so far. * - * GNode_New Create a new GNode for the passed target - * (string). The node is *not* placed in the - * hash table, though all its fields are - * initialized. + * GNode_New Create a new GNode with the given name, don't add it + * to allNodes. * * Targ_FindNode Find the node, or return NULL. * @@ -93,9 +91,6 @@ * Targ_FindList Given a list of names, find nodes for all * of them, creating them as necessary. * - * Targ_Precious Return true if the target is precious and - * 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. @@ -103,8 +98,7 @@ * Debugging: * Targ_PrintGraph * Print out the entire graph, all variables and - * statistics for the directory cache. Should print - * something for suffixes, too, but... + * statistics for the directory cache. */ #include @@ -113,7 +107,7 @@ #include "dir.h" /* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: targ.c,v 1.173 2021/11/28 19:51:06 rillig Exp $"); +MAKE_RCSID("$NetBSD: targ.c,v 1.176 2022/01/07 20:50:35 rillig Exp $"); /* * All target nodes that appeared on the left-hand side of one of the @@ -346,27 +340,6 @@ Targ_FindList(GNodeList *gns, StringList *names) } } -/* See if the given target is precious. */ -bool -Targ_Precious(const GNode *gn) -{ - /* XXX: Why are '::' targets precious? */ - return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP); -} - -/* - * The main target to be made; only for debugging output. - * See mainNode in parse.c for the definitive source. - */ -static GNode *mainTarg; - -/* Remember the main target to make; only used for debugging. */ -void -Targ_SetMain(GNode *gn) -{ - mainTarg = gn; -} - static void PrintNodeNames(GNodeList *gnodes) { @@ -508,7 +481,7 @@ Targ_PrintNode(GNode *gn, int pass) return; debug_printf("#\n"); - if (gn == mainTarg) + if (gn == mainNode) debug_printf("# *** MAIN TARGET ***\n"); if (pass >= 2) { @@ -556,7 +529,6 @@ Targ_PrintNodes(GNodeList *gnodes, int pass) Targ_PrintNode(ln->datum, pass); } -/* Print only those targets that are just a source. */ static void PrintOnlySources(void) { diff --git a/trace.c b/trace.c index 8e2c507d14dc..b48f02296cdf 100644 --- a/trace.c +++ b/trace.c @@ -1,4 +1,4 @@ -/* $NetBSD: trace.c,v 1.29 2021/09/21 23:06:18 rillig Exp $ */ +/* $NetBSD: trace.c,v 1.31 2022/02/05 00:26:21 rillig Exp $ */ /* * Copyright (c) 2000 The NetBSD Foundation, Inc. @@ -48,7 +48,7 @@ #include "job.h" #include "trace.h" -MAKE_RCSID("$NetBSD: trace.c,v 1.29 2021/09/21 23:06:18 rillig Exp $"); +MAKE_RCSID("$NetBSD: trace.c,v 1.31 2022/02/05 00:26:21 rillig Exp $"); static FILE *trfile; static pid_t trpid; @@ -69,8 +69,10 @@ Trace_Init(const char *pathname) if (pathname != NULL) { FStr curDir; trpid = getpid(); - /* XXX: This variable may get overwritten later, which - * would make trwd point to undefined behavior. */ + /* + * XXX: This variable may get overwritten later, which would + * make trwd point to undefined behavior. + */ curDir = Var_Value(SCOPE_GLOBAL, ".CURDIR"); trwd = curDir.str; @@ -88,10 +90,17 @@ Trace_Log(TrEvent event, Job *job) gettimeofday(&rightnow, NULL); +#if __STDC__ >= 199901L fprintf(trfile, "%lld.%06ld %d %s %d %s", (long long)rightnow.tv_sec, (long)rightnow.tv_usec, jobTokensRunning, evname[event], trpid, trwd); +#else + fprintf(trfile, "%ld.%06ld %d %s %d %s", + (long)rightnow.tv_sec, (long)rightnow.tv_usec, + jobTokensRunning, + evname[event], trpid, trwd); +#endif if (job != NULL) { char flags[4]; diff --git a/unit-tests/Makefile b/unit-tests/Makefile index 98ed3907cd5a..2a300133eccc 100644 --- a/unit-tests/Makefile +++ b/unit-tests/Makefile @@ -1,6 +1,6 @@ -# $Id: Makefile,v 1.164 2021/12/12 22:50:00 sjg Exp $ +# $Id: Makefile,v 1.171 2022/01/28 21:33:18 sjg Exp $ # -# $NetBSD: Makefile,v 1.288 2021/12/12 22:16:48 rillig Exp $ +# $NetBSD: Makefile,v 1.302 2022/01/27 21:50:50 sjg Exp $ # # Unit tests for make(1) # @@ -91,8 +91,10 @@ TESTS+= dep-colon TESTS+= dep-colon-bug-cross-file TESTS+= dep-double-colon TESTS+= dep-double-colon-indep +TESTS+= dep-duplicate TESTS+= dep-exclam TESTS+= dep-none +TESTS+= dep-op-missing TESTS+= dep-percent TESTS+= dep-var TESTS+= dep-wildcards @@ -189,7 +191,6 @@ TESTS+= directive-warning TESTS+= dollar TESTS+= doterror TESTS+= dotwait -TESTS+= envfirst TESTS+= error TESTS+= # escape # broken by reverting POSIX changes TESTS+= export @@ -216,8 +217,6 @@ TESTS+= meta-cmd-cmp TESTS+= moderrs TESTS+= modmatch TESTS+= modmisc -TESTS+= modts -TESTS+= modword .if ${.MAKE.UID} > 0 TESTS+= objdir-writable .endif @@ -273,10 +272,12 @@ TESTS+= opt-touch-jobs TESTS+= opt-tracefile TESTS+= opt-var-expanded TESTS+= opt-var-literal +TESTS+= opt-version TESTS+= opt-warnings-as-errors TESTS+= opt-where-am-i TESTS+= opt-x-reduce-exported TESTS+= order +TESTS+= parse TESTS+= parse-var TESTS+= phony-end TESTS+= posix @@ -319,12 +320,12 @@ TESTS+= ternary TESTS+= unexport TESTS+= unexport-env TESTS+= use-inference -TESTS+= var-class -TESTS+= var-class-cmdline -TESTS+= var-class-env -TESTS+= var-class-global -TESTS+= var-class-local -TESTS+= var-class-local-legacy +TESTS+= var-scope +TESTS+= var-scope-cmdline +TESTS+= var-scope-env +TESTS+= var-scope-global +TESTS+= var-scope-local +TESTS+= var-scope-local-legacy TESTS+= var-eval-short TESTS+= var-op TESTS+= var-op-append @@ -340,6 +341,7 @@ TESTS+= varfind TESTS+= varmisc TESTS+= varmod TESTS+= varmod-assign +TESTS+= varmod-assign-shell TESTS+= varmod-defined TESTS+= varmod-edge TESTS+= varmod-exclam-shell @@ -498,7 +500,6 @@ ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2 # If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of # settings FLAGS.test=-dv here, since that is closer to the test code. FLAGS.cond-func-make= via-cmdline -FLAGS.directive-ifmake= first second FLAGS.doterror= # none, especially not -k FLAGS.jobs-error-indirect= # none, especially not -k FLAGS.jobs-error-nested= # none, especially not -k @@ -531,6 +532,7 @@ SED_CMDS.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,' \ SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1} SED_CMDS.opt-debug-graph2= ${STD_SED_CMDS.dg2} SED_CMDS.opt-debug-graph3= ${STD_SED_CMDS.dg3} +SED_CMDS.opt-debug-hash= -e 's,\(numEntries\)=[1-9][0-9],\1=,' SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(),' SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid ,' SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process ,' @@ -541,6 +543,7 @@ SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: \) -q,\1,' SED_CMDS.opt-debug-lint+= ${STD_SED_CMDS.regex} SED_CMDS.opt-jobs-no-action= ${STD_SED_CMDS.hide-from-output} SED_CMDS.opt-no-action-runflags= ${STD_SED_CMDS.hide-from-output} +SED_CMDS.opt-where-am-i= -e '/usr.obj/d' # For Compat_RunCommand, useShell == false. SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,,' # For Compat_RunCommand, useShell == true. @@ -736,6 +739,9 @@ _SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}[][0-9]* warning,make warning,' _SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,' # replace anything after 'stopped in' with unit-tests _SED_CMDS+= -e '/stopped/s, /.*, unit-tests,' +# Allow the test files to be placed anywhere. +_SED_CMDS+= -e 's,\(\.PARSEDIR}\) = `'"/[^']*'"',\1 = ,' +_SED_CMDS+= -e 's,\(\.INCLUDEDFROMDIR}\) = `'"/[^']*'"',\1 = ,' _SED_CMDS+= -e 's,${TMPDIR},TMPDIR,g' # canonicalize ${.OBJDIR} and ${.CURDIR} .if ${.OBJDIR} != ${.CURDIR} diff --git a/unit-tests/comment.mk b/unit-tests/comment.mk index d4fb041104a7..2471c8cf659d 100644 --- a/unit-tests/comment.mk +++ b/unit-tests/comment.mk @@ -1,4 +1,4 @@ -# $NetBSD: comment.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ +# $NetBSD: comment.mk,v 1.4 2022/01/23 18:00:53 rillig Exp $ # # Demonstrate how comments are written in makefiles. @@ -15,7 +15,9 @@ on and on. # Comments can be indented with spaces, but that is rather unusual. # Comments can be indented with a tab. - # These are not shell commands, they are just makefile comments. + # Since parse.c 1.127 from 2007-01-01, these are not shell commands, + # they are just makefile comments. Before that commit, these comments + # triggered the error message "Unassociated shell command". .if 1 # There can be comments after conditions. .endif # And after the closing directive. diff --git a/unit-tests/cond-func-empty.mk b/unit-tests/cond-func-empty.mk index 25c850d23d93..24cb7a680b2a 100644 --- a/unit-tests/cond-func-empty.mk +++ b/unit-tests/cond-func-empty.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-empty.mk,v 1.16 2021/12/11 10:41:31 rillig Exp $ +# $NetBSD: cond-func-empty.mk,v 1.17 2021/12/28 22:13:56 rillig Exp $ # # Tests for the empty() function in .if conditions, which tests a variable # expression for emptiness. @@ -189,5 +189,16 @@ VARNAME= ${VARNAME${:U1}} .if defined(VARNAME${:U2}) && !empty(VARNAME${:U2}) .endif -all: - @:; + +# If the word 'empty' is not followed by '(', it is not a function call but an +# ordinary bare word. This bare word is interpreted as 'defined(empty)', and +# since there is no variable named 'empty', the condition evaluates to false. +.if empty +. error +.endif + +empty= # defined but empty +.if empty +.else +. error +.endif diff --git a/unit-tests/cond-func.exp b/unit-tests/cond-func.exp index 8dc0f821a255..d0663ea68647 100644 --- a/unit-tests/cond-func.exp +++ b/unit-tests/cond-func.exp @@ -2,11 +2,11 @@ make: "cond-func.mk" line 36: Missing closing parenthesis for defined() make: "cond-func.mk" line 51: Missing closing parenthesis for defined() make: "cond-func.mk" line 54: Missing closing parenthesis for defined() make: "cond-func.mk" line 94: The empty variable is never defined. -make: "cond-func.mk" line 102: A plain function name is parsed as !empty(...). -make: "cond-func.mk" line 109: A plain function name is parsed as !empty(...). -make: "cond-func.mk" line 119: Symbols may start with a function name. -make: "cond-func.mk" line 124: Symbols may start with a function name. -make: "cond-func.mk" line 130: Missing closing parenthesis for defined() +make: "cond-func.mk" line 103: A plain function name is parsed as defined(...). +make: "cond-func.mk" line 110: A plain function name is parsed as defined(...). +make: "cond-func.mk" line 120: Symbols may start with a function name. +make: "cond-func.mk" line 125: Symbols may start with a function name. +make: "cond-func.mk" line 131: Missing closing parenthesis for defined() make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-func.mk b/unit-tests/cond-func.mk index 4ff43b72ef88..959367f5c6ab 100644 --- a/unit-tests/cond-func.mk +++ b/unit-tests/cond-func.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func.mk,v 1.9 2020/11/15 14:07:53 rillig Exp $ +# $NetBSD: cond-func.mk,v 1.11 2022/01/07 19:30:17 rillig Exp $ # # Tests for those parts of the functions in .if conditions that are common # among several functions. @@ -94,19 +94,20 @@ ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces . info The empty variable is never defined. .endif -# The plain word 'defined' is interpreted as '!empty(defined)'. +# The plain word 'defined' is interpreted as 'defined(defined)', see +# CondParser_ComparisonOrLeaf. # That variable is not defined (yet). .if defined . error .else -. info A plain function name is parsed as !empty(...). +. info A plain function name is parsed as defined(...). .endif -# If a variable named 'defined' is actually defined and not empty, the plain -# symbol 'defined' evaluates to true. -defined= non-empty +# If a variable named 'defined' is actually defined, the bare word 'defined' +# is interpreted as 'defined(defined)', and the condition evaluates to true. +defined= # defined but empty .if defined -. info A plain function name is parsed as !empty(...). +. info A plain function name is parsed as defined(...). .else . error .endif @@ -119,7 +120,7 @@ defined= non-empty . info Symbols may start with a function name. .endif -defined-var= non-empty +defined-var= # defined but empty .if defined-var . info Symbols may start with a function name. .else @@ -132,6 +133,3 @@ defined-var= non-empty .else . error .endif - -all: - @:; diff --git a/unit-tests/cond-op-parentheses.exp b/unit-tests/cond-op-parentheses.exp index b44093304100..63f7b19570b5 100644 --- a/unit-tests/cond-op-parentheses.exp +++ b/unit-tests/cond-op-parentheses.exp @@ -1,6 +1,7 @@ -make: "cond-op-parentheses.mk" line 13: Parentheses can be nested at least to depth 112. -make: "cond-op-parentheses.mk" line 19: Malformed conditional (() -make: "cond-op-parentheses.mk" line 29: Malformed conditional ()) +make: "cond-op-parentheses.mk" line 19: String comparison operator must be either == or != +make: "cond-op-parentheses.mk" line 22: Malformed conditional ((3) > 2) +make: "cond-op-parentheses.mk" line 40: Malformed conditional (() +make: "cond-op-parentheses.mk" line 53: Malformed conditional ()) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-op-parentheses.mk b/unit-tests/cond-op-parentheses.mk index ca288cad5826..b790f8bec330 100644 --- a/unit-tests/cond-op-parentheses.mk +++ b/unit-tests/cond-op-parentheses.mk @@ -1,8 +1,26 @@ -# $NetBSD: cond-op-parentheses.mk,v 1.4 2021/01/19 17:49:13 rillig Exp $ +# $NetBSD: cond-op-parentheses.mk,v 1.5 2022/01/22 21:50:41 rillig Exp $ # -# Tests for parentheses in .if conditions. +# Tests for parentheses in .if conditions, which group expressions to override +# the precedence of the operators '!', '&&' and '||'. Parentheses cannot be +# used to form arithmetic expressions such as '(3+4)' though. -# TODO: Implementation +# Contrary to the C family of programming languages, the outermost condition +# does not have to be enclosed in parentheses. +.if defined(VAR) +. error +.elif !1 +. error +.endif + +# Parentheses cannot enclose numbers as there is no need for it. Make does +# not implement any arithmetic functions in its condition parser. If +# absolutely necessary, use expr(1). +# expect+1: String comparison operator must be either == or != +.if 3 > (2) +.endif +# expect+1: Malformed conditional ((3) > 2) +.if (3) > 2 +.endif # Test for deeply nested conditions. .if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \ @@ -10,7 +28,10 @@ 1 \ )))))))))))))))))))))))))))))))))))))))))))))))))))))))) \ )))))))))))))))))))))))))))))))))))))))))))))))))))))))) -. info Parentheses can be nested at least to depth 112. +# Parentheses can be nested at least to depth 112. There is nothing special +# about this number though, much higher numbers work as well, at least on +# NetBSD. The actual limit depends on the allowed call stack depth for C code +# of the platform. Anyway, 112 should be enough for all practical purposes. .else . error .endif @@ -24,8 +45,11 @@ # An unbalanced closing parenthesis is a parse error. # -# As of 2021-01-19, CondParser_Term returned TOK_RPAREN even though this -# function promised to only ever return TOK_TRUE, TOK_FALSE or TOK_ERROR. +# Before cond.c 1.237 from 2021-01-19, CondParser_Term returned TOK_RPAREN +# even though the documentation of that function promised to only ever return +# TOK_TRUE, TOK_FALSE or TOK_ERROR. In cond.c 1.241, the return type of that +# function was changed to a properly restricted enum type, to prevent this bug +# from occurring again. .if ) . error .else diff --git a/unit-tests/cond-short.mk b/unit-tests/cond-short.mk index d41c38488fd6..f4e8f87043b5 100644 --- a/unit-tests/cond-short.mk +++ b/unit-tests/cond-short.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-short.mk,v 1.18 2021/12/12 09:49:09 rillig Exp $ +# $NetBSD: cond-short.mk,v 1.19 2021/12/27 18:54:19 rillig Exp $ # # Demonstrates that in conditions, the right-hand side of an && or || # is only evaluated if it can actually influence the result. @@ -14,14 +14,14 @@ # relevant variable expressions was that in the irrelevant variable # expressions, undefined variables were allowed. This allowed for conditions # like 'defined(VAR) && ${VAR:S,from,to,} != ""', which no longer produced an -# error message 'Malformed conditional', but it still evaluated the -# expression, even though the expression was irrelevant. +# error message 'Malformed conditional', but the irrelevant expression was +# still evaluated. # # Since the initial commit on 1993-03-21, the manual page has been saying that # make 'will only evaluate a conditional as far as is necessary to determine', # but that was wrong. The code in cond.c 1.1 from 1993-03-21 looks good since # it calls Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree), but the -# definition of Var_Parse does not call the third parameter 'doEval', as would +# definition of Var_Parse did not call the third parameter 'doEval', as would # be expected, but instead 'err', accompanied by the comment 'TRUE if # undefined variables are an error'. This subtle difference between 'do not # evaluate at all' and 'allow undefined variables' led to the unexpected @@ -122,7 +122,9 @@ VAR= # empty again, for the following tests .if 0 || empty(${echo "expected or empty" 1>&2 :L:sh}) .endif -# Unreachable nested conditions are skipped completely as well. +# Unreachable nested conditions are skipped completely as well. These skipped +# lines may even contain syntax errors. This allows to skip syntactically +# incompatible new features in older versions of make. .if 0 . if ${echo "unexpected nested and" 1>&2 :L:sh} diff --git a/unit-tests/cond-token-number.exp b/unit-tests/cond-token-number.exp index b5bfaf95d575..f078cb007323 100644 --- a/unit-tests/cond-token-number.exp +++ b/unit-tests/cond-token-number.exp @@ -2,7 +2,7 @@ make: "cond-token-number.mk" line 15: Malformed conditional (-0) make: "cond-token-number.mk" line 25: Malformed conditional (+0) make: "cond-token-number.mk" line 35: Malformed conditional (!-1) make: "cond-token-number.mk" line 45: Malformed conditional (!+1) -make: "cond-token-number.mk" line 80: End of the tests. +make: "cond-token-number.mk" line 89: 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 93e2646a60eb..eef528f4b7c6 100644 --- a/unit-tests/cond-token-number.mk +++ b/unit-tests/cond-token-number.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-token-number.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $ +# $NetBSD: cond-token-number.mk,v 1.7 2022/01/02 02:57:39 rillig Exp $ # # Tests for number tokens in .if conditions. # @@ -69,13 +69,22 @@ . error .endif -# This is not a hexadecimal number, even though it has an x. -# It is interpreted as a string instead, effectively meaning defined(3x4). +# This is not a hexadecimal number, even though it has an x. It is +# interpreted as a string instead. In a plain '.if', such a token evaluates +# to true if it is non-empty. In other '.if' directives, such a token is +# evaluated by either FuncDefined or FuncMake. .if 3x4 .else . error .endif +# Make can do radix conversion from hex. +HEX= dead +.if 0x${HEX} == 57005 +.else +. error +.endif + # Ensure that parsing continues until here. .info End of the tests. diff --git a/unit-tests/cond-token-plain.exp b/unit-tests/cond-token-plain.exp index 8afde2d41788..b39e952bf3d0 100644 --- a/unit-tests/cond-token-plain.exp +++ b/unit-tests/cond-token-plain.exp @@ -27,7 +27,7 @@ lhs = "var&&name", rhs = "var&&name", op = != CondParser_Eval: ${:Uvar}||name != "var||name" lhs = "var||name", rhs = "var||name", op = != CondParser_Eval: bare -make: "cond-token-plain.mk" line 106: A bare word is treated like defined(...), and the variable 'bare' is not defined. +make: "cond-token-plain.mk" line 105: A bare word is treated like defined(...), and the variable 'bare' is not defined. CondParser_Eval: VAR make: "cond-token-plain.mk" line 111: A bare word is treated like defined(...). CondParser_Eval: V${:UA}R @@ -56,6 +56,7 @@ CondParser_Eval: 0 make: "cond-token-plain.mk" line 201: Malformed conditional (${0:?:} || left == right) CondParser_Eval: left == right || ${0:?:} make: "cond-token-plain.mk" line 206: Malformed conditional (left == right || ${0:?:}) +make: "cond-token-plain.mk" line 225: Malformed conditional (VAR.${IF_COUNT::+=1} != "") make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-token-plain.mk b/unit-tests/cond-token-plain.mk index 3e59f48bc3c7..1e9f30be9153 100644 --- a/unit-tests/cond-token-plain.mk +++ b/unit-tests/cond-token-plain.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-token-plain.mk,v 1.14 2021/12/12 09:36:00 rillig Exp $ +# $NetBSD: cond-token-plain.mk,v 1.15 2021/12/30 02:14:55 rillig Exp $ # # Tests for plain tokens (that is, string literals without quotes) # in .if conditions. These are also called bare words. @@ -209,5 +209,48 @@ ${:U\\\\}= backslash # See cond-token-string.mk for similar tests where the condition is enclosed # in "quotes". -all: - @:; +.MAKEFLAGS: -d0 + + +# As of cond.c 1.320 from 2021-12-30, the code in CondParser_ComparisonOrLeaf +# looks suspicious of evaluating the expression twice: first for parsing a +# bare word and second for parsing the left-hand side of a comparison. +# +# In '.if' directives, the left-hand side of a comparison must not be a bare +# word though, and this keeps CondParser_Leaf from evaluating the expression +# for the second time. The right-hand side of a comparison may be a bare +# word, but that side has no risk of being parsed more than once. +# +# expect+1: Malformed conditional (VAR.${IF_COUNT::+=1} != "") +.if VAR.${IF_COUNT::+=1} != "" +. error +.else +. error +.endif +.if ${IF_COUNT} != "1" +. error +.endif + +# A different situation is when CondParser.leftUnquotedOK is true. This +# situation arises in expressions of the form ${cond:?yes:no}. As of +# 2021-12-30, the condition in such an expression is evaluated before parsing +# the condition, see varmod-ifelse.mk. To pass a variable expression to the +# condition parser, it needs to be escaped. This rarely happens in practice, +# in most cases the conditions are simple enough that it doesn't matter +# whether the condition is first evaluated and then parsed, or vice versa. +# A half-baked attempt at hiding this implementation detail is +# CondParser.leftUnquotedOK, but that is a rather leaky abstraction. + +#.MAKEFLAGS: -dcv +COND= VAR.$${MOD_COUNT::+=1} +.if ${${COND} == "VAR.":?yes:no} != "yes" +. error +.endif + +# The value "1 1" demonstrates that the expression ${MOD_COUNT::+=1} was +# evaluated twice. In practice, expressions that occur in conditions do not +# have side effects, making this problem rather academic, but it is there. +.if ${MOD_COUNT} != "1 1" +. error +.endif +#.MAKEFLAGS: -d0 diff --git a/unit-tests/cond-token-string.exp b/unit-tests/cond-token-string.exp index 45f9993457d3..1c0f086b464e 100644 --- a/unit-tests/cond-token-string.exp +++ b/unit-tests/cond-token-string.exp @@ -6,9 +6,9 @@ make: "cond-token-string.mk" line 37: Expected. CondParser_Eval: "UNDEF" make: "cond-token-string.mk" line 46: The string literal "UNDEF" is not empty. CondParser_Eval: " " -make: "cond-token-string.mk" line 55: The string literal " " is not empty, even though it consists of whitespace only. +make: "cond-token-string.mk" line 54: The string literal " " is not empty, even though it consists of whitespace only. CondParser_Eval: "${UNDEF}" -make: "cond-token-string.mk" line 64: An undefined variable in quotes expands to an empty string, which then evaluates to false. +make: "cond-token-string.mk" line 63: An undefined variable in quotes expands to an empty string, which then evaluates to false. CondParser_Eval: "${:Uvalue}" make: "cond-token-string.mk" line 68: A nonempty variable expression evaluates to true. CondParser_Eval: "${:U}" diff --git a/unit-tests/dep-duplicate.exp b/unit-tests/dep-duplicate.exp new file mode 100644 index 000000000000..039145f8fd97 --- /dev/null +++ b/unit-tests/dep-duplicate.exp @@ -0,0 +1,4 @@ +make: "dep-duplicate.inc" line 1: warning: duplicate script for target "all" ignored +make: "dep-duplicate.main" line 3: warning: using previous script for "all" defined here +main-output +exit status 0 diff --git a/unit-tests/dep-duplicate.mk b/unit-tests/dep-duplicate.mk new file mode 100644 index 000000000000..6f64ba1c1981 --- /dev/null +++ b/unit-tests/dep-duplicate.mk @@ -0,0 +1,27 @@ +# $NetBSD: dep-duplicate.mk,v 1.3 2022/01/20 19:24:53 rillig Exp $ +# +# Test for a target whose commands are defined twice. This generates a +# warning, not an error, so ensure that the correct commands are kept. +# +# Also ensure that the diagnostics mention the correct file in case of +# included files. Since parse.c 1.231 from 2018-12-22 and before parse.c +# 1.653 from 2022-01-20, the wrong filename had been printed if the file of +# the first commands section was included by its relative path. + +all: .PHONY + @exec > dep-duplicate.main; \ + echo '# empty line 1'; \ + echo '# empty line 2'; \ + echo 'all:; @echo main-output'; \ + echo '.include "dep-duplicate.inc"' + + @exec > dep-duplicate.inc; \ + echo 'all:; @echo inc-output' + + # The main file must be specified using a relative path, just like the + # default 'makefile' or 'Makefile', to produce the same result when + # run via ATF or 'make test'. + @${MAKE} -r -f dep-duplicate.main + + @rm -f dep-duplicate.main + @rm -f dep-duplicate.inc diff --git a/unit-tests/dep-op-missing.exp b/unit-tests/dep-op-missing.exp new file mode 100644 index 000000000000..58b9be353eaa --- /dev/null +++ b/unit-tests/dep-op-missing.exp @@ -0,0 +1,4 @@ +make: "dep-op-missing.tmp" line 1: Invalid line type +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 0 diff --git a/unit-tests/dep-op-missing.mk b/unit-tests/dep-op-missing.mk new file mode 100644 index 000000000000..2079f3c19871 --- /dev/null +++ b/unit-tests/dep-op-missing.mk @@ -0,0 +1,13 @@ +# $NetBSD: dep-op-missing.mk,v 1.1 2021/12/14 00:02:57 rillig Exp $ +# +# Test for a missing dependency operator, in a line with trailing whitespace. + +# Before parse.c 1.578 from 2021-12-14, there was some unreachable error +# handling code in ParseDependencyOp. This test tried to reach it and failed. +# To reach that point, there would have to be trailing whitespace in the line, +# but that is removed in an early stage of parsing. + +all: .PHONY + @printf 'target ' > dep-op-missing.tmp + @${MAKE} -r -f dep-op-missing.tmp || exit 0 + @rm dep-op-missing.tmp diff --git a/unit-tests/dep-wildcards.exp b/unit-tests/dep-wildcards.exp index fb8a44e2c80a..45eafb5d2693 100644 --- a/unit-tests/dep-wildcards.exp +++ b/unit-tests/dep-wildcards.exp @@ -2,8 +2,10 @@ dep-colon-bug-cross-file.mk dep-colon.mk dep-double-colon-indep.mk dep-double-colon.mk +dep-duplicate.mk dep-exclam.mk dep-none.mk +dep-op-missing.mk dep-percent.mk dep-var.mk dep-wildcards.mk diff --git a/unit-tests/dep.exp b/unit-tests/dep.exp index 39a9383953dd..6b7f0fabb12b 100644 --- a/unit-tests/dep.exp +++ b/unit-tests/dep.exp @@ -1 +1,5 @@ -exit status 0 +make: "dep.mk" line 11: Inconsistent operator for only-colon +make: "dep.mk" line 13: Inconsistent operator for only-colon +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/dep.mk b/unit-tests/dep.mk index b2463dfc6458..54566d43d2a1 100644 --- a/unit-tests/dep.mk +++ b/unit-tests/dep.mk @@ -1,8 +1,18 @@ -# $NetBSD: dep.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: dep.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $ # # Tests for dependency declarations, such as "target: sources". -# TODO: Implementation +.MAIN: all + +# As soon as a target is defined using one of the dependency operators, it is +# restricted to this dependency operator and cannot use the others anymore. +only-colon: +# expect+1: Inconsistent operator for only-colon +only-colon! +# expect+1: Inconsistent operator for only-colon +only-colon:: +# Ensure that the target still has the original operator. If it hadn't, there +# would be another error message. +only-colon: all: - @:; diff --git a/unit-tests/depsrc-meta.exp b/unit-tests/depsrc-meta.exp index 77e27582f7da..6f17dcf0ba8a 100644 --- a/unit-tests/depsrc-meta.exp +++ b/unit-tests/depsrc-meta.exp @@ -2,4 +2,6 @@ Skipping meta for actual-test: no commands Skipping meta for .END: .SPECIAL Targets from meta mode: | TARGET depsrc-meta-target +Targets from meta mode in jobs mode: +| TARGET depsrc-meta-target exit status 0 diff --git a/unit-tests/depsrc-meta.mk b/unit-tests/depsrc-meta.mk index d41aad9a9c96..2c7d63a5f2a2 100644 --- a/unit-tests/depsrc-meta.mk +++ b/unit-tests/depsrc-meta.mk @@ -1,31 +1,30 @@ -# $NetBSD: depsrc-meta.mk,v 1.4 2020/11/27 08:39:07 rillig Exp $ +# $NetBSD: depsrc-meta.mk,v 1.6 2022/01/26 22:47:03 rillig Exp $ # # Tests for the special source .META in dependency declarations. # TODO: Implementation # TODO: Explanation -.if make(actual-test) +.MAIN: all +.if make(actual-test) .MAKEFLAGS: -dM .MAKE.MODE= meta curDirOk=true +.endif actual-test: depsrc-meta-target depsrc-meta-target: .META @> ${.TARGET}-file @rm -f ${.TARGET}-file -.elif make(check-results) - check-results: - @echo 'Targets from meta mode:' + @echo 'Targets from meta mode${.MAKE.JOBS:D in jobs mode}:' @awk '/^TARGET/ { print "| " $$0 }' depsrc-meta-target.meta @rm depsrc-meta-target.meta -.else - all: - @${MAKE} -f ${MAKEFILE} actual-test - @${MAKE} -f ${MAKEFILE} check-results + @${MAKE} -r -f ${MAKEFILE} actual-test + @${MAKE} -r -f ${MAKEFILE} check-results -.endif + @${MAKE} -r -f ${MAKEFILE} actual-test -j1 + @${MAKE} -r -f ${MAKEFILE} check-results -j1 diff --git a/unit-tests/depsrc-use.mk b/unit-tests/depsrc-use.mk index 17836cd39e23..3f73a5f04ad9 100644 --- a/unit-tests/depsrc-use.mk +++ b/unit-tests/depsrc-use.mk @@ -1,8 +1,13 @@ -# $NetBSD: depsrc-use.mk,v 1.4 2020/08/22 12:30:57 rillig Exp $ +# $NetBSD: depsrc-use.mk,v 1.5 2021/12/28 14:22:51 rillig Exp $ # # Tests for the special source .USE in dependency declarations, # which allows to append common commands to other targets. +# Before make.h 1.280 from 2021-12-28, a .USEBEFORE target was accidentally +# regarded as a candidate for the main target. On the other hand, a .USE +# target was not. +not-a-main-candidate: .USE + all: action directly first: .USE diff --git a/unit-tests/depsrc-usebefore.mk b/unit-tests/depsrc-usebefore.mk index 001cfb0d71c1..58b3145e4f3f 100644 --- a/unit-tests/depsrc-usebefore.mk +++ b/unit-tests/depsrc-usebefore.mk @@ -1,4 +1,4 @@ -# $NetBSD: depsrc-usebefore.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: depsrc-usebefore.mk,v 1.7 2021/12/28 14:22:51 rillig Exp $ # # Tests for the special source .USEBEFORE in dependency declarations, # which allows to prepend common commands to other targets. @@ -7,6 +7,11 @@ # .USE # depsrc-use.mk +# Before make.h 1.280 from 2021-12-28, a .USEBEFORE target was accidentally +# regarded as a candidate for the main target. On the other hand, a .USE +# target was not. +not-a-main-candidate: .USEBEFORE + all: action directly first: .USEBEFORE diff --git a/unit-tests/depsrc.exp b/unit-tests/depsrc.exp index 06165e6f9ac4..147ea8b24371 100644 --- a/unit-tests/depsrc.exp +++ b/unit-tests/depsrc.exp @@ -1,4 +1,5 @@ : 'Undefined variables are expanded directly in the dependency' : 'declaration. They are not preserved and maybe expanded later.' : 'This is in contrast to local variables such as ${.TARGET}.' +: Making .UNKNOWN from nothing. exit status 0 diff --git a/unit-tests/depsrc.mk b/unit-tests/depsrc.mk index ab9d04c1d3a4..4e5752c97184 100644 --- a/unit-tests/depsrc.mk +++ b/unit-tests/depsrc.mk @@ -1,7 +1,7 @@ -# $NetBSD: depsrc.mk,v 1.4 2020/12/22 19:38:44 rillig Exp $ +# $NetBSD: depsrc.mk,v 1.5 2021/12/13 23:38:54 rillig Exp $ # # Tests for special sources (those starting with a dot, followed by -# uppercase letters) in dependency declarations, such as .PHONY. +# uppercase letters) in dependency declarations, such as '.PHONY'. # TODO: Implementation @@ -14,13 +14,19 @@ target: .PHONY source-${DEFINED_LATER} DEFINED_LATER= later # source-: .PHONY + # This section applies. : 'Undefined variables are expanded directly in the dependency' : 'declaration. They are not preserved and maybe expanded later.' : 'This is in contrast to local variables such as $${.TARGET}.' source-later: .PHONY + # This section doesn't apply. : 'Undefined variables are tried to be expanded in a dependency' : 'declaration. If that fails because the variable is undefined,' : 'the expression is preserved and tried to be expanded later.' -all: - @:; +# Sources that look like keywords but are not known are interpreted as +# ordinary sources. +target: .UNKNOWN + +.UNKNOWN: + : Making ${.TARGET} from ${.ALLSRC:S,^$,nothing,W}. diff --git a/unit-tests/deptgt-error.exp b/unit-tests/deptgt-error.exp index 39a9383953dd..48e2f90954cf 100644 --- a/unit-tests/deptgt-error.exp +++ b/unit-tests/deptgt-error.exp @@ -1 +1,9 @@ -exit status 0 +false fails +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +ERROR_INFO='This information is printed on 'errors'.' +Making sub-error as prerequisite. +Making .ERROR out of nothing. +exit status 1 diff --git a/unit-tests/deptgt-error.mk b/unit-tests/deptgt-error.mk index 5d515b95afc3..67f94e6999f7 100644 --- a/unit-tests/deptgt-error.mk +++ b/unit-tests/deptgt-error.mk @@ -1,9 +1,21 @@ -# $NetBSD: deptgt-error.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: deptgt-error.mk,v 1.4 2022/01/22 21:50:41 rillig Exp $ # # Tests for the special target .ERROR in dependency declarations, which -# collects commands that are run when another target fails. +# is made when another target fails. -# TODO: Implementation +all: .PHONY + false fails -all: - @:; +.ERROR: + @echo 'Making ${.TARGET} out of nothing.' + +.ERROR: sub-error +sub-error: .PHONY + @echo 'Making ${.TARGET} as prerequisite.' + +# Before making the '.ERROR' target, these variable values are printed. +MAKE_PRINT_VAR_ON_ERROR= ERROR_INFO + +# Use single quotes to demonstrate that the output is only informational, it +# does not use any established escaping mechanism. +ERROR_INFO= This information is ${:Uprinted} on 'errors'. diff --git a/unit-tests/deptgt-ignore.exp b/unit-tests/deptgt-ignore.exp index 39a9383953dd..2aa1311c8ff7 100644 --- a/unit-tests/deptgt-ignore.exp +++ b/unit-tests/deptgt-ignore.exp @@ -1 +1,11 @@ -exit status 0 +error-failed before +*** Error code 1 (continuing) +error-ignored before +*** Error code 1 (ignored) +error-ignored after +Making depends-on-ignored from error-ignored. +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/deptgt-ignore.mk b/unit-tests/deptgt-ignore.mk index 49c14d2cfd43..a0191847e69f 100644 --- a/unit-tests/deptgt-ignore.mk +++ b/unit-tests/deptgt-ignore.mk @@ -1,9 +1,31 @@ -# $NetBSD: deptgt-ignore.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: deptgt-ignore.mk,v 1.4 2022/01/22 21:50:41 rillig Exp $ # # Tests for the special target .IGNORE in dependency declarations, which # does not stop if a command from this target exits with a non-zero status. +# +# This test only applies to compatibility mode. In jobs mode such as with +# '-j1', all commands for a single target are bundled into a single shell +# program, which is a different implementation technique, the .IGNORE applies +# there as well. -# TODO: Implementation +.MAKEFLAGS: -d0 # force stdout to be unbuffered -all: - @:; +all: depends-on-failed depends-on-ignored +.PHONY: all depends-on-failed depends-on-ignored error-failed error-ignored + +error-failed error-ignored: + @echo '${.TARGET} before' + @false + @echo '${.TARGET} after' + +depends-on-failed: error-failed + @echo 'Making ${.TARGET} from ${.ALLSRC}.' +depends-on-ignored: error-ignored + @echo 'Making ${.TARGET} from ${.ALLSRC}.' + +# Even though the command 'false' in the middle fails, the remaining commands +# are still run. After that, the target is marked made, so targets depending +# on the target with the ignored commands are made. +.IGNORE: error-ignored + +#.MAKEFLAGS: -dg2 diff --git a/unit-tests/deptgt-interrupt.exp b/unit-tests/deptgt-interrupt.exp index 39a9383953dd..e59bdc7d7c41 100644 --- a/unit-tests/deptgt-interrupt.exp +++ b/unit-tests/deptgt-interrupt.exp @@ -1 +1,2 @@ -exit status 0 +Ctrl-C +exit status 130 diff --git a/unit-tests/deptgt-interrupt.mk b/unit-tests/deptgt-interrupt.mk index d94009a52e05..9a3b4d9e81c9 100644 --- a/unit-tests/deptgt-interrupt.mk +++ b/unit-tests/deptgt-interrupt.mk @@ -1,10 +1,11 @@ -# $NetBSD: deptgt-interrupt.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: deptgt-interrupt.mk,v 1.4 2022/01/22 21:50:41 rillig Exp $ # # Tests for the special target .INTERRUPT in dependency declarations, which # collects commands to be run when make is interrupted while building another # target. -# TODO: Implementation - all: - @:; + @kill -INT ${.MAKE.PID} + +.INTERRUPT: + @echo 'Ctrl-C' diff --git a/unit-tests/deptgt-main.exp b/unit-tests/deptgt-main.exp index 39a9383953dd..40c9a2c3cb8f 100644 --- a/unit-tests/deptgt-main.exp +++ b/unit-tests/deptgt-main.exp @@ -1 +1,2 @@ +This target real-main is the one that is made. exit status 0 diff --git a/unit-tests/deptgt-main.mk b/unit-tests/deptgt-main.mk index 84d05dc25ed6..184b6f3f73bb 100644 --- a/unit-tests/deptgt-main.mk +++ b/unit-tests/deptgt-main.mk @@ -1,10 +1,29 @@ -# $NetBSD: deptgt-main.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: deptgt-main.mk,v 1.4 2022/01/23 21:48:59 rillig Exp $ # # Tests for the special target .MAIN in dependency declarations, which defines # the main target. This main target is built if no target has been specified # on the command line or via MAKEFLAGS. -# TODO: Implementation +# The first target becomes the main target by default. It can be overridden +# though. +all: .PHONY + @echo 'This target is not made.' -all: - @:; +# This target is not the first to be defined, but it lists '.MAIN' as one of +# its sources. The word '.MAIN' only has a special meaning when it appears as +# a _target_ in a dependency declaration, not as a _source_. It is thus +# ignored. +depsrc-main: .PHONY .MAIN + @echo 'This target is not made either.' + +# This target is the first to be marked with '.MAIN', so it replaces the +# previous main target, which was 'all'. +.MAIN: real-main +real-main: .PHONY + @echo 'This target ${.TARGET} is the one that is made.' + +# This target is marked with '.MAIN' but there already is a main target. The +# attribute '.MAIN' is thus ignored. +.MAIN: too-late +too-late: .PHONY + @echo 'This target comes too late, there is already a .MAIN target.' diff --git a/unit-tests/deptgt-notparallel.exp b/unit-tests/deptgt-notparallel.exp index 39a9383953dd..1e4d8ad7befb 100644 --- a/unit-tests/deptgt-notparallel.exp +++ b/unit-tests/deptgt-notparallel.exp @@ -1 +1,9 @@ +: Making 1 out of nothing. +: Making 2 out of nothing. +: Making 3 out of nothing. +: Making 4 out of nothing. +: Making 5 out of nothing. +: Making 6 out of nothing. +: Making 7 out of nothing. +: Making 8 out of nothing. exit status 0 diff --git a/unit-tests/deptgt-notparallel.mk b/unit-tests/deptgt-notparallel.mk index db08aa9d3558..8d32ed8f2461 100644 --- a/unit-tests/deptgt-notparallel.mk +++ b/unit-tests/deptgt-notparallel.mk @@ -1,8 +1,16 @@ -# $NetBSD: deptgt-notparallel.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-notparallel.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $ # -# Tests for the special target .NOTPARALLEL in dependency declarations. +# Tests for the special target .NOTPARALLEL in dependency declarations, which +# prevents the job module from doing anything in parallel, by setting the +# maximum jobs to 1. This only applies to the current make, it is not +# exported to submakes. -# TODO: Implementation +.MAKEFLAGS: -j4 -all: - @:; +# Set opts.maxJobs back to 1. Without this line, the output would be in +# random order, interleaved with separators like '--- 1 ---'. +.NOTPARALLEL: + +all: 1 2 3 4 5 6 7 8 +1 2 3 4 5 6 7 8: .PHONY + : Making ${.TARGET} out of nothing. diff --git a/unit-tests/deptgt-order.exp b/unit-tests/deptgt-order.exp index 5f7dde0ac69d..ecbf03fcc572 100644 --- a/unit-tests/deptgt-order.exp +++ b/unit-tests/deptgt-order.exp @@ -1,3 +1,10 @@ +Parsing line 15: .ORDER: three one +ParseDependency(.ORDER: three one) +# .ORDER forces 'three' to be made before 'one' +# three, unmade, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS, flags none +# one, unmade, type OP_DEPENDS|OP_PHONY, flags none +Parsing line 16: .MAKEFLAGS: -d0 +ParseDependency(.MAKEFLAGS: -d0) : 'Making two out of one.' : 'Making three out of two.' : 'Making all out of three.' diff --git a/unit-tests/deptgt-order.mk b/unit-tests/deptgt-order.mk index f241331ae1e1..88f5958425dd 100644 --- a/unit-tests/deptgt-order.mk +++ b/unit-tests/deptgt-order.mk @@ -1,4 +1,4 @@ -# $NetBSD: deptgt-order.mk,v 1.3 2021/06/17 15:25:33 rillig Exp $ +# $NetBSD: deptgt-order.mk,v 1.4 2021/12/13 23:38:54 rillig Exp $ # # Tests for the special target .ORDER in dependency declarations. @@ -11,7 +11,9 @@ three: two # This .ORDER creates a circular dependency since 'three' depends on 'one' # but 'one' is supposed to be built after 'three'. +.MAKEFLAGS: -dp .ORDER: three one +.MAKEFLAGS: -d0 # XXX: The circular dependency should be detected here. all: three diff --git a/unit-tests/deptgt-path-suffix.exp b/unit-tests/deptgt-path-suffix.exp index 39a9383953dd..228a29851f48 100644 --- a/unit-tests/deptgt-path-suffix.exp +++ b/unit-tests/deptgt-path-suffix.exp @@ -1 +1,4 @@ -exit status 0 +make: "deptgt-path-suffix.mk" line 8: Suffix '.c' not defined (yet) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/deptgt-path-suffix.mk b/unit-tests/deptgt-path-suffix.mk index 3a7e697bc748..494a076a5520 100644 --- a/unit-tests/deptgt-path-suffix.mk +++ b/unit-tests/deptgt-path-suffix.mk @@ -1,8 +1,16 @@ -# $NetBSD: deptgt-path-suffix.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-path-suffix.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $ # # Tests for the special target .PATH.suffix in dependency declarations. # TODO: Implementation +# expect+1: Suffix '.c' not defined (yet) +.PATH.c: .. + +.SUFFIXES: .c + +# Now the suffix is defined, and the path is recorded. +.PATH.c: .. + all: @:; diff --git a/unit-tests/deptgt.exp b/unit-tests/deptgt.exp index bdac2aee3e6c..0a27f562293d 100644 --- a/unit-tests/deptgt.exp +++ b/unit-tests/deptgt.exp @@ -1,14 +1,17 @@ make: "deptgt.mk" line 10: warning: Extra target ignored make: "deptgt.mk" line 28: Unassociated shell command ": command3 # parse error, since targets == NULL" -ParseReadLine (34): '${:U}: empty-source' +Parsing line 34: ${:U}: empty-source ParseDependency(: empty-source) -ParseReadLine (35): ' : command for empty targets list' -ParseReadLine (36): ': empty-source' +Parsing line 35: : command for empty targets list +Parsing line 36: : empty-source ParseDependency(: empty-source) -ParseReadLine (37): ' : command for empty targets list' -ParseReadLine (38): '.MAKEFLAGS: -d0' +Parsing line 37: : command for empty targets list +Parsing line 38: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) make: "deptgt.mk" line 46: Unknown modifier "Z" +make: "deptgt.mk" line 49: warning: Extra target ignored +make: "deptgt.mk" line 52: warning: Extra target (ordinary) ignored +make: "deptgt.mk" line 55: warning: Special and mundane targets don't mix. Mundane ones ignored 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 15d7e59aeced..044644dcbd66 100644 --- a/unit-tests/deptgt.mk +++ b/unit-tests/deptgt.mk @@ -1,4 +1,4 @@ -# $NetBSD: deptgt.mk,v 1.11 2021/04/04 10:13:09 rillig Exp $ +# $NetBSD: deptgt.mk,v 1.12 2021/12/13 23:38:54 rillig Exp $ # # Tests for special targets like .BEGIN or .SUFFIXES in dependency # declarations. @@ -45,5 +45,14 @@ ${:U}: empty-source # that nobody uses it. $$$$$$$${:U:Z}: +# expect+1: warning: Extra target ignored +.END ordinary: + +# expect+1: warning: Extra target (ordinary) ignored +.PATH ordinary: + +# expect+1: Special and mundane targets don't mix. Mundane ones ignored +ordinary .PATH: + all: @:; diff --git a/unit-tests/directive-dinclude.exp b/unit-tests/directive-dinclude.exp index 39a9383953dd..5ea0dabb3c7e 100755 --- a/unit-tests/directive-dinclude.exp +++ b/unit-tests/directive-dinclude.exp @@ -1 +1,4 @@ -exit status 0 +make: "directive-dinclude-error.inc" line 1: Invalid line type +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-dinclude.mk b/unit-tests/directive-dinclude.mk index 94fa5fb4429b..d968924a6a99 100755 --- a/unit-tests/directive-dinclude.mk +++ b/unit-tests/directive-dinclude.mk @@ -1,9 +1,24 @@ -# $NetBSD: directive-dinclude.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $ +# $NetBSD: directive-dinclude.mk,v 1.2 2022/01/23 21:48:59 rillig Exp $ # # Tests for the .dinclude directive, which includes another file, -# typically named .depend. +# silently skipping it if it cannot be opened. This is primarily used for +# including '.depend' files, that's where the 'd' comes from. +# +# The 'silently skipping' only applies to the case where the file cannot be +# opened. Parse errors and other errors are handled the same way as in the +# other .include directives. -# TODO: Implementation +# No complaint that there is no such file. +.dinclude "${.CURDIR}/directive-dinclude-nonexistent.inc" -all: - @:; +# No complaint either, even though the operating system error is ENOTDIR, not +# ENOENT. +.dinclude "${MAKEFILE}/subdir" + +# Errors that are not related to opening the file are still reported. +# expect: make: "directive-dinclude-error.inc" line 1: Invalid line type +_!= echo 'syntax error' > directive-dinclude-error.inc +.dinclude "${.CURDIR}/directive-dinclude-error.inc" +_!= rm directive-dinclude-error.inc + +all: .PHONY diff --git a/unit-tests/directive-elifdef.mk b/unit-tests/directive-elifdef.mk index f960c1513e8e..6a9925a67376 100644 --- a/unit-tests/directive-elifdef.mk +++ b/unit-tests/directive-elifdef.mk @@ -1,8 +1,21 @@ -# $NetBSD: directive-elifdef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-elifdef.mk,v 1.3 2022/01/22 16:23:56 rillig Exp $ # -# Tests for the .elifdef directive. +# Tests for the .elifdef directive, which is seldom used. Instead of writing +# '.elifdef VAR', the usual form is the more versatile '.elif defined(VAR)'. -# TODO: Implementation +# At this point, VAR is not defined, so the condition evaluates to false. +.if 0 +.elifdef VAR +. error +.endif + +VAR= # defined + +# At this point, VAR is defined, so the condition evaluates to true. +.if 0 +.elifdef VAR +.else +. error +.endif all: - @:; diff --git a/unit-tests/directive-elifndef.mk b/unit-tests/directive-elifndef.mk index 19bb66c11b01..87aaf2fdd9ac 100644 --- a/unit-tests/directive-elifndef.mk +++ b/unit-tests/directive-elifndef.mk @@ -1,8 +1,23 @@ -# $NetBSD: directive-elifndef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-elifndef.mk,v 1.3 2022/01/22 21:50:41 rillig Exp $ # -# Tests for the .elifndef directive. +# Tests for the .elifndef directive, which is an obscure form of writing the +# more usual '.elif !defined(VAR)'. -# TODO: Implementation +# At this point, VAR is not yet defined, and due to the 'n' in 'elifndef' the +# condition evaluates to true. +.if 0 +.elifndef VAR && VAR || VAR +.else +. error +.endif + +VAR= # defined + +# At this point, VAR is defined, and due to the 'n' in 'elifndef' the +# condition evaluates to false. +.if 0 +.elifndef VAR && VAR || VAR +. error +.endif all: - @:; diff --git a/unit-tests/directive-export-impl.exp b/unit-tests/directive-export-impl.exp index 152fc0de4063..778d825996ff 100644 --- a/unit-tests/directive-export-impl.exp +++ b/unit-tests/directive-export-impl.exp @@ -1,8 +1,8 @@ -ParseReadLine (21): 'UT_VAR= <${REF}>' +Parsing line 21: UT_VAR= <${REF}> Global: UT_VAR = <${REF}> -ParseReadLine (28): '.export UT_VAR' +Parsing line 28: .export UT_VAR Global: .MAKE.EXPORTED = UT_VAR -ParseReadLine (32): ': ${UT_VAR:N*}' +Parsing line 32: : ${UT_VAR:N*} Var_Parse: ${UT_VAR:N*} (eval-defined) Var_Parse: ${REF}> (eval-defined) Evaluating modifier ${UT_VAR:N...} on value "<>" @@ -14,6 +14,7 @@ CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>" Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined) Evaluating modifier ${:!...} on value "" (eval-defined, undefined) Modifier part: "echo "$UT_VAR"" +Capturing the output of command "echo "$UT_VAR"" Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR" Result of ${.MAKE.EXPORTED:O} is "UT_VAR" @@ -23,7 +24,7 @@ Var_Parse: ${UT_VAR} (eval) Var_Parse: ${REF}> (eval) Result of ${:!echo "\$UT_VAR"!} is "<>" (eval-defined, defined) lhs = "<>", rhs = "<>", op = != -ParseReadLine (50): ': ${UT_VAR:N*}' +Parsing line 50: : ${UT_VAR:N*} Var_Parse: ${UT_VAR:N*} (eval-defined) Var_Parse: ${REF}> (eval-defined) Evaluating modifier ${UT_VAR:N...} on value "<>" @@ -31,12 +32,13 @@ Pattern for ':N' is "*" ModifyWords: split "<>" into 1 word Result of ${UT_VAR:N*} is "" ParseDependency(: ) -ParseReadLine (54): 'REF= defined' +Parsing line 54: REF= defined Global: REF = defined CondParser_Eval: ${:!echo "\$UT_VAR"!} != "" Var_Parse: ${:!echo "\$UT_VAR"!} != "" (eval-defined) Evaluating modifier ${:!...} on value "" (eval-defined, undefined) Modifier part: "echo "$UT_VAR"" +Capturing the output of command "echo "$UT_VAR"" Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR" Result of ${.MAKE.EXPORTED:O} is "UT_VAR" @@ -46,10 +48,10 @@ Var_Parse: ${UT_VAR} (eval) Var_Parse: ${REF}> (eval) Result of ${:!echo "\$UT_VAR"!} is "" (eval-defined, defined) lhs = "", rhs = "", op = != -ParseReadLine (62): 'all:' +Parsing line 62: all: ParseDependency(all:) Global: .ALLTARGETS = all -ParseReadLine (63): '.MAKEFLAGS: -d0' +Parsing line 63: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) Global: .MAKEFLAGS = -r -k -d cpv -d Global: .MAKEFLAGS = -r -k -d cpv -d 0 diff --git a/unit-tests/directive-for-escape.exp b/unit-tests/directive-for-escape.exp index 492f82e16d1f..5fa4ae1ed877 100644 --- a/unit-tests/directive-for-escape.exp +++ b/unit-tests/directive-for-escape.exp @@ -27,29 +27,29 @@ make: "directive-for-escape.mk" line 43: value-with-modifier For: end for 1 For: loop body: . info ${:U\${UNDEF\:U\\$\\$} -make: "directive-for-escape.mk" line 57: ${UNDEF:U\$ +make: "directive-for-escape.mk" line 72: ${UNDEF:U\backslash$ For: loop body: . info ${:U{{\}\}} -make: "directive-for-escape.mk" line 57: {{}} +make: "directive-for-escape.mk" line 72: {{}} For: loop body: . info ${:Uend\}} -make: "directive-for-escape.mk" line 57: end} +make: "directive-for-escape.mk" line 72: end} For: end for 1 For: loop body: . info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end} -make: "directive-for-escape.mk" line 69: beginend +make: "directive-for-escape.mk" line 84: beginend For: end for 1 For: loop body: . info ${:U\$} -make: "directive-for-escape.mk" line 77: $ +make: "directive-for-escape.mk" line 92: $ For: end for 1 For: loop body: . info ${NUMBERS} ${:Ureplaced} -make: "directive-for-escape.mk" line 85: one two three replaced +make: "directive-for-escape.mk" line 100: one two three replaced For: end for 1 For: loop body: . info ${:Ureplaced} -make: "directive-for-escape.mk" line 95: replaced +make: "directive-for-escape.mk" line 110: replaced For: end for 1 For: loop body: . info . $$i: ${:Uinner} @@ -62,31 +62,85 @@ For: loop body: . info . $${i2}: ${i2} . info . $${i,}: ${i,} . info . adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner} -make: "directive-for-escape.mk" line 103: . $i: inner -make: "directive-for-escape.mk" line 104: . ${i}: inner -make: "directive-for-escape.mk" line 105: . ${i:M*}: inner -make: "directive-for-escape.mk" line 106: . $(i): inner -make: "directive-for-escape.mk" line 107: . $(i:M*): inner -make: "directive-for-escape.mk" line 108: . ${i${:U}}: outer -make: "directive-for-escape.mk" line 109: . ${i\}}: inner} -make: "directive-for-escape.mk" line 110: . ${i2}: two -make: "directive-for-escape.mk" line 111: . ${i,}: comma -make: "directive-for-escape.mk" line 112: . adjacent: innerinnerinnerinner +make: "directive-for-escape.mk" line 118: . $i: inner +make: "directive-for-escape.mk" line 119: . ${i}: inner +make: "directive-for-escape.mk" line 120: . ${i:M*}: inner +make: "directive-for-escape.mk" line 121: . $(i): inner +make: "directive-for-escape.mk" line 122: . $(i:M*): inner +make: "directive-for-escape.mk" line 123: . ${i${:U}}: outer +make: "directive-for-escape.mk" line 124: . ${i\}}: inner} +make: "directive-for-escape.mk" line 125: . ${i2}: two +make: "directive-for-escape.mk" line 126: . ${i,}: comma +make: "directive-for-escape.mk" line 127: . adjacent: innerinnerinnerinner For: end for 1 For: loop body: . info eight $$$$$$$$ and no cents. . info eight ${:Udollar}${:Udollar}${:Udollar}${:Udollar} and no cents. -make: "directive-for-escape.mk" line 120: eight $$$$ and no cents. -make: "directive-for-escape.mk" line 121: eight dollardollardollardollar and no cents. -make: "directive-for-escape.mk" line 130: eight and no cents. +make: "directive-for-escape.mk" line 135: eight $$$$ and no cents. +make: "directive-for-escape.mk" line 136: eight dollardollardollardollar and no cents. +make: "directive-for-escape.mk" line 145: eight and no cents. For: end for 1 -make: "directive-for-escape.mk" line 137: newline in .for value -make: "directive-for-escape.mk" line 137: newline in .for value +make: "directive-for-escape.mk" line 152: newline in .for value +make: "directive-for-escape.mk" line 152: newline in .for value For: loop body: . info short: ${:U" "} . info long: ${:U" "} -make: "directive-for-escape.mk" line 138: short: " " -make: "directive-for-escape.mk" line 139: long: " " +make: "directive-for-escape.mk" line 153: short: " " +make: "directive-for-escape.mk" line 154: long: " " +For: end for 1 +For: loop body: +For: end for 1 +Parse_PushInput: .for loop in directive-for-escape.mk, line 167 +make: "directive-for-escape.mk" line 167: newline in .for value + in .for loop from directive-for-escape.mk:167 with i = " +" +For: loop body: +: ${:U" "} +SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `directive-for-escape.mk' +Parsing line 168: : ${:U" "} +ParseDependency(: " ") +ParseEOF: returning to file directive-for-escape.mk, line 170 +SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `directive-for-escape.mk' +Parsing line 170: .MAKEFLAGS: -d0 +ParseDependency(.MAKEFLAGS: -d0) +For: end for 1 +For: loop body: +# ${:U#} +For: loop body: +# ${:U\\\\#} +For: end for 1 +For: loop body: +# ${:U\$} +For: loop body: +# ${:U$i} +For: loop body: +# ${:U$(i)} +For: loop body: +# ${:U${i}} +For: loop body: +# ${:U$$} +For: loop body: +# ${:U$$$$} +For: loop body: +# ${:U${:U\$\$}} +For: end for 1 +For: loop body: +# ${:U${.TARGET}} +For: loop body: +# ${:U${.TARGET}} +For: loop body: +# ${:U$${.TARGET\}} +For: loop body: +# ${:U$${.TARGET\}} +For: end for 1 +For: loop body: +# ${:U(((} +For: loop body: +# ${:U{{{} +For: loop body: +# ${:U)))} +For: loop body: +# ${:U\}\}\}} make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-for-escape.mk b/unit-tests/directive-for-escape.mk index 725fa85d68c3..03a7a16b6a7b 100644 --- a/unit-tests/directive-for-escape.mk +++ b/unit-tests/directive-for-escape.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for-escape.mk,v 1.12 2021/12/05 11:40:03 rillig Exp $ +# $NetBSD: directive-for-escape.mk,v 1.15 2022/01/27 20:15:14 rillig Exp $ # # Test escaping of special characters in the iteration values of a .for loop. # These values get expanded later using the :U variable modifier, and this @@ -50,7 +50,22 @@ VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier) # being that each '$' is written as '$$'. # # The .for loop splits ${VALUES} into 3 words, at the space characters, since -# these are not escaped. +# the '$$' is an ordinary character and the spaces are not escaped. +# Word 1 is '${UNDEF:U\$\$' +# Word 2 is '{{}}' +# Word 3 is 'end}' +# The first iteration expands the body of the .for loop to: +# expect: . info ${:U\${UNDEF\:U\\$\\$} +# The modifier ':U' unescapes the '\$' to a simple '$'. +# The modifier ':U' unescapes the '\:' to a simple ':'. +# The modifier ':U' unescapes the '\\' to a simple '\'. +# The modifier ':U' resolves the expression '$\' to the word 'backslash', due +# to the following variable definition. +${:U\\}= backslash +# FIXME: There was no expression '$\' in the original text of the previous +# line, that's a surprise in the parser. +# The modifier ':U' unescapes the '\$' to a simple '$'. +# expect+4: ${UNDEF:U\backslash$ VALUES= $${UNDEF:U\$$\$$ {{}} end} # XXX: Where in the code does the '\$\$' get converted into a single '\$'? .for i in ${VALUES} @@ -139,4 +154,47 @@ ${closing-brace}= # alternative interpretation . info long: ${i} .endfor +# No error since the newline character is not actually used. +.for i in "${.newline}" +.endfor + +# Between for.c 1.161 from 2022-01-08 and before for.c 1.163 from 2022-01-09, +# a newline character in a .for loop led to a crash since at the point where +# the error message including the stack trace is printed, the body of the .for +# loop is assembled, and at that point, ForLoop.nextItem had already been +# advanced. +.MAKEFLAGS: -dp +.for i in "${.newline}" +: $i +.endfor +.MAKEFLAGS: -d0 + +.MAKEFLAGS: -df +.for i in \# \\\# +# $i +.endfor + +.for i in $$ $$i $$(i) $${i} $$$$ $$$$$$$$ $${:U\$$\$$} +# $i +.endfor + +# The expression '${.TARGET}' must be preserved as it is one of the 7 built-in +# target-local variables. See for.c 1.45 from 2009-01-14. +.for i in ${.TARGET} $${.TARGET} $$${.TARGET} $$$${.TARGET} +# $i +.endfor +# expect: # ${:U${.TARGET}} +# XXX: Why does '$' result in the same text as '$$'? +# expect: # ${:U${.TARGET}} +# XXX: Why does the '$$' before the '${.TARGET}' lead to an escaped '}'? +# expect: # ${:U$${.TARGET\}} +# XXX: Why does '$' result in the same text as '$$'? +# XXX: Why does the '$$' before the '${.TARGET}' lead to an escaped '}'? +# expect: # ${:U$${.TARGET\}} + +.for i in ((( {{{ ))) }}} +# $i +.endfor +.MAKEFLAGS: -d0 + all: diff --git a/unit-tests/directive-for-generating-endif.exp b/unit-tests/directive-for-generating-endif.exp index 9e1301abf96f..5f57f89c250f 100755 --- a/unit-tests/directive-for-generating-endif.exp +++ b/unit-tests/directive-for-generating-endif.exp @@ -1,7 +1,7 @@ make: "directive-for-generating-endif.mk" line 21: if-less endif make: "directive-for-generating-endif.mk" line 21: if-less endif make: "directive-for-generating-endif.mk" line 21: if-less endif -make: "directive-for-generating-endif.mk" line 0: 3 open conditionals +make: "directive-for-generating-endif.mk" line 26: 3 open conditionals make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-for.exp b/unit-tests/directive-for.exp index 4e882aad7b68..dda487917e68 100755 --- a/unit-tests/directive-for.exp +++ b/unit-tests/directive-for.exp @@ -19,6 +19,24 @@ make: "directive-for.mk" line 148: outer "quoted" \"quoted\" make: "directive-for.mk" line 154: Unknown modifier "Z" make: "directive-for.mk" line 155: XXX: Not reached word1 make: "directive-for.mk" line 155: XXX: Not reached word3 +make: "directive-for.mk" line 160: no iteration variables in for +make: "directive-for.mk" line 162: Missing argument for ".error" +make: "directive-for.mk" line 163: for-less endfor +make: "directive-for.mk" line 187: 1 open conditional +make: "directive-for.mk" line 203: for-less endfor +make: "directive-for.mk" line 204: if-less endif +make: "directive-for.mk" line 212: if-less endif +For: end for 1 +For: loop body: +.\ + for inner in i +.\ + endfor +make: "directive-for.mk" line 229: Unexpected end of file in .for loop +For: loop body: +.\ + endfor +make: "directive-for.mk" line 227: for-less endfor make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-for.mk b/unit-tests/directive-for.mk index 153762509b7a..572c4d6a5c92 100755 --- a/unit-tests/directive-for.mk +++ b/unit-tests/directive-for.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for.mk,v 1.10 2020/12/27 09:58:35 rillig Exp $ +# $NetBSD: directive-for.mk,v 1.13 2022/01/15 12:35:18 rillig Exp $ # # Tests for the .for directive. # @@ -11,7 +11,7 @@ # Using the .for loop, lists of values can be produced. # In simple cases, the :@var@${var}@ variable modifier can be used to -# reach the same effects. +# achieve the same effects. # .undef NUMBERS .for num in 1 2 3 @@ -135,7 +135,7 @@ EXPANSION${plus}= value # Ensure that braces and parentheses are properly escaped by the .for loop. # Each line must print the same word 3 times. -# See GetEscapes. +# See ForLoop_SubstBody. .for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{ . info $v ${v} $(v) .endfor @@ -155,5 +155,76 @@ var= outer . info XXX: Not reached ${var} .endfor -all: - @:; + +# An empty list of variables to the left of the 'in' is a parse error. +.for in value # expect+0: no iteration variables in for +# XXX: The loop body is evaluated once, even with the parse error above. +. error # expect+0: Missing argument for ".error" +.endfor # expect+0: for-less endfor + +# An empty list of iteration values to the right of the 'in' is accepted. +# Unlike in the shell, it is not a parse error. +.for var in +. error +.endfor + +# If the iteration values become empty after expanding the expressions, the +# body of the loop is not evaluated. It is not a parse error. +.for var in ${:U} +. error +.endfor + + +# The loop body can be empty. +.for var in 1 2 3 +.endfor + + +# A mismatched .if inside a .for loop is detected each time when the loop body +# is processed. +.for var in value +. if 0 +.endfor # expect+0: 1 open conditional + +# If there are no iteration values, the loop body is not processed, and the +# check for mismatched conditionals is not performed. +.for var in ${:U} +. if 0 +.endfor + + +# When a .for without the corresponding .endfor occurs in an inactive branch +# of an .if, the .for directive is just skipped, it does not even need a +# corresponding .endfor. In other words, the behavior of the parser depends +# on the actual values of the conditions in the .if clauses. +.if 0 +. for var in value # does not need a corresponding .endfor +.endif +.endfor # expect+0: for-less endfor +.endif # expect+0: if-less endif + + +# When a .for without the corresponding .endfor occurs in an active branch of +# an .if, the parser just counts the number of .for and .endfor directives, +# without looking at any other directives. +.if 1 +. for var in value +. endif # expect+0: if-less endif +. endfor # no 'for-less endfor' +.endif # no 'if-less endif' + + +# When make parses a .for loop, it assumes that there is no line break between +# the '.' and the 'for' or 'endfor', as there is no practical reason to break +# the line at this point. When make scans the outer .for loop, it does not +# recognize the inner directives as such. When make scans the inner .for +# loop, it recognizes the '.\n for' but does not recognize the '.\n endfor', +# as LK_FOR_BODY preserves the backslash-newline sequences. +.MAKEFLAGS: -df +.for outer in o +.\ + for inner in i +.\ + endfor +.endfor +.MAKEFLAGS: -d0 diff --git a/unit-tests/directive-hyphen-include.exp b/unit-tests/directive-hyphen-include.exp index 39a9383953dd..d0835fe464b1 100755 --- a/unit-tests/directive-hyphen-include.exp +++ b/unit-tests/directive-hyphen-include.exp @@ -1 +1,4 @@ -exit status 0 +make: "directive-hyphen-include-error.inc" line 1: Invalid line type +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-hyphen-include.mk b/unit-tests/directive-hyphen-include.mk index 8c851be43215..fbfbeb200d4f 100755 --- a/unit-tests/directive-hyphen-include.mk +++ b/unit-tests/directive-hyphen-include.mk @@ -1,9 +1,23 @@ -# $NetBSD: directive-hyphen-include.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $ +# $NetBSD: directive-hyphen-include.mk,v 1.2 2022/01/23 21:48:59 rillig Exp $ # # Tests for the .-include directive, which includes another file, # silently skipping it if it cannot be opened. +# +# The 'silently skipping' only applies to the case where the file cannot be +# opened. Parse errors and other errors are handled the same way as in the +# other .include directives. -# TODO: Implementation +# No complaint that there is no such file. +.-include "${.CURDIR}/directive-hyphen-include-nonexistent.inc" -all: - @:; +# No complaint either, even though the operating system error is ENOTDIR, not +# ENOENT. +.-include "${MAKEFILE}/subdir" + +# Errors that are not related to opening the file are still reported. +# expect: make: "directive-hyphen-include-error.inc" line 1: Invalid line type +_!= echo 'syntax error' > directive-hyphen-include-error.inc +.-include "${.CURDIR}/directive-hyphen-include-error.inc" +_!= rm directive-hyphen-include-error.inc + +all: .PHONY diff --git a/unit-tests/directive-if.exp b/unit-tests/directive-if.exp index 89a394fc0f22..5682df501e9c 100644 --- a/unit-tests/directive-if.exp +++ b/unit-tests/directive-if.exp @@ -1,17 +1,18 @@ make: "directive-if.mk" line 13: 0 evaluates to false. make: "directive-if.mk" line 17: 1 evaluates to true. -make: "directive-if.mk" line 40: Unknown directive "ifx" -make: "directive-if.mk" line 41: This is not conditional. -make: "directive-if.mk" line 42: if-less else +make: "directive-if.mk" line 41: Unknown directive "ifx" make: "directive-if.mk" line 43: This is not conditional. -make: "directive-if.mk" line 44: if-less endif -make: "directive-if.mk" line 47: Malformed conditional () -make: "directive-if.mk" line 57: Quotes in plain words are probably a mistake. -make: "directive-if.mk" line 66: Don't do this, always put a space after a directive. -make: "directive-if.mk" line 70: Don't do this, always put a space after a directive. -make: "directive-if.mk" line 76: Don't do this, always put a space around comparison operators. -make: "directive-if.mk" line 82: Don't do this, always put a space after a directive. -make: "directive-if.mk" line 86: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 45: if-less else +make: "directive-if.mk" line 47: This is not conditional. +make: "directive-if.mk" line 49: if-less endif +make: "directive-if.mk" line 53: Malformed conditional () +make: "directive-if.mk" line 63: Quotes in plain words are probably a mistake. +make: "directive-if.mk" line 72: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 76: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 82: Don't do this, always put a space around comparison operators. +make: "directive-if.mk" line 88: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 92: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 100: Unknown directive "ifn" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-if.mk b/unit-tests/directive-if.mk index b1ad2396b398..1acd5c958008 100644 --- a/unit-tests/directive-if.mk +++ b/unit-tests/directive-if.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-if.mk,v 1.9 2020/12/19 22:33:11 rillig Exp $ +# $NetBSD: directive-if.mk,v 1.11 2022/01/23 21:48:59 rillig Exp $ # # Tests for the .if directive. # @@ -37,13 +37,19 @@ # longer interpreted as a variant of '.if', therefore the '.error' and '.else' # are interpreted as ordinary directives, producing the error messages # "if-less else" and "if-less endif". +# expect+1: Unknown directive "ifx" .ifx 123 +# expect+1: This is not conditional. .info This is not conditional. +# expect+1: if-less else .else +# expect+1: This is not conditional. .info This is not conditional. +# expect+1: if-less endif .endif # Missing condition. +# expect+1: Malformed conditional () .if . error .else @@ -86,4 +92,9 @@ . info Don't do this, always put a space after a directive. .endif -all: + +# The directives '.ifdef' and '.ifmake' can be negated by inserting an 'n'. +# This doesn't work for a plain '.if' though. +# +# expect+1: Unknown directive "ifn" +.ifn 0 diff --git a/unit-tests/directive-ifdef.exp b/unit-tests/directive-ifdef.exp index 1a1358988f39..39a9383953dd 100644 --- a/unit-tests/directive-ifdef.exp +++ b/unit-tests/directive-ifdef.exp @@ -1,4 +1 @@ -make: "directive-ifdef.mk" line 12: Function calls in .ifdef are possible. -make: "directive-ifdef.mk" line 23: String literals are tested for emptiness. -make: "directive-ifdef.mk" line 27: String literals are tested for emptiness. Whitespace is non-empty. exit status 0 diff --git a/unit-tests/directive-ifdef.mk b/unit-tests/directive-ifdef.mk index 12f3648e8b2c..8a60fb4a2669 100644 --- a/unit-tests/directive-ifdef.mk +++ b/unit-tests/directive-ifdef.mk @@ -1,33 +1,51 @@ -# $NetBSD: directive-ifdef.mk,v 1.4 2021/01/21 23:03:41 rillig Exp $ +# $NetBSD: directive-ifdef.mk,v 1.5 2022/01/23 21:48:59 rillig Exp $ # -# Tests for the .ifdef directive. - -# TODO: Implementation +# Tests for the .ifdef directive, which evaluates bare words by calling +# 'defined(word)'. DEFINED= defined -# It looks redundant to have a call to defined() in an .ifdef, but it's -# possible. The .ifdef only affects plain symbols, not function calls. -.ifdef defined(DEFINED) -. info Function calls in .ifdef are possible. +# There is no variable named 'UNDEF', therefore the condition evaluates to +# false. +.ifdef UNDEF +. error +.endif + +# There is a variable named 'DEFINED', so the condition evaluates to true. +.ifdef DEFINED .else . error .endif -# String literals are handled the same in all variants of the .if directive. -# They evaluate to true if they are not empty. Whitespace counts as non-empty -# as well. +# Since a bare word is an abbreviation for 'defined(word)', these can be +# used to construct complex conditions. +.ifdef UNDEF && DEFINED +. error +.endif +.ifdef UNDEF || DEFINED +.else +. error +.endif + +# It looks redundant to have a call to defined() in an .ifdef, but it's +# possible. The '.ifdef' only affects bare words, not function calls. +.ifdef defined(DEFINED) +.else +. error +.endif + +# String literals are handled the same in all variants of the '.if' directive, +# they evaluate to true if they are not empty, therefore this particular +# example looks confusing and is thus not found in practice. .ifdef "" . error .else -. info String literals are tested for emptiness. .endif +# Whitespace counts as non-empty as well. .ifdef " " -. info String literals are tested for emptiness. Whitespace is non-empty. .else . error .endif -all: - @:; +all: .PHONY diff --git a/unit-tests/directive-ifmake.exp b/unit-tests/directive-ifmake.exp index fd4bcae151a0..bf4ded97911f 100644 --- a/unit-tests/directive-ifmake.exp +++ b/unit-tests/directive-ifmake.exp @@ -8,4 +8,8 @@ make: "directive-ifmake.mk" line 75: ok : first : second : late-target -exit status 0 +make: don't know how to make !edge (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-ifmake.mk b/unit-tests/directive-ifmake.mk index 4d49add72626..4824ce4d0570 100644 --- a/unit-tests/directive-ifmake.mk +++ b/unit-tests/directive-ifmake.mk @@ -1,12 +1,12 @@ -# $NetBSD: directive-ifmake.mk,v 1.8 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: directive-ifmake.mk,v 1.9 2022/01/22 16:23:56 rillig Exp $ # # Tests for the .ifmake directive, which provides a shortcut for asking # whether a certain target is requested to be made from the command line. # # TODO: Describe why the shortcut may be useful (if it's useful at all), -# instead of sticking to the simple '.if' only. +# instead of using the more versatile '.if make(target)'. -# The targets 'first' and 'second' are passed in on the command line. +.MAKEFLAGS: first second # This is the most basic form. .ifmake first @@ -15,9 +15,9 @@ . warning positive condition fails .endif -# The not operator works as expected. -# An alternative interpretation were that this condition is asking whether -# the target "!first" was requested. To distinguish this, see the next test. +# The '!' is interpreted as 'not'. A possible alternative interpretation of +# this condition is whether the target named "!first" was requested. To +# distinguish these cases, see the next test. .ifmake !first . warning unexpected .else @@ -78,5 +78,30 @@ .endif -first second unmentioned late-target: +# As an edge case, a target can actually be named "!first" on the command +# line. There is no way to define a target of this name though since in a +# dependency line, a plain '!' is interpreted as a dependency operator. + +.MAKEFLAGS: !edge +.ifmake edge +. error +.endif + +# The '\!edge' in the following condition is parsed as a bare word. For such +# a bare word, there is no escaping mechanism so the backslash passes through. +# Since the condition function 'make' accepts a pattern instead of a plain +# target name, the '\' is finally discarded in Str_Match. +.ifmake \!edge +.else +. error +.endif + +# In a dependency line, a plain '!' is interpreted as a dependency operator +# (the other two are ':' and '::'). If the '!' is escaped by a '\', as +# implemented in ParseDependencyTargetWord, the additional backslash is never +# removed though. The target name thus becomes '\!edge' instead of the +# intended '!edge'. Defining a target whose name contains a '!' will either +# require additional tricks, or it may even be impossible. + +first second unmentioned late-target \!edge: : $@ diff --git a/unit-tests/directive-include.exp b/unit-tests/directive-include.exp index b339f393e7e8..42975d444c31 100755 --- a/unit-tests/directive-include.exp +++ b/unit-tests/directive-include.exp @@ -6,6 +6,8 @@ make: "directive-include.mk" line 25: Could not find nonexistent.mk make: "directive-include.mk" line 47: Could not find " make: "directive-include.mk" line 52: Unknown modifier "Z" make: "directive-include.mk" line 52: Could not find nonexistent.mk +make: "directive-include.mk" line 57: Cannot open /nonexistent +make: "directive-include.mk" line 62: Invalid line type make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-include.mk b/unit-tests/directive-include.mk index a6b300b3d273..edf27d02483e 100755 --- a/unit-tests/directive-include.mk +++ b/unit-tests/directive-include.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-include.mk,v 1.7 2021/12/03 22:48:07 rillig Exp $ +# $NetBSD: directive-include.mk,v 1.11 2022/01/15 12:35:18 rillig Exp $ # # Tests for the .include directive, which includes another file. @@ -51,4 +51,35 @@ DQUOT= " # FIXME: Add proper error handling, no file must be included here. .include "nonexistent${:U123:Z}.mk" +# The traditional include directive is seldom used. +include /dev/null # comment +# expect+1: Cannot open /nonexistent +include /nonexistent # comment +sinclude /nonexistent # comment +include ${:U/dev/null} # comment +include /dev/null /dev/null +# expect+1: Invalid line type +include + +# XXX: trailing whitespace in diagnostic, missing quotes around filename +### TODO: expect+1: Could not find +# The following include directive behaves differently, depending on whether +# the current file has a slash or is a relative filename. In the first case, +# make opens the directory of the current file and tries to read from it, +# resulting in the error message """ line 1: Zero byte read from file". +# In the second case, the error message is "Could not find ", without quotes +# or any other indicator for the empty filename at the end of the line. +#include ${:U} + + +# Since parse.c 1.612 from 2022-01-01 and before parse.c 1.620 from +# 2022-01-07, including an empty regular file called bmake_malloc(0), which +# may return a null pointer. On OpenBSD, this led to a segmentation fault in +# Buf_InitSize, which assumes that bmake_malloc never returns NULL, just like +# all other places in the code. +_!= > directive-include-empty +.include "${.CURDIR}/directive-include-empty" +_!= rm directive-include-empty + + all: diff --git a/unit-tests/directive-info.exp b/unit-tests/directive-info.exp index 2652c191460c..70def02441d1 100644 --- a/unit-tests/directive-info.exp +++ b/unit-tests/directive-info.exp @@ -9,7 +9,7 @@ make: "directive-info.mk" line 22: Missing argument for ".info" make: "directive-info.mk" line 23: Missing argument for ".info" make: "directive-info.mk" line 26: Unknown directive "info-message" make: "directive-info.mk" line 27: no-target: no-source -make: "directive-info.mk" line 36: expect line 30 for multi-line message +make: "directive-info.mk" line 35: expect line 35 for multi-line message make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-info.mk b/unit-tests/directive-info.mk index 5feea0cde565..54f6a0f5aad0 100644 --- a/unit-tests/directive-info.mk +++ b/unit-tests/directive-info.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-info.mk,v 1.8 2020/12/19 22:33:11 rillig Exp $ +# $NetBSD: directive-info.mk,v 1.9 2022/01/08 20:21:34 rillig Exp $ # # Tests for the .info directive. # @@ -27,11 +27,12 @@ .info no-target: no-source # This is a .info directive, not a dependency. # See directive.mk for more tests of this kind. -# Since at least 2002-01-01, the line number that is used in error messages -# and the .info directives is the number of completely read lines. For the -# following multi-line directive, this means that the reported line number is -# the one of the last line, not the first line. -.info expect line 30 for\ +# Since at least 2002-01-01 and before parse.c 1.639 from 2022-01-08, the line +# number that is used in error messages and the .info directives was the +# number of completely read lines. For the following multi-line directive, +# this meant that the reported line number was the one of the last line, not +# of the first line. +.info expect line 35 for\ multi$\ -line message diff --git a/unit-tests/directive-sinclude.exp b/unit-tests/directive-sinclude.exp index 39a9383953dd..ffdfefca0d4f 100755 --- a/unit-tests/directive-sinclude.exp +++ b/unit-tests/directive-sinclude.exp @@ -1 +1,4 @@ -exit status 0 +make: "directive-include-error.inc" line 1: Invalid line type +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-sinclude.mk b/unit-tests/directive-sinclude.mk index 1932e7b3ba13..a935ea2c068f 100755 --- a/unit-tests/directive-sinclude.mk +++ b/unit-tests/directive-sinclude.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-sinclude.mk,v 1.2 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: directive-sinclude.mk,v 1.4 2022/01/23 21:48:59 rillig Exp $ # # Tests for the .sinclude directive, which includes another file, # silently skipping it if it cannot be opened. @@ -7,7 +7,17 @@ # opened. Parse errors and other errors are handled the same way as in the # other .include directives. -# TODO: Implementation +# No complaint that there is no such file. +.sinclude "${.CURDIR}/directive-include-nonexistent.inc" -all: - @:; +# No complaint either, even though the operating system error is ENOTDIR, not +# ENOENT. +.sinclude "${MAKEFILE}/subdir" + +# Errors that are not related to opening the file are still reported. +# expect: make: "directive-include-error.inc" line 1: Invalid line type +_!= echo 'syntax error' > directive-include-error.inc +.sinclude "${.CURDIR}/directive-include-error.inc" +_!= rm directive-include-error.inc + +all: .PHONY diff --git a/unit-tests/directive-undef.exp b/unit-tests/directive-undef.exp index 56c871429397..20df58a8dc73 100644 --- a/unit-tests/directive-undef.exp +++ b/unit-tests/directive-undef.exp @@ -1,6 +1,6 @@ make: "directive-undef.mk" line 29: The .undef directive requires an argument make: "directive-undef.mk" line 86: Unknown modifier "Z" -make: "directive-undef.mk" line 103: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore. +make: "directive-undef.mk" line 102: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-unexport-env.mk b/unit-tests/directive-unexport-env.mk index ef58ae732e6d..e9620684dfcb 100644 --- a/unit-tests/directive-unexport-env.mk +++ b/unit-tests/directive-unexport-env.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-unexport-env.mk,v 1.7 2020/12/12 18:11:42 rillig Exp $ +# $NetBSD: directive-unexport-env.mk,v 1.8 2022/01/23 16:09:38 rillig Exp $ # # Tests for the .unexport-env directive. # @@ -21,5 +21,4 @@ UT_UNEXPORTED= value .unexport-env UT_EXPORTED UT_UNEXPORTED .MAKEFLAGS: -d0 -all: - @:; +all: .PHONY diff --git a/unit-tests/directive-warning.exp b/unit-tests/directive-warning.exp index b08b3207392c..932b88a151e2 100644 --- a/unit-tests/directive-warning.exp +++ b/unit-tests/directive-warning.exp @@ -1,11 +1,11 @@ -make: "directive-warning.mk" line 11: Unknown directive "warn" -make: "directive-warning.mk" line 12: Unknown directive "warn" -make: "directive-warning.mk" line 13: Unknown directive "warnin" -make: "directive-warning.mk" line 14: Unknown directive "warnin" -make: "directive-warning.mk" line 15: Missing argument for ".warning" -make: "directive-warning.mk" line 16: warning: message -make: "directive-warning.mk" line 17: Unknown directive "warnings" -make: "directive-warning.mk" line 18: Unknown directive "warnings" +make: "directive-warning.mk" line 9: Unknown directive "warn" +make: "directive-warning.mk" line 10: Unknown directive "warn" +make: "directive-warning.mk" line 11: Unknown directive "warnin" +make: "directive-warning.mk" line 12: Unknown directive "warnin" +make: "directive-warning.mk" line 13: Missing argument for ".warning" +make: "directive-warning.mk" line 14: warning: message +make: "directive-warning.mk" line 15: Unknown directive "warnings" +make: "directive-warning.mk" line 16: Unknown directive "warnings" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-warning.mk b/unit-tests/directive-warning.mk index d586c9fed170..9d5cec1ff0b8 100644 --- a/unit-tests/directive-warning.mk +++ b/unit-tests/directive-warning.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-warning.mk,v 1.6 2020/12/19 22:33:11 rillig Exp $ +# $NetBSD: directive-warning.mk,v 1.7 2022/01/23 16:09:38 rillig Exp $ # # Tests for the .warning directive. # @@ -6,16 +6,13 @@ # produced the wrong error message "Unknown directive". Since parse.c 1.503 # from 2020-12-19, the correct "Missing argument" is produced. -# TODO: Implementation - .warn # misspelled .warn message # misspelled .warnin # misspelled .warnin message # misspelled .warning # "Missing argument" -.warning message # ok +.warning message # expect+0: message .warnings # misspelled .warnings messages # Accepted before 2020-12-13 01:07:54. -all: - @:; +all: .PHONY diff --git a/unit-tests/directive.exp b/unit-tests/directive.exp index ee866b7ee2b3..2002fa73f58c 100644 --- a/unit-tests/directive.exp +++ b/unit-tests/directive.exp @@ -1,12 +1,14 @@ -make: "directive.mk" line 9: Unknown directive "indented" make: "directive.mk" line 10: Unknown directive "indented" -make: "directive.mk" line 11: Unknown directive "indented" -make: "directive.mk" line 15: Unknown directive "info" +make: "directive.mk" line 12: Unknown directive "indented" +make: "directive.mk" line 14: Unknown directive "indented" +make: "directive.mk" line 21: Unknown directive "info" Global: .info = Global: .info = value -make: "directive.mk" line 26: := value +make: "directive.mk" line 33: := value Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 +make: "directive.mk" line 42: Invalid line type +make: "directive.mk" line 45: Invalid line type make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive.mk b/unit-tests/directive.mk index d463ce4f009a..365a070f8f30 100644 --- a/unit-tests/directive.mk +++ b/unit-tests/directive.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $ +# $NetBSD: directive.mk,v 1.6 2022/01/23 16:09:38 rillig Exp $ # # Tests for the preprocessing directives, such as .if or .info. @@ -6,23 +6,30 @@ # Unknown directives are correctly named in the error messages, # even if they are indented. +# expect+1: Unknown directive "indented" .indented none +# expect+1: Unknown directive "indented" . indented 2 spaces +# expect+1: Unknown directive "indented" . indented tab # Directives must be written directly, not indirectly via variable # expressions. +# FIXME: The error message is misleading because it shows the expanded text of +# the line, while the parser works on the unexpanded line. +# expect+1: Unknown directive "info" .${:Uinfo} directives cannot be indirect # There is no directive called '.target', therefore this is parsed as a # dependency declaration with 2 targets and 1 source. .target target: source -# This looks ambiguous. It could be either an .info message or a variable -# assignment. It is a variable assignment. +# The following lines demonstrate how the parser tells an .info message apart +# from a variable assignment to ".info", which syntactically is very similar. .MAKEFLAGS: -dv -.info:= value +.info:= value # This is a variable assignment. .info?= value # This is a variable assignment as well. +# expect+1: := value .info := value # The space after the '.info' makes this # a directive. .MAKEFLAGS: -d0 @@ -31,5 +38,8 @@ # Not even the space after the '.info' can change anything about this. .${:Uinfo} : source -all: - @:; +# expect+1: Invalid line type +target-without-colon + +# expect+1: Invalid line type +target-without-colon another-target diff --git a/unit-tests/envfirst.mk b/unit-tests/envfirst.mk deleted file mode 100644 index 60a331a5db64..000000000000 --- a/unit-tests/envfirst.mk +++ /dev/null @@ -1,44 +0,0 @@ -# $NetBSD: envfirst.mk,v 1.5 2021/02/04 21:42:47 rillig Exp $ -# -# The -e option makes environment variables stronger than global variables. - -.MAKEFLAGS: -e - -.if ${FROM_ENV} != value-from-env -. error ${FROM_ENV} -.endif - -# Try to override the variable; this does not have any effect. -FROM_ENV= value-from-mk -.if ${FROM_ENV} != value-from-env -. error ${FROM_ENV} -.endif - -# Try to append to the variable; this also doesn't have any effect. -FROM_ENV+= appended -.if ${FROM_ENV} != value-from-env -. error ${FROM_ENV} -.endif - -# The default assignment also cannot change the variable. -FROM_ENV?= default -.if ${FROM_ENV} != value-from-env -. error ${FROM_ENV} -.endif - -# Neither can the assignment modifiers. -.if ${FROM_ENV::=from-condition} -.endif -.if ${FROM_ENV} != value-from-env -. error ${FROM_ENV} -.endif - -# Even .undef doesn't work since it only affects the global scope, -# which is independent from the environment variables. -.undef FROM_ENV -.if ${FROM_ENV} != value-from-env -. error ${FROM_ENV} -.endif - -all: - @: nothing diff --git a/unit-tests/hanoi-include.mk b/unit-tests/hanoi-include.mk index 3ad0a751845a..3b9438bf2169 100644 --- a/unit-tests/hanoi-include.mk +++ b/unit-tests/hanoi-include.mk @@ -1,10 +1,10 @@ -# $NetBSD: hanoi-include.mk,v 1.1 2020/10/03 17:30:54 rillig Exp $ +# $NetBSD: hanoi-include.mk,v 1.2 2022/01/08 22:13:43 rillig Exp $ # # Implements the Towers of Hanoi puzzle, thereby demonstrating a bunch of -# useful programming techniques: +# more or less useful programming techniques: # # * default assignment using the ?= assignment operator -# * including the same file recursively +# * including the same file recursively (rather unusual) # * 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 diff --git a/unit-tests/include-main.exp b/unit-tests/include-main.exp index c8a670a1c14a..e677826373c1 100644 --- a/unit-tests/include-main.exp +++ b/unit-tests/include-main.exp @@ -2,13 +2,13 @@ make: "include-main.mk" line 14: main-before-ok make: "include-main.mk" line 21: main-before-for-ok make: "include-sub.mk" line 4: sub-before-ok make: "include-sub.mk" line 14: sub-before-for-ok -ParseReadLine (5): '. info subsub-ok' +Parsing line 5: . info subsub-ok make: "include-subsub.mk" line 5: subsub-ok - in .for loop from include-sub.mk:31 - in .for loop from include-sub.mk:30 - in .for loop from include-sub.mk:29 - in .include from include-main.mk:27 -ParseReadLine (6): '.MAKEFLAGS: -d0' + in .for loop from include-sub.mk:31 with i = include + in .for loop from include-sub.mk:30 with i = nested + in .for loop from include-sub.mk:29 with i = deeply + in include-main.mk:27 +Parsing line 6: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) make: "include-sub.mk" line 38: sub-after-ok make: "include-sub.mk" line 45: sub-after-for-ok diff --git a/unit-tests/include-main.mk b/unit-tests/include-main.mk index d3f122aef718..9a4c6630506b 100644 --- a/unit-tests/include-main.mk +++ b/unit-tests/include-main.mk @@ -1,11 +1,11 @@ -# $NetBSD: include-main.mk,v 1.6 2021/01/22 00:44:55 rillig Exp $ +# $NetBSD: include-main.mk,v 1.7 2022/01/08 23:41:43 rillig Exp $ # # 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 before 2020-09-05 it was the "filename in which -# the latest .include happened". See parse.c, function ParseSetIncludeFile. +# the latest .include happened". See parse.c, function SetParseFile. # # Since 2020-09-05, the .INCLUDEDFROMDIR and .INCLUDEDFROMFILE variables # properly handle nested includes and even .for loops. diff --git a/unit-tests/include-sub.mk b/unit-tests/include-sub.mk index 0b8dc77398ab..57d2aafe9d1d 100644 --- a/unit-tests/include-sub.mk +++ b/unit-tests/include-sub.mk @@ -1,4 +1,4 @@ -# $NetBSD: include-sub.mk,v 1.7 2020/11/02 19:07:09 rillig Exp $ +# $NetBSD: include-sub.mk,v 1.9 2022/01/08 23:41:43 rillig Exp $ .if ${.INCLUDEDFROMFILE} == "include-main.mk" . info sub-before-ok @@ -20,11 +20,11 @@ # To see the variable 'includes' in action: # # Breakpoints: -# Parse_File at "Vector_Push(&includes)" -# ParseMessage at entry +# Parse_PushInput at "Vector_Push(&includes)" +# HandleMessage at entry # Watches: -# ((const IFile *[10])(*includes.items)) -# *curFile +# ((const IncludedFile *[10])(*includes.items)) +# *CurFile() .for i in deeply . for i in nested diff --git a/unit-tests/meta-cmd-cmp.exp b/unit-tests/meta-cmd-cmp.exp index bfc52123e3b2..dc63da3b346b 100644 --- a/unit-tests/meta-cmd-cmp.exp +++ b/unit-tests/meta-cmd-cmp.exp @@ -34,4 +34,20 @@ vs Building .meta-cmd-cmp.cmp2 This line not compared FLAGS= Skipping meta for .END: .SPECIAL +filter0: +Building .meta-cmd-cmp.filter +Skipping meta for .END: .SPECIAL +filter1: +.meta-cmd-cmp.filter.meta: 2: a build command has changed +@echo ccache cc -c foo.c > .meta-cmd-cmp.filter +vs +@echo cc -c foo.c > .meta-cmd-cmp.filter +Building .meta-cmd-cmp.filter +Skipping meta for .END: .SPECIAL +filter2: +`.meta-cmd-cmp.filter' is up to date. +Skipping meta for .END: .SPECIAL +filter3: +`.meta-cmd-cmp.filter' is up to date. +Skipping meta for .END: .SPECIAL exit status 0 diff --git a/unit-tests/meta-cmd-cmp.mk b/unit-tests/meta-cmd-cmp.mk index a1c0f7c10063..b2628fdbd2a9 100644 --- a/unit-tests/meta-cmd-cmp.mk +++ b/unit-tests/meta-cmd-cmp.mk @@ -1,4 +1,4 @@ -# $NetBSD: meta-cmd-cmp.mk,v 1.2 2020/12/05 22:51:34 sjg Exp $ +# $NetBSD: meta-cmd-cmp.mk,v 1.4 2022/01/27 06:02:59 sjg Exp $ # # Tests META_MODE command line comparison # @@ -9,7 +9,7 @@ tf:= .${.PARSEFILE:R} .if ${.TARGETS:Nall} == "" -all: prep one two change1 change2 post +all: prep one two change1 change2 filter0 filter1 filter2 filter3 post CLEANFILES= ${tf}* @@ -22,6 +22,7 @@ FLAGS?= FLAGS2?= tests= ${tf}.cmp ${tf}.nocmp ${tf}.cmp2 +filter_tests= ${tf}.filter ${tf}.cmp: @echo FLAGS=${FLAGS:Uempty} > $@ @@ -35,6 +36,19 @@ ${tf}.cmp2: @echo FLAGS2=${FLAGS2:Uempty} > $@ @echo This line not compared FLAGS=${FLAGS:Uempty} ${.OODATE:MNOMETA_CMP} +COMPILER_WRAPPERS+= ccache distcc icecc +WRAPPER?= ccache +.ifdef WITH_CMP_FILTER +.MAKE.META.CMP_FILTER+= ${COMPILER_WRAPPERS:S,^,N,} +.endif +.ifdef WITH_LOCAL_CMP_FILTER +# local variable +${tf}.filter: .MAKE.META.CMP_FILTER= ${COMPILER_WRAPPERS:S,^,N,} +.endif + +${tf}.filter: + @echo ${WRAPPER} cc -c foo.c > $@ + # these do the same one two: .PHONY @echo $@: @@ -48,5 +62,23 @@ change2: .PHONY @echo $@: @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} FLAGS2=changed ${tests} +filter0: .PHONY + @echo $@: + @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} ${filter_tests} + +filter1: .PHONY + @echo $@: + @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} WRAPPER= ${filter_tests} + +filter2: .PHONY + @echo $@: + @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} -DWITH_CMP_FILTER \ + WRAPPER=distcc ${filter_tests} + +filter3: .PHONY + @echo $@: + @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} -DWITH_LOCAL_CMP_FILTER \ + WRAPPER=icecc ${filter_tests} + # don't let gcov mess up the results .MAKE.META.IGNORE_PATTERNS+= *.gcda diff --git a/unit-tests/modts.exp b/unit-tests/modts.exp deleted file mode 100644 index 18837016add4..000000000000 --- a/unit-tests/modts.exp +++ /dev/null @@ -1,14 +0,0 @@ -make: Bad modifier ":tx" for variable "LIST" -LIST:tx="}" -make: Bad modifier ":ts\X" for variable "LIST" -LIST:ts/x:tu="\X:tu}" -FU_mod-ts="a/b/cool" -FU_mod-ts:ts:T="cool" == cool? -B.${AAA:ts}="Baaa" == Baaa? -:ts :S => aaxBbxaaxbbxaaxbb -:ts :S space => axa a axc -:ts :S space :M => axaxaxaxc -:ts :S => axa a axc -:ts :S :@ => axa a axc -:ts :S :@ :M => axaxaxaxc -exit status 0 diff --git a/unit-tests/modts.mk b/unit-tests/modts.mk deleted file mode 100644 index 4776c5818ea5..000000000000 --- a/unit-tests/modts.mk +++ /dev/null @@ -1,47 +0,0 @@ -# $NetBSD: modts.mk,v 1.8 2020/11/03 18:42:33 rillig Exp $ - -LIST= one two three four five six - -FU_mod-ts= a / b / cool - -AAA= a a a -B.aaa= Baaa - -all: mod-ts mod-ts-space - -# Use print or printf iff they are builtin. -# XXX note that this causes problems, when make decides -# there is no need to use a shell, so avoid where possible. -.if ${(type print) 2> /dev/null || echo:L:sh:Mbuiltin} != "" -PRINT= print -r -- -.elif ${(type printf) 2> /dev/null || echo:L:sh:Mbuiltin} != "" -PRINT= printf '%s\n' -.else -PRINT= echo -.endif - -mod-ts: - @${PRINT} 'LIST:tx="${LIST:tx}"' - @${PRINT} 'LIST:ts/x:tu="${LIST:ts\X:tu}"' - @${PRINT} 'FU_$@="${FU_${@:ts}:ts}"' - @${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?' - @${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?' - -mod-ts-space: - # After the :ts modifier, the whole string is interpreted as a single - # word since all spaces have been replaced with x. - @${PRINT} ':ts :S => '${aa bb aa bb aa bb:L:tsx:S,b,B,:Q} - - # The :ts modifier also applies to word separators that are added - # afterwards. - @${PRINT} ':ts :S space => '${a ababa c:L:tsx:S,b, ,g:Q} - @${PRINT} ':ts :S space :M => '${a ababa c:L:tsx:S,b, ,g:M*:Q} - - # Not all modifiers behave this way though. Some of them always use - # a space as word separator instead of the :ts separator. - # This seems like an oversight during implementation. - @${PRINT} ':ts :S => '${a ababa c:L:tsx:S,b, ,g:Q} - @${PRINT} ':ts :S :@ => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:Q} - - # A final :M* modifier applies the :ts separator again, though. - @${PRINT} ':ts :S :@ :M => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:M*:Q} diff --git a/unit-tests/modword.exp b/unit-tests/modword.exp deleted file mode 100644 index 02e9974c02d6..000000000000 --- a/unit-tests/modword.exp +++ /dev/null @@ -1,126 +0,0 @@ -make: Bad modifier ":[]" for variable "LIST" -LIST:[]="" is an error -LIST:[0]="one two three four five six" -LIST:[0x0]="one two three four five six" -LIST:[000]="one two three four five six" -LIST:[*]="one two three four five six" -LIST:[@]="one two three four five six" -LIST:[0]:C/ /,/="one,two three four five six" -LIST:[0]:C/ /,/g="one,two,three,four,five,six" -LIST:[0]:C/ /,/1g="one,two,three,four,five,six" -LIST:[*]:C/ /,/="one,two three four five six" -LIST:[*]:C/ /,/g="one,two,three,four,five,six" -LIST:[*]:C/ /,/1g="one,two,three,four,five,six" -LIST:[@]:C/ /,/="one two three four five six" -LIST:[@]:C/ /,/g="one two three four five six" -LIST:[@]:C/ /,/1g="one two three four five six" -LIST:[@]:[0]:C/ /,/="one,two three four five six" -LIST:[0]:[@]:C/ /,/="one two three four five six" -LIST:[@]:[*]:C/ /,/="one,two three four five six" -LIST:[*]:[@]:C/ /,/="one two three four five six" -EMPTY="" -EMPTY:[#]="1" == 1 ? -ESCAPEDSPACE="\ " -ESCAPEDSPACE:[#]="1" == 1 ? -REALLYSPACE=" " -REALLYSPACE:[#]="1" == 1 ? -LIST:[#]="6" -LIST:[0]:[#]="1" == 1 ? -LIST:[*]:[#]="1" == 1 ? -LIST:[@]:[#]="6" -LIST:[1]:[#]="1" -LIST:[1..3]:[#]="3" -EMPTY:[1]="" -ESCAPEDSPACE="\ " -ESCAPEDSPACE:[1]="\ " -REALLYSPACE=" " -REALLYSPACE:[1]="" == "" ? -REALLYSPACE:[*]:[1]=" " == " " ? -LIST:[1]="one" -make: Bad modifier ":[1.]" for variable "LIST" -LIST:[1.]="" is an error -make: Bad modifier ":[1]." for variable "LIST" -LIST:[1].="}" is an error -LIST:[2]="two" -LIST:[6]="six" -LIST:[7]="" -LIST:[999]="" -make: Bad modifier ":[-]" for variable "LIST" -LIST:[-]="" is an error -make: Bad modifier ":[--]" for variable "LIST" -LIST:[--]="" is an error -LIST:[-1]="six" -LIST:[-2]="five" -LIST:[-6]="one" -LIST:[-7]="" -LIST:[-999]="" -LONGLIST:[17]="17" -LONGLIST:[0x11]="17" -LONGLIST:[021]="17" -LIST:[0]:[1]="one two three four five six" -LIST:[*]:[1]="one two three four five six" -LIST:[@]:[1]="one" -LIST:[0]:[2]="" -LIST:[*]:[2]="" -LIST:[@]:[2]="two" -LIST:[*]:C/ /,/:[2]="" -LIST:[*]:C/ /,/:[*]:[2]="" -LIST:[*]:C/ /,/:[@]:[2]="three" -LONGLIST:[012..0x12]="10 11 12 13 14 15 16 17 18" -make: Bad modifier ":[1.]" for variable "LIST" -LIST:[1.]="" is an error -make: Bad modifier ":[1..]" for variable "LIST" -LIST:[1..]="" is an error -make: Bad modifier ":[1.. ]" for variable "LIST" -LIST:[1.. ]="" is an error -LIST:[1..1]="one" -make: Bad modifier ":[1..1.]" for variable "LIST" -LIST:[1..1.]="" is an error -LIST:[1..2]="one two" -LIST:[2..1]="two one" -LIST:[3..-2]="three four five" -LIST:[-4..4]="three four" -make: Bad modifier ":[0..1]" for variable "LIST" -LIST:[0..1]="" is an error -make: Bad modifier ":[-1..0]" for variable "LIST" -LIST:[-1..0]="" is an error -LIST:[-1..1]="six five four three two one" -LIST:[0..0]="one two three four five six" -LIST:[3..99]="three four five six" -LIST:[-3..-99]="four three two one" -LIST:[-99..-3]="one two three four" -HASH="#" == "#" ? -LIST:[${HASH}]="6" -LIST:[${ZERO}]="one two three four five six" -LIST:[${ZERO}x${ONE}]="one" -LIST:[${ONE}]="one" -LIST:[${MINUSONE}]="six" -LIST:[${STAR}]="one two three four five six" -LIST:[${AT}]="one two three four five six" -make: Bad modifier ":[${EMPTY" for variable "LIST" -LIST:[${EMPTY}]="" is an error -LIST:[${LONGLIST:[21]:S/2//}]="one" -LIST:[${LIST:[#]}]="six" -LIST:[${LIST:[${HASH}]}]="six" -LIST:[ -1.. +3]="six five four three" -LIST:S/ /,/="one two three four five six" -LIST:S/ /,/W="one,two three four five six" -LIST:S/ /,/gW="one,two,three,four,five,six" -EMPTY:S/^/,/="," -EMPTY:S/^/,/W="," -LIST:C/ /,/="one two three four five six" -LIST:C/ /,/W="one,two three four five six" -LIST:C/ /,/gW="one,two,three,four,five,six" -EMPTY:C/^/,/="," -EMPTY:C/^/,/W="," -LIST:tW="one two three four five six" -LIST:tw="one two three four five six" -LIST:tW:C/ /,/="one,two three four five six" -LIST:tW:C/ /,/g="one,two,three,four,five,six" -LIST:tW:C/ /,/1g="one,two,three,four,five,six" -LIST:tw:C/ /,/="one two three four five six" -LIST:tw:C/ /,/g="one two three four five six" -LIST:tw:C/ /,/1g="one two three four five six" -LIST:tw:tW:C/ /,/="one,two three four five six" -LIST:tW:tw:C/ /,/="one two three four five six" -exit status 0 diff --git a/unit-tests/modword.mk b/unit-tests/modword.mk deleted file mode 100644 index 95bb1fec78c3..000000000000 --- a/unit-tests/modword.mk +++ /dev/null @@ -1,161 +0,0 @@ -# $NetBSD: modword.mk,v 1.6 2021/03/14 16:00:07 rillig Exp $ -# -# Test behaviour of new :[] modifier -# TODO: When was this modifier new? - -all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw - -LIST= one two three -LIST+= four five six -LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - -EMPTY= # the space should be ignored -ESCAPEDSPACE= \ # escaped space before the '#' -REALLYSPACE:= ${EMPTY:C/^/ /W} -HASH= \# -AT= @ -STAR= * -ZERO= 0 -ONE= 1 -MINUSONE= -1 - -mod-squarebrackets: mod-squarebrackets-0-star-at \ - mod-squarebrackets-hash \ - mod-squarebrackets-n \ - mod-squarebrackets-start-end \ - mod-squarebrackets-nested \ - mod-squarebrackets-space - -mod-squarebrackets-0-star-at: - @echo 'LIST:[]="${LIST:[]}" is an error' - @echo 'LIST:[0]="${LIST:[0]}"' - @echo 'LIST:[0x0]="${LIST:[0x0]}"' - @echo 'LIST:[000]="${LIST:[000]}"' - @echo 'LIST:[*]="${LIST:[*]}"' - @echo 'LIST:[@]="${LIST:[@]}"' - @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"' - @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"' - @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"' - @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"' - @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"' - @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"' - @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"' - @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"' - @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"' - @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"' - @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"' - @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"' - @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"' - -mod-squarebrackets-hash: - @echo 'EMPTY="${EMPTY}"' - @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?' - @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' - @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?' - @echo 'REALLYSPACE="${REALLYSPACE}"' - @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?' - @echo 'LIST:[#]="${LIST:[#]}"' - @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?' - @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?' - @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"' - @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"' - @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"' - -mod-squarebrackets-n: - @echo 'EMPTY:[1]="${EMPTY:[1]}"' - @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' - @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"' - @echo 'REALLYSPACE="${REALLYSPACE}"' - @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?' - @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?' - @echo 'LIST:[1]="${LIST:[1]}"' - @echo 'LIST:[1.]="${LIST:[1.]}" is an error' - @echo 'LIST:[1].="${LIST:[1].}" is an error' - @echo 'LIST:[2]="${LIST:[2]}"' - @echo 'LIST:[6]="${LIST:[6]}"' - @echo 'LIST:[7]="${LIST:[7]}"' - @echo 'LIST:[999]="${LIST:[999]}"' - @echo 'LIST:[-]="${LIST:[-]}" is an error' - @echo 'LIST:[--]="${LIST:[--]}" is an error' - @echo 'LIST:[-1]="${LIST:[-1]}"' - @echo 'LIST:[-2]="${LIST:[-2]}"' - @echo 'LIST:[-6]="${LIST:[-6]}"' - @echo 'LIST:[-7]="${LIST:[-7]}"' - @echo 'LIST:[-999]="${LIST:[-999]}"' - @echo 'LONGLIST:[17]="${LONGLIST:[17]}"' - @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"' - @echo 'LONGLIST:[021]="${LONGLIST:[021]}"' - @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"' - @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"' - @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"' - @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"' - @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"' - @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"' - @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"' - @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"' - @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"' - @echo 'LONGLIST:[012..0x12]="${LONGLIST:[012..0x12]}"' - -mod-squarebrackets-start-end: - @echo 'LIST:[1.]="${LIST:[1.]}" is an error' - @echo 'LIST:[1..]="${LIST:[1..]}" is an error' - @echo 'LIST:[1.. ]="${LIST:[1.. ]}" is an error' - @echo 'LIST:[1..1]="${LIST:[1..1]}"' - @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error' - @echo 'LIST:[1..2]="${LIST:[1..2]}"' - @echo 'LIST:[2..1]="${LIST:[2..1]}"' - @echo 'LIST:[3..-2]="${LIST:[3..-2]}"' - @echo 'LIST:[-4..4]="${LIST:[-4..4]}"' - @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error' - @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error' - @echo 'LIST:[-1..1]="${LIST:[-1..1]}"' - @echo 'LIST:[0..0]="${LIST:[0..0]}"' - @echo 'LIST:[3..99]="${LIST:[3..99]}"' - @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"' - @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"' - -mod-squarebrackets-nested: - @echo 'HASH="${HASH}" == "#" ?' - @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"' - @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"' - @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"' - @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"' - @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"' - @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"' - @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"' - @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error' - @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"' - @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"' - @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"' - -mod-squarebrackets-space: - # As of 2020-11-01, it is possible to have spaces before the numbers - # but not after them. This is an unintended side-effect of using - # strtol for parsing the numbers. - @echo 'LIST:[ -1.. +3]="${LIST:[ -1.. +3]}"' - -mod-C-W: - @echo 'LIST:C/ /,/="${LIST:C/ /,/}"' - @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"' - @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"' - @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"' - @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"' - -mod-S-W: - @echo 'LIST:S/ /,/="${LIST:S/ /,/}"' - @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"' - @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"' - @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"' - @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"' - -mod-tW-tw: - @echo 'LIST:tW="${LIST:tW}"' - @echo 'LIST:tw="${LIST:tw}"' - @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"' - @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"' - @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"' - @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"' - @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"' - @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"' - @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"' - @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"' diff --git a/unit-tests/opt-debug-cond.exp b/unit-tests/opt-debug-cond.exp index 39a9383953dd..c2bd1e168bcf 100644 --- a/unit-tests/opt-debug-cond.exp +++ b/unit-tests/opt-debug-cond.exp @@ -1 +1,6 @@ +CondParser_Eval: ${:U12345} > ${:U55555} +lhs = 12345.000000, rhs = 55555.000000, op = > +CondParser_Eval: "string" != "string" +lhs = "string", rhs = "string", op = != +CondParser_Eval: "nonempty" exit status 0 diff --git a/unit-tests/opt-debug-cond.mk b/unit-tests/opt-debug-cond.mk index 2b9d1029c7d9..056996cf0ece 100644 --- a/unit-tests/opt-debug-cond.mk +++ b/unit-tests/opt-debug-cond.mk @@ -1,10 +1,24 @@ -# $NetBSD: opt-debug-cond.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# $NetBSD: opt-debug-cond.mk,v 1.2 2022/01/23 16:09:38 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 +.MAKEFLAGS: -dc -all: - @:; +# expect: CondParser_Eval: ${:U12345} > ${:U55555} +# expect: lhs = 12345.000000, rhs = 55555.000000, op = > +.if ${:U12345} > ${:U55555} + +# expect: CondParser_Eval: "string" != "string" +# expect: lhs = "string", rhs = "string", op = != +.elif "string" != "string" + +# expect: CondParser_Eval: "nonempty" +.elif "nonempty" + +.endif + +.MAKEFLAGS: -d0 + +all: .PHONY diff --git a/unit-tests/opt-debug-curdir.mk b/unit-tests/opt-debug-curdir.mk index 3c37d2988675..ac5750e7e18c 100644 --- a/unit-tests/opt-debug-curdir.mk +++ b/unit-tests/opt-debug-curdir.mk @@ -1,8 +1,8 @@ -# $NetBSD: opt-debug-curdir.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# $NetBSD: opt-debug-curdir.mk,v 1.2 2022/01/23 16:09:38 rillig Exp $ # -# Tests for the -dC command line option, which does nothing, as of 2020-09-05. +# Tests for the -dC command line option, which does nothing, as of 2020-09-05, +# as the string "DEBUG(CWD" does not occur in the source code. -# TODO: Implementation +.MAKEFLAGS: -dC -all: - @:; +all: .PHONY diff --git a/unit-tests/opt-debug-file.exp b/unit-tests/opt-debug-file.exp index 39a9383953dd..8ff220b3f541 100644 --- a/unit-tests/opt-debug-file.exp +++ b/unit-tests/opt-debug-file.exp @@ -1 +1,12 @@ -exit status 0 +make: "opt-debug-file.mk" line 43: This goes to stderr only, once. +make: "opt-debug-file.mk" line 45: This goes to stderr only, once. +make: "opt-debug-file.mk" line 47: This goes to stderr, and in addition to the debug log. +CondParser_Eval: ${:!cat opt-debug-file.debuglog!:Maddition:[#]} != 1 +lhs = 1.000000, rhs = 1.000000, op = != +make: Missing delimiter for modifier ':S' +make: Missing delimiter for modifier ':S' +make: Missing delimiter for modifier ':S' +CondParser_Eval: ${:!cat opt-debug-file.debuglog!:Mdelimiter:[#]} != 1 +lhs = 1.000000, rhs = 1.000000, op = != +Cannot open debug file "/nonexistent-6f21c672-a22d-4ef7/opt-debug-file.debuglog" +exit status 2 diff --git a/unit-tests/opt-debug-file.mk b/unit-tests/opt-debug-file.mk index 1ed477ef3c40..b878c2bcf734 100644 --- a/unit-tests/opt-debug-file.mk +++ b/unit-tests/opt-debug-file.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-debug-file.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# $NetBSD: opt-debug-file.mk,v 1.8 2022/01/11 19:47:34 rillig Exp $ # # Tests for the -dF command line option, which redirects the debug log # to a file instead of writing it to stderr. @@ -11,14 +11,16 @@ 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. +# Before main.c 1.362 from 2020-10-03, it was not possible to disable debug +# logging again. Since then, an easier way is the undocumented option '-d0'. .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. +# when it is used in the condition below. Therefore, be careful when storing +# untrusted input in variables. #.MAKEFLAGS: -dc -dFstderr .if !${DEBUG_OUTPUT:tW:M*VAR = value expanded*} . error ${DEBUG_OUTPUT} @@ -26,12 +28,44 @@ DEBUG_OUTPUT:= ${:!cat opt-debug-file.debuglog!} # 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 '\$'. +# +# XXX: In the :M modifier, a dollar is escaped using '$$', not '\$'. This +# escaping scheme unnecessarily differs from all other modifiers. .if !${:!cat opt-debug-file.debuglog!:tW:M*VAR = value $${:Uexpanded}*} . error .endif -_!= rm opt-debug-file.debuglog +.MAKEFLAGS: -d0 -all: - @:; + +# See Parse_Error. +.MAKEFLAGS: -dFstdout +. info This goes to stderr only, once. +.MAKEFLAGS: -dFstderr +. info This goes to stderr only, once. +.MAKEFLAGS: -dFopt-debug-file.debuglog +. info This goes to stderr, and in addition to the debug log. +.MAKEFLAGS: -dFstderr -d0c +.if ${:!cat opt-debug-file.debuglog!:Maddition:[#]} != 1 +. error +.endif + + +# See ApplyModifier_Subst, which calls Error. +.MAKEFLAGS: -dFstdout +: This goes to stderr only, once. ${:U:S +.MAKEFLAGS: -dFstderr +: This goes to stderr only, once. ${:U:S +.MAKEFLAGS: -dFopt-debug-file.debuglog +: This goes to stderr, and in addition to the debug log. ${:U:S +.MAKEFLAGS: -dFstderr -d0c +.if ${:!cat opt-debug-file.debuglog!:Mdelimiter:[#]} != 1 +. error +.endif + + +# If the debug log file cannot be opened, make prints an error message and +# exits immediately since the debug log file is usually selected from the +# command line. +_:= ${:!rm opt-debug-file.debuglog!} +.MAKEFLAGS: -dF/nonexistent-6f21c672-a22d-4ef7/opt-debug-file.debuglog diff --git a/unit-tests/opt-debug-hash.exp b/unit-tests/opt-debug-hash.exp index 39a9383953dd..b239399ec44d 100644 --- a/unit-tests/opt-debug-hash.exp +++ b/unit-tests/opt-debug-hash.exp @@ -1 +1,6 @@ -exit status 0 +make: "opt-debug-hash.mk" line 11: Missing argument for ".error" +make: Fatal errors encountered -- cannot continue +HashTable targets: size=16 numEntries=0 maxchain=0 +HashTable Global variables: size=16 numEntries= maxchain=3 +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/opt-debug-hash.mk b/unit-tests/opt-debug-hash.mk index c8cb99acd261..8b757ff3f290 100644 --- a/unit-tests/opt-debug-hash.mk +++ b/unit-tests/opt-debug-hash.mk @@ -1,10 +1,11 @@ -# $NetBSD: opt-debug-hash.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# $NetBSD: opt-debug-hash.mk,v 1.3 2022/01/22 18:59:24 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 +.MAKEFLAGS: -dh -all: - @:; +# Force a parse error, to demonstrate the newline character in the diagnostic +# that had been missing before parse.c 1.655 from 2022-01-22. +.error diff --git a/unit-tests/opt-debug-parse.exp b/unit-tests/opt-debug-parse.exp index 39a9383953dd..0e11024647a1 100644 --- a/unit-tests/opt-debug-parse.exp +++ b/unit-tests/opt-debug-parse.exp @@ -1 +1,26 @@ +Parse_PushInput: .for loop in opt-debug-parse.mk, line 16 +SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' +Parsing line 20: .info trace with multi-line .for loop head +make: "opt-debug-parse.mk" line 20: trace with multi-line .for loop head + in .for loop from opt-debug-parse.mk:16 with var = value +ParseEOF: returning to file opt-debug-parse.mk, line 22 +SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' +Parsing line 25: .include "/dev/null" +Parse_PushInput: file /dev/null, line 1 +SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `null' +SetFilenameVars: ${.INCLUDEDFROMDIR} = ${.INCLUDEDFROMFILE} = `opt-debug-parse.mk' +ParseEOF: returning to file opt-debug-parse.mk, line 26 +SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' +Parse_PushInput: .for loop in opt-debug-parse.mk, line 30 +SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' +Parsing line 31: .info trace +make: "opt-debug-parse.mk" line 31: trace + in .for loop from opt-debug-parse.mk:30 with a = 1, b = 2, c = 3 +Parsing line 31: .info trace +make: "opt-debug-parse.mk" line 31: trace + in .for loop from opt-debug-parse.mk:30 with a = 4, b = 5, c = 6 +ParseEOF: returning to file opt-debug-parse.mk, line 33 +SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' +Parsing line 35: .MAKEFLAGS: -d0 +ParseDependency(.MAKEFLAGS: -d0) exit status 0 diff --git a/unit-tests/opt-debug-parse.mk b/unit-tests/opt-debug-parse.mk index 3427b68beb96..c56b46c261b3 100644 --- a/unit-tests/opt-debug-parse.mk +++ b/unit-tests/opt-debug-parse.mk @@ -1,9 +1,37 @@ -# $NetBSD: opt-debug-parse.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# $NetBSD: opt-debug-parse.mk,v 1.6 2022/01/08 23:52:26 rillig Exp $ # # Tests for the -dp command line option, which adds debug logging about # makefile parsing. +.MAKEFLAGS: -dp + # TODO: Implementation -all: - @:; +# Before parse.c 1.639 from 2022-01-08, PrintStackTrace and other diagnostics +# printed a wrong line number, using the last line before the loop body, while +# it should rather be the line number where the .for loop starts. +# +# Before parse.c 1.643 from 2022-01-08, PrintStackTrace tried to be too clever +# by merging stack trace entries, printing confusing line numbers as a result. +.for \ + var \ + in \ + value +.info trace with multi-line .for loop head +.endfor + +# Before parse.c 1.461 from 2022-01-08, the debug log said it returned to +# the line of the '.include' instead of the line following it. +.include "/dev/null" + + +# In .for loops with multiple variables, the variable details are included in +# the stack trace, just as with a single variable. +.for a b c in 1 2 3 ${:U4 5 6} +.info trace +.endfor + + +.MAKEFLAGS: -d0 + +all: .PHONY diff --git a/unit-tests/opt-debug-var.exp b/unit-tests/opt-debug-var.exp index 39a9383953dd..b8cbddeb30bb 100644 --- a/unit-tests/opt-debug-var.exp +++ b/unit-tests/opt-debug-var.exp @@ -1 +1,7 @@ +Global: ASSIGNED = value +Global: SUBST = +Global: SUBST = value +Var_Parse: y(ASSIGNED) (eval) +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-var.mk b/unit-tests/opt-debug-var.mk index 4d0ef9447324..5b0c5648ab55 100644 --- a/unit-tests/opt-debug-var.mk +++ b/unit-tests/opt-debug-var.mk @@ -1,9 +1,31 @@ -# $NetBSD: opt-debug-var.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# $NetBSD: opt-debug-var.mk,v 1.2 2022/01/23 16:09:38 rillig Exp $ # # Tests for the -dv command line option, which adds debug logging about # variable assignment and evaluation. -# TODO: Implementation +.MAKEFLAGS: -dv -all: - @:; +# expect: Global: ASSIGNED = value +ASSIGNED= value + +# TODO: Explain why the empty assignment "Global: SUBST = " is needed. +# expect: Global: SUBST = value +SUBST:= value + +.if defined(ASSIGNED) +.endif + +# The usual form of variable expressions is ${VAR}. The form $(VAR) is used +# less often as it can be visually confused with the shell construct for +# capturing the output of a subshell, which looks the same. +# +# In conditions, a call to the function 'empty' is syntactically similar to +# the form $(VAR), only that the initial '$' is the 'y' of 'empty'. +# +# expect: Var_Parse: y(ASSIGNED) (eval) +.if !empty(ASSIGNED) +.endif + +.MAKEFLAGS: -d0 + +all: .PHONY diff --git a/unit-tests/opt-debug-x-trace.exp b/unit-tests/opt-debug-x-trace.exp index 39a9383953dd..abd4a61e2d82 100644 --- a/unit-tests/opt-debug-x-trace.exp +++ b/unit-tests/opt-debug-x-trace.exp @@ -1 +1,3 @@ ++ echo 'Counting 1 2 3 4 5 6 7' +Counting 1 2 3 4 5 6 7 exit status 0 diff --git a/unit-tests/opt-debug-x-trace.mk b/unit-tests/opt-debug-x-trace.mk index 0936ba506966..54c04a9a4cf8 100644 --- a/unit-tests/opt-debug-x-trace.mk +++ b/unit-tests/opt-debug-x-trace.mk @@ -1,10 +1,12 @@ -# $NetBSD: opt-debug-x-trace.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# $NetBSD: opt-debug-x-trace.mk,v 1.2 2022/01/23 16:09:38 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 +.MAKEFLAGS: -dx -all: - @:; +# expect: + echo 'Counting 1 2 3 4 5 6 7' +# expect: Counting 1 2 3 4 5 6 7 +all: .PHONY + @echo 'Counting ${:U:range=7}' diff --git a/unit-tests/opt-define.mk b/unit-tests/opt-define.mk index ce0516ba44bc..7c4bbc179316 100644 --- a/unit-tests/opt-define.mk +++ b/unit-tests/opt-define.mk @@ -1,8 +1,28 @@ -# $NetBSD: opt-define.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-define.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $ # -# Tests for the -D command line option. +# Tests for the -D command line option, which defines global variables to the +# value 1, like in the C preprocessor. -# TODO: Implementation +.MAKEFLAGS: -DVAR -all: - @:; +# The variable has the exact value "1", not "1.0". +.if ${VAR} != "1" +. error +.endif + +# The variable can be overwritten by assigning another value to it. This +# would not be possible if the variable had been specified on the command line +# as 'VAR=1' instead of '-DVAR'. +VAR= overwritten +.if ${VAR} != "overwritten" +. error +.endif + +# The variable can be undefined. If the variable had been defined in the +# "Internal" scope instead, undefining it would have no effect. +.undef VAR +.if defined(VAR) +. error +.endif + +all: .PHONY diff --git a/unit-tests/opt-env.exp b/unit-tests/opt-env.exp index 39a9383953dd..e584a77e01b9 100644 --- a/unit-tests/opt-env.exp +++ b/unit-tests/opt-env.exp @@ -1 +1,5 @@ -exit status 0 +make: "opt-env.mk" line 9: Malformed conditional (${FROM_ENV} != value-from-env) +make: "opt-env.mk" line 16: value-from-mk + +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/opt-env.mk b/unit-tests/opt-env.mk index 32e95ef41f5a..0cfa1aa6470f 100644 --- a/unit-tests/opt-env.mk +++ b/unit-tests/opt-env.mk @@ -1,8 +1,45 @@ -# $NetBSD: opt-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-env.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $ # # Tests for the -e command line option. -# TODO: Implementation +# The variable FROM_ENV is defined in ./Makefile. -all: - @:; +.MAKEFLAGS: -e + +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# Try to override the variable; this does not have any effect. +FROM_ENV= value-from-mk +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# Try to append to the variable; this also doesn't have any effect. +FROM_ENV+= appended +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# The default assignment also cannot change the variable. +FROM_ENV?= default +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# Neither can the assignment modifiers. +.if ${FROM_ENV::=from-condition} +.endif +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# Even .undef doesn't work since it only affects the global scope, +# which is independent from the environment variables. +.undef FROM_ENV +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +all: .PHONY diff --git a/unit-tests/opt-jobs-internal.exp b/unit-tests/opt-jobs-internal.exp index 39a9383953dd..470bdbddd0f8 100644 --- a/unit-tests/opt-jobs-internal.exp +++ b/unit-tests/opt-jobs-internal.exp @@ -1 +1,6 @@ -exit status 0 +make: internal error -- J option malformed (garbage) +usage: make [-BeikNnqrSstWwX] + [-C directory] [-D variable] [-d flags] [-f makefile] + [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file] + [-V variable] [-v variable] [variable=value] [target ...] +exit status 2 diff --git a/unit-tests/opt-jobs-internal.mk b/unit-tests/opt-jobs-internal.mk index 5426807ca98b..44755a797751 100644 --- a/unit-tests/opt-jobs-internal.mk +++ b/unit-tests/opt-jobs-internal.mk @@ -1,8 +1,9 @@ -# $NetBSD: opt-jobs-internal.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-jobs-internal.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $ # # Tests for the (intentionally undocumented) -J command line option. +# +# Only test the error handling here, the happy path is covered in other tests +# as a side effect. -# TODO: Implementation - -all: - @:; +# expect: make: internal error -- J option malformed (garbage) +.MAKEFLAGS: -Jgarbage diff --git a/unit-tests/opt-raw.mk b/unit-tests/opt-raw.mk index d3591bb99dab..91caffcd72ae 100644 --- a/unit-tests/opt-raw.mk +++ b/unit-tests/opt-raw.mk @@ -1,8 +1,14 @@ -# $NetBSD: opt-raw.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-raw.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $ # -# Tests for the -r command line option. +# Tests for the -r command line option, which skips the system-defined default +# rules from . -# TODO: Implementation +# To provide a clean testing environment without unintended side effects, +# these unit tests run make with the option '-r' by default. This means there +# are no predefined suffixes and no predefined tools. -all: - @:; +.if defined(CC) +. error +.endif + +all: .PHONY diff --git a/unit-tests/opt-silent.exp b/unit-tests/opt-silent.exp index 39a9383953dd..8863b8a2965d 100644 --- a/unit-tests/opt-silent.exp +++ b/unit-tests/opt-silent.exp @@ -1 +1,3 @@ +message +silent message exit status 0 diff --git a/unit-tests/opt-silent.mk b/unit-tests/opt-silent.mk index 7822d46ac48a..01e5b18e2b12 100644 --- a/unit-tests/opt-silent.mk +++ b/unit-tests/opt-silent.mk @@ -1,8 +1,10 @@ -# $NetBSD: opt-silent.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-silent.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $ # # Tests for the -s command line option. -# TODO: Implementation +.MAKEFLAGS: -s +# No matter whether a command is prefixed by '@' or not, it is not echoed. all: - @:; + echo 'message' + @echo 'silent message' diff --git a/unit-tests/var-class.exp b/unit-tests/opt-version.exp similarity index 93% rename from unit-tests/var-class.exp rename to unit-tests/opt-version.exp index 39a9383953dd..1636aafc11e5 100644 --- a/unit-tests/var-class.exp +++ b/unit-tests/opt-version.exp @@ -1 +1,2 @@ + exit status 0 diff --git a/unit-tests/opt-version.mk b/unit-tests/opt-version.mk new file mode 100644 index 000000000000..51a4e8a1a0aa --- /dev/null +++ b/unit-tests/opt-version.mk @@ -0,0 +1,12 @@ +# $NetBSD: opt-version.mk,v 1.1 2021/12/23 11:05:59 rillig Exp $ +# +# Tests for the command line option '--version', which outputs the version +# number of make. NetBSD's make does not have a version number, but the bmake +# distribution created from it has. + +# As of 2021-12-23, the output is a single empty line since the '--' does not +# end the command line options. Command line parsing then continues as if +# nothing had happened, and the '-version' is split into '-v ersion', which is +# interpreted as "print the expanded variable named 'ersion'". + +.MAKEFLAGS: --version diff --git a/unit-tests/opt-where-am-i.exp b/unit-tests/opt-where-am-i.exp index 39a9383953dd..e64df44b83b7 100644 --- a/unit-tests/opt-where-am-i.exp +++ b/unit-tests/opt-where-am-i.exp @@ -1 +1,4 @@ +make: Entering directory `/' +make: Leaving directory `/' +make: Leaving directory `' exit status 0 diff --git a/unit-tests/opt-where-am-i.mk b/unit-tests/opt-where-am-i.mk index 9158a598174c..f1eeca920a32 100644 --- a/unit-tests/opt-where-am-i.mk +++ b/unit-tests/opt-where-am-i.mk @@ -1,8 +1,14 @@ -# $NetBSD: opt-where-am-i.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-where-am-i.mk,v 1.4 2022/01/27 02:24:46 sjg Exp $ # -# Tests for the -w command line option. +# Tests for the -w command line option, which outputs the current directory +# at the beginning and end of running make. This is useful when building +# large source trees that involve several nested make calls. -# TODO: Implementation +# The first "Entering directory" is missing since the below .MAKEFLAGS comes +# too late for it. +.MAKEFLAGS: -w all: - @:; +.if ${.CURDIR} != "/" + @MAKE_OBJDIR_CHECK_WRITABLE=no ${MAKE} -r -f ${MAKEFILE:tA} -C / +.endif diff --git a/unit-tests/parse.exp b/unit-tests/parse.exp new file mode 100644 index 000000000000..5807f9c17e2c --- /dev/null +++ b/unit-tests/parse.exp @@ -0,0 +1,5 @@ +make: "parse.mk" line 7: Makefile appears to contain unresolved CVS/RCS/??? merge conflicts +make: "parse.mk" line 14: Makefile appears to contain unresolved CVS/RCS/??? merge conflicts +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/parse.mk b/unit-tests/parse.mk new file mode 100644 index 000000000000..9dccc7f5b7ce --- /dev/null +++ b/unit-tests/parse.mk @@ -0,0 +1,14 @@ +# $NetBSD: parse.mk,v 1.2 2022/01/22 17:10:51 rillig Exp $ +# +# Test those parts of the parsing that do not belong in any of the other +# categories. + +# expect+1: Makefile appears to contain unresolved CVS/RCS/??? merge conflicts +<<<<<< old + +# No diagnostic since the following line is parsed as a variable assignment, +# even though the variable name is empty. See also varname-empty.mk. +====== middle + +# expect+1: Makefile appears to contain unresolved CVS/RCS/??? merge conflicts +>>>>>> new diff --git a/unit-tests/posix.mk b/unit-tests/posix.mk index fc4cbead3263..43219258306e 100644 --- a/unit-tests/posix.mk +++ b/unit-tests/posix.mk @@ -1,4 +1,4 @@ -# $NetBSD: posix.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ +# $NetBSD: posix.mk,v 1.3 2022/01/23 18:15:29 rillig Exp $ all: x plus subs err @@ -14,11 +14,10 @@ plus: subs: @echo make -n - @${.MAKE} -f ${MAKEFILE} -n plus + @${.MAKE} -r -f ${MAKEFILE} -n plus @echo make -n -j1 - @${.MAKE} -f ${MAKEFILE} -n -j1 plus + @${.MAKE} -r -f ${MAKEFILE} -n -j1 plus err: @(echo Now we expect an error...; exit 1) @echo "Oops! you shouldn't see this!" - diff --git a/unit-tests/sh.mk b/unit-tests/sh.mk index f79a4099e990..3bbedf4d678a 100644 --- a/unit-tests/sh.mk +++ b/unit-tests/sh.mk @@ -1,7 +1,10 @@ -# $NetBSD: sh.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: sh.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $ # # Tests for running shell commands from the targets, or from the != variable # assignment operator or the :sh variable modifier. +# +# See also: +# var-op-shell.mk for the assignment operator '!=' # TODO: Implementation diff --git a/unit-tests/suff-incomplete.exp b/unit-tests/suff-incomplete.exp index 2331436d378e..acb5f0542dbe 100644 --- a/unit-tests/suff-incomplete.exp +++ b/unit-tests/suff-incomplete.exp @@ -1,17 +1,17 @@ -ParseReadLine (9): '.SUFFIXES:' +Parsing line 9: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -ParseReadLine (11): '.SUFFIXES: .a .b .c' +Parsing line 11: .SUFFIXES: .a .b .c ParseDependency(.SUFFIXES: .a .b .c) Adding suffix ".a" Adding suffix ".b" Adding suffix ".c" -ParseReadLine (17): '.a.b:' +Parsing line 17: .a.b: ParseDependency(.a.b:) defining transformation from `.a' to `.b' inserting ".a" (1) at end of list inserting ".b" (2) at end of list -ParseReadLine (21): '.a.c: ${.PREFIX}.dependency' +Parsing line 21: .a.c: ${.PREFIX}.dependency deleting incomplete transformation from `.a' to `.b' ParseDependency(.a.c: ${.PREFIX}.dependency) defining transformation from `.a' to `.c' @@ -20,10 +20,10 @@ inserting ".c" (3) at end of list # LinkSource: added child .a.c - ${.PREFIX}.dependency # .a.c, unmade, type OP_DEPENDS|OP_TRANSFORM, flags none # ${.PREFIX}.dependency, unmade, type none, flags none -ParseReadLine (23): '.DEFAULT:' +Parsing line 23: .DEFAULT: transformation .a.c complete ParseDependency(.DEFAULT:) -ParseReadLine (24): ' : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default.' +Parsing line 24: : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default. transformation .DEFAULT complete Wildcard expanding "all"... SuffFindDeps "all" diff --git a/unit-tests/suff-main-several.exp b/unit-tests/suff-main-several.exp index b20f5ecf1143..7d499bcf5040 100644 --- a/unit-tests/suff-main-several.exp +++ b/unit-tests/suff-main-several.exp @@ -1,11 +1,11 @@ -ParseReadLine (8): '.1.2 .1.3 .1.4:' +Parsing line 8: .1.2 .1.3 .1.4: ParseDependency(.1.2 .1.3 .1.4:) Setting main node to ".1.2" -ParseReadLine (9): ' : Making ${.TARGET} from ${.IMPSRC}.' -ParseReadLine (14): 'next-main:' +Parsing line 9: : Making ${.TARGET} from ${.IMPSRC}. +Parsing line 14: next-main: ParseDependency(next-main:) -ParseReadLine (15): ' : Making ${.TARGET}' -ParseReadLine (19): '.SUFFIXES: .1 .2 .3 .4' +Parsing line 15: : Making ${.TARGET} +Parsing line 19: .SUFFIXES: .1 .2 .3 .4 ParseDependency(.SUFFIXES: .1 .2 .3 .4) Adding suffix ".1" Adding suffix ".2" @@ -26,42 +26,42 @@ defining transformation from `.1' to `.4' inserting ".1" (1) at end of list inserting ".4" (4) at end of list Setting main node to "next-main" -ParseReadLine (24): '.SUFFIXES:' +Parsing line 24: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -ParseReadLine (32): '.SUFFIXES: .4 .3 .2 .1' +Parsing line 32: .SUFFIXES: .4 .3 .2 .1 ParseDependency(.SUFFIXES: .4 .3 .2 .1) Adding suffix ".4" Adding suffix ".3" Adding suffix ".2" Adding suffix ".1" -ParseReadLine (33): '.SUFFIXES:' +Parsing line 33: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -ParseReadLine (34): '.SUFFIXES: .1 .2 .3 .4' +Parsing line 34: .SUFFIXES: .1 .2 .3 .4 ParseDependency(.SUFFIXES: .1 .2 .3 .4) Adding suffix ".1" Adding suffix ".2" Adding suffix ".3" Adding suffix ".4" -ParseReadLine (35): '.SUFFIXES:' +Parsing line 35: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -ParseReadLine (36): '.SUFFIXES: .4 .3 .2 .1' +Parsing line 36: .SUFFIXES: .4 .3 .2 .1 ParseDependency(.SUFFIXES: .4 .3 .2 .1) Adding suffix ".4" Adding suffix ".3" Adding suffix ".2" Adding suffix ".1" -ParseReadLine (38): 'suff-main-several.1:' +Parsing line 38: suff-main-several.1: ParseDependency(suff-main-several.1:) -ParseReadLine (39): ' : Making ${.TARGET} out of nothing.' -ParseReadLine (40): 'next-main: suff-main-several.{2,3,4}' +Parsing line 39: : Making ${.TARGET} out of nothing. +Parsing line 40: next-main: suff-main-several.{2,3,4} ParseDependency(next-main: suff-main-several.{2,3,4}) # LinkSource: added child next-main - suff-main-several.{2,3,4} # next-main, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # suff-main-several.{2,3,4}, unmade, type none, flags none -ParseReadLine (42): '.MAKEFLAGS: -d0 -dg1' +Parsing line 42: .MAKEFLAGS: -d0 -dg1 ParseDependency(.MAKEFLAGS: -d0 -dg1) #*** Input graph: # .1.2, unmade, type OP_TRANSFORM, flags none diff --git a/unit-tests/suff-rebuild.exp b/unit-tests/suff-rebuild.exp index 7ef53ae2e151..8c0979537524 100644 --- a/unit-tests/suff-rebuild.exp +++ b/unit-tests/suff-rebuild.exp @@ -1,36 +1,36 @@ -ParseReadLine (10): '.SUFFIXES:' +Parsing line 10: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -ParseReadLine (12): '.SUFFIXES: .a .b .c' +Parsing line 12: .SUFFIXES: .a .b .c ParseDependency(.SUFFIXES: .a .b .c) Adding suffix ".a" Adding suffix ".b" Adding suffix ".c" -ParseReadLine (14): 'suff-rebuild-example.a:' +Parsing line 14: suff-rebuild-example.a: ParseDependency(suff-rebuild-example.a:) Adding "suff-rebuild-example.a" to all targets. -ParseReadLine (15): ' : Making ${.TARGET} out of nothing.' -ParseReadLine (17): '.a.b:' +Parsing line 15: : Making ${.TARGET} out of nothing. +Parsing line 17: .a.b: ParseDependency(.a.b:) defining transformation from `.a' to `.b' inserting ".a" (1) at end of list inserting ".b" (2) at end of list -ParseReadLine (18): ' : Making ${.TARGET} from ${.IMPSRC}.' -ParseReadLine (19): '.b.c:' +Parsing line 18: : Making ${.TARGET} from ${.IMPSRC}. +Parsing line 19: .b.c: transformation .a.b complete ParseDependency(.b.c:) defining transformation from `.b' to `.c' inserting ".b" (2) at end of list inserting ".c" (3) at end of list -ParseReadLine (20): ' : Making ${.TARGET} from ${.IMPSRC}.' -ParseReadLine (21): '.c:' +Parsing line 20: : Making ${.TARGET} from ${.IMPSRC}. +Parsing line 21: .c: transformation .b.c complete ParseDependency(.c:) defining transformation from `.c' to `' inserting ".c" (3) at end of list inserting "" (0) at end of list -ParseReadLine (22): ' : Making ${.TARGET} from ${.IMPSRC}.' -ParseReadLine (44): '.SUFFIXES: .c .b .a' +Parsing line 22: : Making ${.TARGET} from ${.IMPSRC}. +Parsing line 44: .SUFFIXES: .c .b .a transformation .c complete ParseDependency(.SUFFIXES: .c .b .a) Adding ".END" to all targets. diff --git a/unit-tests/var-class-cmdline.exp b/unit-tests/var-class-cmdline.exp deleted file mode 100644 index 6df2155ca7eb..000000000000 --- a/unit-tests/var-class-cmdline.exp +++ /dev/null @@ -1,4 +0,0 @@ -make: "var-class-cmdline.mk" line 67: global -make: "var-class-cmdline.mk" line 76: makeflags -makeflags -exit status 0 diff --git a/unit-tests/var-class-global.mk b/unit-tests/var-class-global.mk deleted file mode 100644 index 81345ffda463..000000000000 --- a/unit-tests/var-class-global.mk +++ /dev/null @@ -1,8 +0,0 @@ -# $NetBSD: var-class-global.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ -# -# Tests for global variables, which are the most common variables. - -# TODO: Implementation - -all: - @:; diff --git a/unit-tests/var-class-local.exp b/unit-tests/var-class-local.exp deleted file mode 100644 index db85b47cae06..000000000000 --- a/unit-tests/var-class-local.exp +++ /dev/null @@ -1,5 +0,0 @@ -: Making var-class-local.c out of nothing. -: Making var-class-local.o from var-class-local.c. -: Making basename "var-class-local.o" in "." from "var-class-local.c" in ".". -: all overwritten -exit status 0 diff --git a/unit-tests/var-class-local.mk b/unit-tests/var-class-local.mk deleted file mode 100644 index f9d56e539ff0..000000000000 --- a/unit-tests/var-class-local.mk +++ /dev/null @@ -1,45 +0,0 @@ -# $NetBSD: var-class-local.mk,v 1.5 2020/11/05 18:08:39 rillig Exp $ -# -# Tests for target-local variables, such as ${.TARGET} or $@. - -# TODO: Implementation - -# Ensure that the name of the variable is exactly the given one. -# The variable "@" is an alias for ".TARGET", so the implementation might -# canonicalize these aliases at some point, and that might be surprising. -# This aliasing happens for single-character variable names like $@ or $< -# (see VarFind, CanonicalVarname), but not for braced or parenthesized -# expressions like ${@}, ${.TARGET} ${VAR:Mpattern} (see Var_Parse, -# ParseVarname). -.if ${@:L} != "@" -. error -.endif -.if ${.TARGET:L} != ".TARGET" -. error -.endif -.if ${@F:L} != "@F" -. error -.endif -.if ${@D:L} != "@D" -. error -.endif - -all: - -.SUFFIXES: .c .o - -var-class-local.c: - : Making ${.TARGET} out of nothing. - -.c.o: - : Making ${.TARGET} from ${.IMPSRC}. - - # The local variables @F, @D, &2!} # is ignored as well. To do that, it is necessary to step through the code of # each modifier. +# TODO: Test the modifiers in the same order as they appear in ApplyModifier. + .if 0 && ${FAIL} .endif diff --git a/unit-tests/var-op-expand.mk b/unit-tests/var-op-expand.mk index 237f7baf1c62..1d905aeb3757 100644 --- a/unit-tests/var-op-expand.mk +++ b/unit-tests/var-op-expand.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-expand.mk,v 1.15 2021/11/30 23:52:19 rillig Exp $ +# $NetBSD: var-op-expand.mk,v 1.16 2021/12/28 10:47:00 rillig Exp $ # # Tests for the := variable assignment operator, which expands its # right-hand side. @@ -9,7 +9,7 @@ # Force the test results to be independent of the default value of this # setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake # distribution and pkgsrc/devel/bmake. -.MAKE.SAVE_DOLLARS:= yes +.MAKE.SAVE_DOLLARS:= yes # If the right-hand side does not contain a dollar sign, the ':=' assignment # operator has the same effect as the '=' assignment operator. diff --git a/unit-tests/var-op-shell.exp b/unit-tests/var-op-shell.exp index 890bfa43c38e..0e9bd2cbc35a 100644 --- a/unit-tests/var-op-shell.exp +++ b/unit-tests/var-op-shell.exp @@ -1,7 +1,11 @@ -make: "var-op-shell.mk" line 28: warning: "echo "failed"; false" returned non-zero status -make: "var-op-shell.mk" line 34: warning: "false" returned non-zero status -make: "var-op-shell.mk" line 56: warning: "kill $$" exited on a signal +make: "var-op-shell.mk" line 31: warning: "echo "failed"; false" returned non-zero status +make: "var-op-shell.mk" line 37: warning: "false" returned non-zero status +make: "var-op-shell.mk" line 59: warning: "kill $$" exited on a signal /bin/no/such/command: not found -make: "var-op-shell.mk" line 62: warning: "/bin/no/such/command" returned non-zero status +make: "var-op-shell.mk" line 65: warning: "/bin/no/such/command" returned non-zero status stderr +Capturing the output of command "echo '$$$$'" +Global: OUTPUT = $$$$ +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-shell.mk b/unit-tests/var-op-shell.mk index 0fdc54fc6041..bd2a48f17cc4 100644 --- a/unit-tests/var-op-shell.mk +++ b/unit-tests/var-op-shell.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-shell.mk,v 1.4 2021/02/06 04:55:08 sjg Exp $ +# $NetBSD: var-op-shell.mk,v 1.6 2022/01/10 20:32:29 rillig Exp $ # # Tests for the != variable assignment operator, which runs its right-hand # side through the shell. @@ -15,7 +15,7 @@ OUTPUT!= echo "success"'ful' # an empty output produced the error message "Couldn't read shell's output # for \"%s\"". # -# The error message is still there but reserved for technical errors. +# The error message is still in Cmd_Exec but reserved for technical errors. # It may be possible to trigger the error message by killing the shell after # reading part of its output. OUTPUT!= true @@ -24,7 +24,10 @@ OUTPUT!= true .endif # The output of a shell command that failed is processed nevertheless. -# TODO: Make this an error in lint mode. +# Unlike the other places that run external commands (expression modifier +# '::!=', expression modifier ':!...!'), a failed command generates only a +# warning, not an "error". These "errors" are ignored in default mode, for +# compatibility, but not in lint mode (-dL). OUTPUT!= echo "failed"; false .if ${OUTPUT} != "failed" . error @@ -78,4 +81,10 @@ OUTPUT!= echo '$$$$$$$$' . error .endif + +# As a debugging aid, log the exact command that is run via the shell. +.MAKEFLAGS: -dv +OUTPUT!= echo '$$$$$$$$' +.MAKEFLAGS: -d0 + all: diff --git a/unit-tests/var-op-sunsh.mk b/unit-tests/var-op-sunsh.mk index 0d15b8c88b92..956c1192616c 100644 --- a/unit-tests/var-op-sunsh.mk +++ b/unit-tests/var-op-sunsh.mk @@ -1,8 +1,8 @@ -# $NetBSD: var-op-sunsh.mk,v 1.8 2021/04/04 10:13:09 rillig Exp $ +# $NetBSD: var-op-sunsh.mk,v 1.9 2022/01/16 09:38:04 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. +# assignment operator, adopted from SUN make. .MAKEFLAGS: -dL # Enable sane error messages @@ -24,9 +24,12 @@ VAR :sh = echo colon-sh-spaced # 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. +# Since 2020-10-04, this is a normal variable assignment to the variable named +# 'VAR:shell', using the '=' assignment operator. VAR:shell= echo colon-shell +# The variable name needs to be generated using a ${:U...} expression because +# it is not possible to express the ':' as part of a literal variable name, +# see ParseVarname. .if ${${:UVAR\:shell}} != "echo colon-shell" . error .endif @@ -95,30 +98,52 @@ VAR :sh :sh :sh :sh= echo multiple # 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. +# +# Since the text to the left of the assignment operator '=' does not end with +# ':sh', the effective assignment operator becomes '=', not '!='. 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_Var. +# parentheses. Braces and parentheses are interchangeable, that is, a '(' can +# be closed by either ')' or '}'. These braces and parentheses are only +# counted by Parse_IsVar, in particular Parse_Var doesn't see them. 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. +# The assignment modifier ':sh' can be combined with the assignment operator +# '+='. In such a case the ':sh' is silently ignored, and the effective +# assignment operator is '+='. # -# XXX: This combination should not be allowed at all. +# XXX: This combination should not be allowed at all, as it is confusing. VAR= one VAR :sh += echo two .if ${VAR} != "one echo two" . error ${VAR} .endif -# TODO: test VAR:sh!=command +# The assignment modifier ':sh' can be combined with the assignment operator +# '!='. In such a case the ':sh' is silently ignored, and the effective +# assignment operator is '!=', just like with '+=' or the other compound +# assignment operators. +# +# XXX: This combination should not be allowed at all, as it is confusing. +VAR :sh != echo echo echo echo spaces-around +.if ${VAR} != "echo echo echo spaces-around" +. error ${VAR} +.endif -all: - @:; +# If there is no space between the variable name and the assignment modifier +# ':sh', the ':sh' becomes part of the variable name, as the parser only +# expects a single assignment modifier to the left of the '=', which in this +# case is the '!'. +VAR:sh != echo echo echo echo space-after +.if ${${:UVAR\:sh}} != "echo echo echo space-after" +. error ${${:UVAR\:sh}} +.endif + +all: .PHONY diff --git a/unit-tests/var-recursive.exp b/unit-tests/var-recursive.exp index 9739d8bcca13..44c381f94ff9 100644 --- a/unit-tests/var-recursive.exp +++ b/unit-tests/var-recursive.exp @@ -1,12 +1,19 @@ make: "var-recursive.mk" line 20: still there Variable DIRECT is recursive. + in var-recursive.mk:21 make: stopped in unit-tests Variable INDIRECT1 is recursive. + in var-recursive.mk:28 make: stopped in unit-tests make: "var-recursive.mk" line 35: ok Variable V is recursive. + in var-recursive.mk:43 + +make: stopped in unit-tests +: OK +In a command near "var-recursive.mk" line 55: Variable VAR is recursive. make: stopped in unit-tests exit status 0 diff --git a/unit-tests/var-recursive.mk b/unit-tests/var-recursive.mk index da1fb696d655..1825c8a63120 100644 --- a/unit-tests/var-recursive.mk +++ b/unit-tests/var-recursive.mk @@ -1,9 +1,9 @@ -# $NetBSD: var-recursive.mk,v 1.2 2020/10/31 13:45:00 rillig Exp $ +# $NetBSD: var-recursive.mk,v 1.4 2022/01/29 10:21:26 rillig Exp $ # # Tests for variable expressions that refer to themselves and thus # cannot be evaluated. -TESTS= direct indirect conditional short +TESTS= direct indirect conditional short target # Since make exits immediately when it detects a recursive expression, # the actual tests are run in sub-makes. @@ -42,6 +42,18 @@ CONDITIONAL= ${1:?ok:${CONDITIONAL}} V= $V . info $V +.elif ${TEST} == target + +# If a recursive variable is accessed in a command of a target, the makefiles +# are not parsed anymore, so there is no location information from the +# .includes and .for directives. In such a case, use the location of the last +# command of the target to provide at least a hint to the location. +VAR= ${VAR} +target: + : OK + : ${VAR} + : OK + .else . error Unknown test "${TEST}" .endif diff --git a/unit-tests/var-scope-cmdline.exp b/unit-tests/var-scope-cmdline.exp new file mode 100644 index 000000000000..a1227a1dd1f2 --- /dev/null +++ b/unit-tests/var-scope-cmdline.exp @@ -0,0 +1,4 @@ +make: "var-scope-cmdline.mk" line 67: global +make: "var-scope-cmdline.mk" line 76: makeflags +makeflags +exit status 0 diff --git a/unit-tests/var-class-cmdline.mk b/unit-tests/var-scope-cmdline.mk similarity index 98% rename from unit-tests/var-class-cmdline.mk rename to unit-tests/var-scope-cmdline.mk index 679e051bb242..1f4a3e700253 100644 --- a/unit-tests/var-class-cmdline.mk +++ b/unit-tests/var-scope-cmdline.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-class-cmdline.mk,v 1.5 2021/02/23 21:59:31 rillig Exp $ +# $NetBSD: var-scope-cmdline.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $ # # Tests for variables specified on the command line. # diff --git a/unit-tests/envfirst.exp b/unit-tests/var-scope-env.exp similarity index 100% rename from unit-tests/envfirst.exp rename to unit-tests/var-scope-env.exp diff --git a/unit-tests/var-class-env.mk b/unit-tests/var-scope-env.mk similarity index 60% rename from unit-tests/var-class-env.mk rename to unit-tests/var-scope-env.mk index 6e6b4891d3fd..d58ad7c6f2e2 100644 --- a/unit-tests/var-class-env.mk +++ b/unit-tests/var-scope-env.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-class-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-scope-env.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $ # # Tests for variables specified in the process environment. diff --git a/unit-tests/var-class-env.exp b/unit-tests/var-scope-global.exp similarity index 100% rename from unit-tests/var-class-env.exp rename to unit-tests/var-scope-global.exp diff --git a/unit-tests/var-scope-global.mk b/unit-tests/var-scope-global.mk new file mode 100644 index 000000000000..02ed8fe701c0 --- /dev/null +++ b/unit-tests/var-scope-global.mk @@ -0,0 +1,18 @@ +# $NetBSD: var-scope-global.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $ +# +# Tests for global variables, which are the most common variables. + +# Global variables can be assigned and appended to. +GLOBAL= value +GLOBAL+= addition +.if ${GLOBAL} != "value addition" +. error +.endif + +# Global variables can be removed from their scope. +.undef GLOBAL +.if defined(GLOBAL) +. error +.endif + +all: .PHONY diff --git a/unit-tests/var-class-global.exp b/unit-tests/var-scope-local-legacy.exp similarity index 100% rename from unit-tests/var-class-global.exp rename to unit-tests/var-scope-local-legacy.exp diff --git a/unit-tests/var-class-local-legacy.mk b/unit-tests/var-scope-local-legacy.mk similarity index 64% rename from unit-tests/var-class-local-legacy.mk rename to unit-tests/var-scope-local-legacy.mk index bfd9733fd42b..e519d63e7c51 100644 --- a/unit-tests/var-class-local-legacy.mk +++ b/unit-tests/var-scope-local-legacy.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-class-local-legacy.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-scope-local-legacy.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $ # # Tests for legacy target-local variables, such as ${?@AZ[\]^_`az{|}~ exit status 0 diff --git a/unit-tests/varmod-quote-dollar.mk b/unit-tests/varmod-quote-dollar.mk index fedbe8a10f4b..3316b04bed1e 100644 --- a/unit-tests/varmod-quote-dollar.mk +++ b/unit-tests/varmod-quote-dollar.mk @@ -1,10 +1,10 @@ -# $NetBSD: varmod-quote-dollar.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varmod-quote-dollar.mk,v 1.3 2022/01/22 17:10:51 rillig Exp $ # # Tests for the :q variable modifier, which quotes the string for the shell # and doubles dollar signs, to prevent them from being interpreted by a # child process of make. -# TODO: Implementation +ASCII_CHARS= ${.newline} !"\#$$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~ all: - @:; + @${MAKE} -r -f /dev/null CHARS=${ASCII_CHARS:q} -V CHARS diff --git a/unit-tests/varmod-select-words.exp b/unit-tests/varmod-select-words.exp index 39a9383953dd..02e9974c02d6 100644 --- a/unit-tests/varmod-select-words.exp +++ b/unit-tests/varmod-select-words.exp @@ -1 +1,126 @@ +make: Bad modifier ":[]" for variable "LIST" +LIST:[]="" is an error +LIST:[0]="one two three four five six" +LIST:[0x0]="one two three four five six" +LIST:[000]="one two three four five six" +LIST:[*]="one two three four five six" +LIST:[@]="one two three four five six" +LIST:[0]:C/ /,/="one,two three four five six" +LIST:[0]:C/ /,/g="one,two,three,four,five,six" +LIST:[0]:C/ /,/1g="one,two,three,four,five,six" +LIST:[*]:C/ /,/="one,two three four five six" +LIST:[*]:C/ /,/g="one,two,three,four,five,six" +LIST:[*]:C/ /,/1g="one,two,three,four,five,six" +LIST:[@]:C/ /,/="one two three four five six" +LIST:[@]:C/ /,/g="one two three four five six" +LIST:[@]:C/ /,/1g="one two three four five six" +LIST:[@]:[0]:C/ /,/="one,two three four five six" +LIST:[0]:[@]:C/ /,/="one two three four five six" +LIST:[@]:[*]:C/ /,/="one,two three four five six" +LIST:[*]:[@]:C/ /,/="one two three four five six" +EMPTY="" +EMPTY:[#]="1" == 1 ? +ESCAPEDSPACE="\ " +ESCAPEDSPACE:[#]="1" == 1 ? +REALLYSPACE=" " +REALLYSPACE:[#]="1" == 1 ? +LIST:[#]="6" +LIST:[0]:[#]="1" == 1 ? +LIST:[*]:[#]="1" == 1 ? +LIST:[@]:[#]="6" +LIST:[1]:[#]="1" +LIST:[1..3]:[#]="3" +EMPTY:[1]="" +ESCAPEDSPACE="\ " +ESCAPEDSPACE:[1]="\ " +REALLYSPACE=" " +REALLYSPACE:[1]="" == "" ? +REALLYSPACE:[*]:[1]=" " == " " ? +LIST:[1]="one" +make: Bad modifier ":[1.]" for variable "LIST" +LIST:[1.]="" is an error +make: Bad modifier ":[1]." for variable "LIST" +LIST:[1].="}" is an error +LIST:[2]="two" +LIST:[6]="six" +LIST:[7]="" +LIST:[999]="" +make: Bad modifier ":[-]" for variable "LIST" +LIST:[-]="" is an error +make: Bad modifier ":[--]" for variable "LIST" +LIST:[--]="" is an error +LIST:[-1]="six" +LIST:[-2]="five" +LIST:[-6]="one" +LIST:[-7]="" +LIST:[-999]="" +LONGLIST:[17]="17" +LONGLIST:[0x11]="17" +LONGLIST:[021]="17" +LIST:[0]:[1]="one two three four five six" +LIST:[*]:[1]="one two three four five six" +LIST:[@]:[1]="one" +LIST:[0]:[2]="" +LIST:[*]:[2]="" +LIST:[@]:[2]="two" +LIST:[*]:C/ /,/:[2]="" +LIST:[*]:C/ /,/:[*]:[2]="" +LIST:[*]:C/ /,/:[@]:[2]="three" +LONGLIST:[012..0x12]="10 11 12 13 14 15 16 17 18" +make: Bad modifier ":[1.]" for variable "LIST" +LIST:[1.]="" is an error +make: Bad modifier ":[1..]" for variable "LIST" +LIST:[1..]="" is an error +make: Bad modifier ":[1.. ]" for variable "LIST" +LIST:[1.. ]="" is an error +LIST:[1..1]="one" +make: Bad modifier ":[1..1.]" for variable "LIST" +LIST:[1..1.]="" is an error +LIST:[1..2]="one two" +LIST:[2..1]="two one" +LIST:[3..-2]="three four five" +LIST:[-4..4]="three four" +make: Bad modifier ":[0..1]" for variable "LIST" +LIST:[0..1]="" is an error +make: Bad modifier ":[-1..0]" for variable "LIST" +LIST:[-1..0]="" is an error +LIST:[-1..1]="six five four three two one" +LIST:[0..0]="one two three four five six" +LIST:[3..99]="three four five six" +LIST:[-3..-99]="four three two one" +LIST:[-99..-3]="one two three four" +HASH="#" == "#" ? +LIST:[${HASH}]="6" +LIST:[${ZERO}]="one two three four five six" +LIST:[${ZERO}x${ONE}]="one" +LIST:[${ONE}]="one" +LIST:[${MINUSONE}]="six" +LIST:[${STAR}]="one two three four five six" +LIST:[${AT}]="one two three four five six" +make: Bad modifier ":[${EMPTY" for variable "LIST" +LIST:[${EMPTY}]="" is an error +LIST:[${LONGLIST:[21]:S/2//}]="one" +LIST:[${LIST:[#]}]="six" +LIST:[${LIST:[${HASH}]}]="six" +LIST:[ -1.. +3]="six five four three" +LIST:S/ /,/="one two three four five six" +LIST:S/ /,/W="one,two three four five six" +LIST:S/ /,/gW="one,two,three,four,five,six" +EMPTY:S/^/,/="," +EMPTY:S/^/,/W="," +LIST:C/ /,/="one two three four five six" +LIST:C/ /,/W="one,two three four five six" +LIST:C/ /,/gW="one,two,three,four,five,six" +EMPTY:C/^/,/="," +EMPTY:C/^/,/W="," +LIST:tW="one two three four five six" +LIST:tw="one two three four five six" +LIST:tW:C/ /,/="one,two three four five six" +LIST:tW:C/ /,/g="one,two,three,four,five,six" +LIST:tW:C/ /,/1g="one,two,three,four,five,six" +LIST:tw:C/ /,/="one two three four five six" +LIST:tw:C/ /,/g="one two three four five six" +LIST:tw:C/ /,/1g="one two three four five six" +LIST:tw:tW:C/ /,/="one,two three four five six" +LIST:tW:tw:C/ /,/="one two three four five six" exit status 0 diff --git a/unit-tests/varmod-select-words.mk b/unit-tests/varmod-select-words.mk index ab094bf056b0..910b67a24e39 100644 --- a/unit-tests/varmod-select-words.mk +++ b/unit-tests/varmod-select-words.mk @@ -1,12 +1,166 @@ -# $NetBSD: varmod-select-words.mk,v 1.3 2021/12/05 12:06:23 rillig Exp $ +# $NetBSD: varmod-select-words.mk,v 1.4 2022/01/23 16:09:38 rillig Exp $ # # Tests for the :[...] variable modifier, which selects a single word # or a range of words from a variable. # +# History: +# The variable modifier ':[...]' was added on 2003-09-27. +# # See also: # modword.mk (should be migrated here) -# TODO: Implementation +all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw -all: - @:; +LIST= one two three 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 '#', the actual value is '\ ' +REALLYSPACE:= ${:U } +HASH= \# +AT= @ +STAR= * +ZERO= 0 +ONE= 1 +MINUSONE= -1 + +mod-squarebrackets: mod-squarebrackets-0-star-at \ + mod-squarebrackets-hash \ + mod-squarebrackets-n \ + mod-squarebrackets-start-end \ + mod-squarebrackets-nested \ + mod-squarebrackets-space + +mod-squarebrackets-0-star-at: + @echo 'LIST:[]="${LIST:[]}" is an error' + @echo 'LIST:[0]="${LIST:[0]}"' + @echo 'LIST:[0x0]="${LIST:[0x0]}"' + @echo 'LIST:[000]="${LIST:[000]}"' + @echo 'LIST:[*]="${LIST:[*]}"' + @echo 'LIST:[@]="${LIST:[@]}"' + @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"' + @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"' + @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"' + @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"' + @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"' + @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"' + @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"' + @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"' + @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"' + @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"' + @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"' + @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"' + @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"' + +mod-squarebrackets-hash: + @echo 'EMPTY="${EMPTY}"' + @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?' + @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' + @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?' + @echo 'REALLYSPACE="${REALLYSPACE}"' + @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?' + @echo 'LIST:[#]="${LIST:[#]}"' + @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?' + @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?' + @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"' + @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"' + @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"' + +mod-squarebrackets-n: + @echo 'EMPTY:[1]="${EMPTY:[1]}"' + @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' + @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"' + @echo 'REALLYSPACE="${REALLYSPACE}"' + @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?' + @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?' + @echo 'LIST:[1]="${LIST:[1]}"' + @echo 'LIST:[1.]="${LIST:[1.]}" is an error' + @echo 'LIST:[1].="${LIST:[1].}" is an error' + @echo 'LIST:[2]="${LIST:[2]}"' + @echo 'LIST:[6]="${LIST:[6]}"' + @echo 'LIST:[7]="${LIST:[7]}"' + @echo 'LIST:[999]="${LIST:[999]}"' + @echo 'LIST:[-]="${LIST:[-]}" is an error' + @echo 'LIST:[--]="${LIST:[--]}" is an error' + @echo 'LIST:[-1]="${LIST:[-1]}"' + @echo 'LIST:[-2]="${LIST:[-2]}"' + @echo 'LIST:[-6]="${LIST:[-6]}"' + @echo 'LIST:[-7]="${LIST:[-7]}"' + @echo 'LIST:[-999]="${LIST:[-999]}"' + @echo 'LONGLIST:[17]="${LONGLIST:[17]}"' + @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"' + @echo 'LONGLIST:[021]="${LONGLIST:[021]}"' + @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"' + @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"' + @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"' + @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"' + @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"' + @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"' + @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"' + @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"' + @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"' + @echo 'LONGLIST:[012..0x12]="${LONGLIST:[012..0x12]}"' + +mod-squarebrackets-start-end: + @echo 'LIST:[1.]="${LIST:[1.]}" is an error' + @echo 'LIST:[1..]="${LIST:[1..]}" is an error' + @echo 'LIST:[1.. ]="${LIST:[1.. ]}" is an error' + @echo 'LIST:[1..1]="${LIST:[1..1]}"' + @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error' + @echo 'LIST:[1..2]="${LIST:[1..2]}"' + @echo 'LIST:[2..1]="${LIST:[2..1]}"' + @echo 'LIST:[3..-2]="${LIST:[3..-2]}"' + @echo 'LIST:[-4..4]="${LIST:[-4..4]}"' + @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error' + @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error' + @echo 'LIST:[-1..1]="${LIST:[-1..1]}"' + @echo 'LIST:[0..0]="${LIST:[0..0]}"' + @echo 'LIST:[3..99]="${LIST:[3..99]}"' + @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"' + @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"' + +mod-squarebrackets-nested: + @echo 'HASH="${HASH}" == "#" ?' + @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"' + @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"' + @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"' + @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"' + @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"' + @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"' + @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"' + @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error' + @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"' + @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"' + @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"' + +mod-squarebrackets-space: + # As of 2020-11-01, it is possible to have spaces before the numbers + # but not after them. This is an unintended side-effect of using + # strtol for parsing the numbers. + @echo 'LIST:[ -1.. +3]="${LIST:[ -1.. +3]}"' + +mod-C-W: + @echo 'LIST:C/ /,/="${LIST:C/ /,/}"' + @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"' + @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"' + @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"' + @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"' + +mod-S-W: + @echo 'LIST:S/ /,/="${LIST:S/ /,/}"' + @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"' + @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"' + @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"' + @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"' + +mod-tW-tw: + @echo 'LIST:tW="${LIST:tW}"' + @echo 'LIST:tw="${LIST:tw}"' + @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"' + @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"' + @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"' + @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"' + @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"' + @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"' + @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"' + @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"' diff --git a/unit-tests/varmod-shell.exp b/unit-tests/varmod-shell.exp index 9aef0c9e5acc..adcfe7f251a9 100644 --- a/unit-tests/varmod-shell.exp +++ b/unit-tests/varmod-shell.exp @@ -1,3 +1,13 @@ make: "echo word; false" returned non-zero status make: "echo word; false" returned non-zero status +Global: _ = +Var_Parse: ${:!echo word; ${:Ufalse}!} (eval-keep-dollar-and-undefined) +Evaluating modifier ${:!...} on value "" (eval-keep-dollar-and-undefined, undefined) +Modifier part: "echo word; false" +Capturing the output of command "echo word; false" +make: "echo word; false" returned non-zero status +Result of ${:!echo word; ${:Ufalse}!} is "word" (eval-keep-dollar-and-undefined, defined) +Global: _ = word +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/unit-tests/varmod-shell.mk b/unit-tests/varmod-shell.mk index c736042f80a0..d449709cee0f 100644 --- a/unit-tests/varmod-shell.mk +++ b/unit-tests/varmod-shell.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-shell.mk,v 1.6 2021/02/14 20:16:17 rillig Exp $ +# $NetBSD: varmod-shell.mk,v 1.7 2022/01/10 20:32:29 rillig Exp $ # # Tests for the ':!cmd!' variable modifier, which runs the shell command # given by the variable modifier and returns its output. @@ -20,8 +20,7 @@ # # Between 2000-04-29 and 2020-11-17, the error message mentioned the previous # value of the expression (which is usually an empty string) instead of the -# command that was executed. It's strange that such a simple bug could -# survive such a long time. +# command that was executed. .if ${:!echo word; false!} != "word" . error .endif @@ -29,4 +28,9 @@ . error .endif + +.MAKEFLAGS: -dv # to see the actual command +_:= ${:!echo word; ${:Ufalse}!} +.MAKEFLAGS: -d0 + all: diff --git a/unit-tests/varmod-sun-shell.exp b/unit-tests/varmod-sun-shell.exp index 5087bc66d943..4954458b13e1 100644 --- a/unit-tests/varmod-sun-shell.exp +++ b/unit-tests/varmod-sun-shell.exp @@ -1,2 +1,13 @@ make: "echo word; false" returned non-zero status +Global: _ = +Var_Parse: ${echo word; ${:Ufalse}:L:sh} (eval-keep-dollar-and-undefined) +Evaluating modifier ${echo word; false:L} on value "" (eval-keep-dollar-and-undefined, undefined) +Result of ${echo word; false:L} is "echo word; false" (eval-keep-dollar-and-undefined, defined) +Evaluating modifier ${echo word; false:s...} on value "echo word; false" (eval-keep-dollar-and-undefined, defined) +Capturing the output of command "echo word; false" +make: "echo word; false" returned non-zero status +Result of ${echo word; false:sh} is "word" (eval-keep-dollar-and-undefined, defined) +Global: _ = word +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/unit-tests/varmod-sun-shell.mk b/unit-tests/varmod-sun-shell.mk index 712b36bc7030..97acc5bd8c0f 100644 --- a/unit-tests/varmod-sun-shell.mk +++ b/unit-tests/varmod-sun-shell.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-sun-shell.mk,v 1.1 2021/02/14 20:16:17 rillig Exp $ +# $NetBSD: varmod-sun-shell.mk,v 1.2 2022/01/10 20:32:29 rillig Exp $ # # Tests for the :sh variable modifier, which runs the shell command # given by the variable value and returns its output. @@ -18,4 +18,9 @@ . error .endif + +.MAKEFLAGS: -dv # to see the actual command +_:= ${echo word; ${:Ufalse}:L:sh} +.MAKEFLAGS: -d0 + all: diff --git a/unit-tests/varmod-to-separator.exp b/unit-tests/varmod-to-separator.exp index 3f8f1b2a11eb..bfcfa3ebc103 100644 --- a/unit-tests/varmod-to-separator.exp +++ b/unit-tests/varmod-to-separator.exp @@ -1,19 +1,25 @@ -make: "varmod-to-separator.mk" line 107: Invalid character number at "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 at "100:tu}" -make: "varmod-to-separator.mk" line 121: Malformed conditional (${WORDS:[1..3]:ts\x100:tu}) +make: "varmod-to-separator.mk" line 153: Invalid character number at "400:tu}" +make: "varmod-to-separator.mk" line 153: Malformed conditional (${WORDS:[1..3]:ts\400:tu}) +make: "varmod-to-separator.mk" line 167: Invalid character number at "100:tu}" +make: "varmod-to-separator.mk" line 167: Malformed conditional (${WORDS:[1..3]:ts\x100:tu}) make: Bad modifier ":ts\-300" for variable "WORDS" -make: "varmod-to-separator.mk" line 128: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) +make: "varmod-to-separator.mk" line 174: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) make: Bad modifier ":ts\8" for variable "1 2 3" -make: "varmod-to-separator.mk" line 136: Malformed conditional (${1 2 3:L:ts\8:tu}) +make: "varmod-to-separator.mk" line 182: Malformed conditional (${1 2 3:L:ts\8:tu}) make: Bad modifier ":ts\100L" for variable "1 2 3" -make: "varmod-to-separator.mk" line 143: Malformed conditional (${1 2 3:L:ts\100L}) +make: "varmod-to-separator.mk" line 189: Malformed conditional (${1 2 3:L:ts\100L}) make: Bad modifier ":ts\x40g" for variable "1 2 3" -make: "varmod-to-separator.mk" line 150: Malformed conditional (${1 2 3:L:ts\x40g}) +make: "varmod-to-separator.mk" line 196: Malformed conditional (${1 2 3:L:ts\x40g}) make: Bad modifier ":tx" for variable "WORDS" -make: "varmod-to-separator.mk" line 158: Malformed conditional (${WORDS:tx} != "anything") +make: "varmod-to-separator.mk" line 205: Malformed conditional (${WORDS:tx}) +make: Bad modifier ":ts\X" for variable "WORDS" +make: "varmod-to-separator.mk" line 213: Malformed conditional (${WORDS:ts\X}) make: Bad modifier ":t\X" for variable "WORDS" -make: "varmod-to-separator.mk" line 165: Malformed conditional (${WORDS:t\X} != "anything") +make: "varmod-to-separator.mk" line 221: Malformed conditional (${WORDS:t\X} != "anything") +make: Bad modifier ":ts\69" for variable "" +make: "varmod-to-separator.mk" line 237: Malformed conditional (${:Ua b:ts\69}) +make: "varmod-to-separator.mk" line 246: Invalid character number at "1F60E}" +make: "varmod-to-separator.mk" line 246: Malformed conditional (${:Ua b:ts\x1F60E}) 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 08c6126ecc68..e724a9a1ce82 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.7 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: varmod-to-separator.mk,v 1.10 2022/01/23 21:48:59 rillig Exp $ # # Tests for the :ts variable modifier, which joins the words of the variable # using an arbitrary character as word separator. @@ -80,6 +80,52 @@ WORDS= one two three four five six . warning The :ts modifier followed by an :S modifier does not work. .endif +# After the modifier ':ts/', the expression value is a single word since all +# spaces have been replaced with '/'. This single word does not start with +# 'two', which makes the modifier ':S' a no-op. +.if ${WORDS:ts/:S/^two/2/} != "one/two/three/four/five/six" +. error +.endif + +# After the :ts modifier, the whole string is interpreted as a single +# word since all spaces have been replaced with x. Because of this single +# word, only the first 'b' is replaced with 'B'. +.if ${aa bb aa bb aa bb:L:tsx:S,b,B,} != "aaxBbxaaxbbxaaxbb" +. error +.endif + +# The :ts modifier also applies to word separators that are added +# afterwards. First, the modifier ':tsx' joins the 3 words, then the modifier +# ':S' replaces the 2 'b's with spaces. These spaces are part of the word, +# so when the words are joined at the end of the modifier ':S', there is only +# a single word, and the custom separator from the modifier ':tsx' has no +# effect. +.if ${a ababa c:L:tsx:S,b, ,g} != "axa a axc" +. error +.endif + +# Adding the modifier ':M*' at the end of the above chain splits the +# expression value and then joins it again. At this point of splitting, the +# newly added spaces are treated as word separators, resulting in 3 words. +# When these 3 words are joined, the separator from the modifier ':tsx' is +# used. +.if ${a ababa c:L:tsx:S,b, ,g:M*} != "axaxaxaxc" +. error +.endif + +# Not all modifiers use the separator from the previous modifier ':ts' though. +# The modifier ':@' always uses a space as word separator instead. This has +# probably been an oversight during implementation. For consistency, the +# result should rather be "axaxaxaxc", as in the previous example. +.if ${a ababa c:L:tsx:S,b, ,g:@v@$v@} != "axa a axc" +. error +.endif + +# Adding a final :M* modifier applies the :ts separator again, though. +.if ${a ababa c:L:tsx:S,b, ,g:@v@${v}@:M*} != "axaxaxaxc" +. error +.endif + # The separator can be \n, which is a newline. .if ${WORDS:[1..3]:ts\n} != "one${.newline}two${.newline}three" . warning The separator \n does not produce a newline. @@ -155,9 +201,19 @@ WORDS= one two three four five six # 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. +# expect: make: Bad modifier ":tx" for variable "WORDS" +.if ${WORDS:tx} +. error +.else +. error +.endif + +# The word separator must be can only be a single character. +# expect: make: Bad modifier ":ts\X" for variable "WORDS" +.if ${WORDS:ts\X} +. error +.else +. error .endif # After the backslash, only n, t, an octal number, or x and a hexadecimal @@ -166,10 +222,29 @@ WORDS= one two three four five six . info This line is not reached. .endif -# TODO: This modifier used to accept decimal numbers as well, in the form -# ':ts\120'. When has this been changed to octal, and what happens now -# for ':ts\90' ('Z' in decimal ASCII, undefined in octal)? -# TODO: :ts\x1F600 +# Since 2003.07.23.18.06.46 and before 2016.03.07.20.20.35, the modifier ':ts' +# interpreted an "octal escape" as decimal if the first digit was not '0'. +.if ${:Ua b:ts\61} != "a1b" # decimal would have been "a=b" +. error +.endif -all: +# Since the character escape is always interpreted as octal, let's see what +# happens for non-octal digits. From 2003.07.23.18.06.46 to +# 2016.02.27.16.20.06, the result was '1E2', since 2016.03.07.20.20.35 make no +# longer accepts this escape and complains. +# expect: make: Bad modifier ":ts\69" for variable "" +.if ${:Ua b:ts\69} +. error +.else +. error +.endif + +# Try whether bmake is Unicode-ready. +# expect+2: Invalid character number at "1F60E}" +# expect+1: Malformed conditional (${:Ua b:ts\x1F60E}) +.if ${:Ua b:ts\x1F60E} # U+1F60E "smiling face with sunglasses" +. error +.else +. error +.endif diff --git a/unit-tests/varname-dot-make-jobs.exp b/unit-tests/varname-dot-make-jobs.exp index 39a9383953dd..1308f9116240 100644 --- a/unit-tests/varname-dot-make-jobs.exp +++ b/unit-tests/varname-dot-make-jobs.exp @@ -1 +1,8 @@ +undefined +1 +--- echo --- +5 +--- echo --- +20 +00000000000000000000000000000001 exit status 0 diff --git a/unit-tests/varname-dot-make-jobs.mk b/unit-tests/varname-dot-make-jobs.mk index 1e99b3d28ea8..af5eebfe7498 100644 --- a/unit-tests/varname-dot-make-jobs.mk +++ b/unit-tests/varname-dot-make-jobs.mk @@ -1,8 +1,24 @@ -# $NetBSD: varname-dot-make-jobs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-dot-make-jobs.mk,v 1.3 2022/01/26 22:47:03 rillig Exp $ # -# Tests for the special .MAKE.JOBS variable. +# Tests for the special .MAKE.JOBS variable, which is defined in jobs mode +# only. There it contains the number of jobs that may run in parallel. -# TODO: Implementation +.MAIN: all + +echo: .PHONY + @echo ${.MAKE.JOBS:Uundefined} all: - @:; + @${MAKE} -r -f ${MAKEFILE} echo + @${MAKE} -r -f ${MAKEFILE} echo -j1 + @${MAKE} -r -f ${MAKEFILE} echo -j5 + @${MAKE} -r -f ${MAKEFILE} echo -j20 + @${MAKE} -r -f ${MAKEFILE} echo -j00000000000000000000000000000001 + +# expect: undefined +# expect: 1 +# expect: 5 +# expect: 20 +# The value of .MAKE.JOBS is the exact text given in the command line, not the +# canonical number. This doesn't have practical consequences though. +# expect: 00000000000000000000000000000001 diff --git a/unit-tests/varname-dot-make-pid.mk b/unit-tests/varname-dot-make-pid.mk index bea114d33547..d7ef5bfd5c44 100644 --- a/unit-tests/varname-dot-make-pid.mk +++ b/unit-tests/varname-dot-make-pid.mk @@ -1,8 +1,16 @@ -# $NetBSD: varname-dot-make-pid.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-dot-make-pid.mk,v 1.3 2022/01/23 21:48:59 rillig Exp $ # -# Tests for the special .MAKE.PID variable. +# Tests for the special .MAKE.PID variable, which contains the process ID of +# the make process itself. -# TODO: Implementation +# The process ID must be a positive integer. +.if ${.MAKE.PID:C,[0-9],,g} != "" +. error +.elif !(${.MAKE.PID} > 0) +. error +.endif -all: - @:; +# Ensure that the process exists. +_!= kill -0 ${.MAKE.PID} + +all: .PHONY diff --git a/unit-tests/varname-dot-make-ppid.mk b/unit-tests/varname-dot-make-ppid.mk index c9471542ca35..91f13fd2feec 100644 --- a/unit-tests/varname-dot-make-ppid.mk +++ b/unit-tests/varname-dot-make-ppid.mk @@ -1,8 +1,23 @@ -# $NetBSD: varname-dot-make-ppid.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-dot-make-ppid.mk,v 1.3 2022/01/23 21:48:59 rillig Exp $ # -# Tests for the special .MAKE.PPID variable. +# Tests for the special .MAKE.PPID variable, which contains the process ID of +# make's parent process. -# TODO: Implementation +# The parent process ID must be a positive integer. +.if ${.MAKE.PPID:C,[0-9],,g} != "" +. error +.elif !(${.MAKE.PPID} > 0) +. error +.endif -all: - @:; +# Ensure that the process exists. +_!= kill -0 ${.MAKE.PPID} + +# The parent process ID must be different from the process ID. If they were +# the same, make would run as process 1, which is not a good idea because make +# is not prepared to clean up after other processes. +.if ${.MAKE.PPID} == ${.MAKE.PID} +. error +.endif + +all: .PHONY diff --git a/unit-tests/varname-dot-shell.exp b/unit-tests/varname-dot-shell.exp index bfbcfc960182..28516ab1ea84 100755 --- a/unit-tests/varname-dot-shell.exp +++ b/unit-tests/varname-dot-shell.exp @@ -1,31 +1,31 @@ -ParseReadLine (10): 'ORIG_SHELL:= ${.SHELL}' +Parsing line 10: ORIG_SHELL:= ${.SHELL} Global: ORIG_SHELL = Var_Parse: ${.SHELL} (eval-keep-dollar-and-undefined) Global:delete .SHELL (not found) Command: .SHELL = (details omitted) Global: ORIG_SHELL = (details omitted) -ParseReadLine (12): '.SHELL= overwritten' +Parsing line 12: .SHELL= overwritten Global: .SHELL = overwritten CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) Var_Parse: ${ORIG_SHELL} (eval-defined) lhs = "(details omitted)", rhs = "(details omitted)", op = != -ParseReadLine (19): '.MAKEFLAGS: .SHELL+=appended' +Parsing line 19: .MAKEFLAGS: .SHELL+=appended ParseDependency(.MAKEFLAGS: .SHELL+=appended) Ignoring append to .SHELL since it is read-only CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) Var_Parse: ${ORIG_SHELL} (eval-defined) lhs = "(details omitted)", rhs = "(details omitted)", op = != -ParseReadLine (27): '.undef .SHELL' +Parsing line 27: .undef .SHELL Global:delete .SHELL -ParseReadLine (28): '.SHELL= newly overwritten' +Parsing line 28: .SHELL= newly overwritten Global: .SHELL = newly overwritten CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) Var_Parse: ${ORIG_SHELL} (eval-defined) lhs = "(details omitted)", rhs = "(details omitted)", op = != -ParseReadLine (33): '.MAKEFLAGS: -d0' +Parsing line 33: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) Global: .MAKEFLAGS = -r -k -d cpv -d Global: .MAKEFLAGS = -r -k -d cpv -d 0 diff --git a/unit-tests/varname-dot-suffixes.mk b/unit-tests/varname-dot-suffixes.mk index de8034a172cc..babbe20d1c7c 100644 --- a/unit-tests/varname-dot-suffixes.mk +++ b/unit-tests/varname-dot-suffixes.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname-dot-suffixes.mk,v 1.1 2021/12/12 22:16:48 rillig Exp $ +# $NetBSD: varname-dot-suffixes.mk,v 1.2 2022/01/15 12:35:18 rillig Exp $ # # Tests for the special "variable" .SUFFIXES, which lists the suffixes that # have been registered for use in suffix transformation rules. Suffixes are @@ -67,7 +67,7 @@ .SUFFIXES+= append # expect: Global: .SUFFIXES = assign ignored (read-only) _:= ${.SUFFIXES::=assign} -# expect: Command: .SUFFIXES = preserve ignored (read-only) +# expect: Global: .SUFFIXES = preserve ignored (read-only) _:= ${preserve:L:_=.SUFFIXES} .MAKEFLAGS: -d0 @@ -96,6 +96,8 @@ _:= ${preserve:L:_=.SUFFIXES} .MAKEFLAGS: -dv # expect: Command: .SUFFIXES = 1 ignored (read-only) # expect: Command: .SUFFIXES = 2 ignored (read-only) +# XXX: Missing space after ':' +# expect: Command:delete .SUFFIXES (not found) .if ${1 2:L:@.SUFFIXES@${.SUFFIXES}@} != ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" . error .endif diff --git a/unit-tests/varname-empty.exp b/unit-tests/varname-empty.exp index 75a3f4151a8c..72e79abe1ea9 100644 --- a/unit-tests/varname-empty.exp +++ b/unit-tests/varname-empty.exp @@ -13,6 +13,7 @@ Var_SetExpand: variable name "" expands to empty string, with value "assigned" - SetVar: variable name is empty - ignored Var_SetExpand: variable name "" expands to empty string, with value "" - ignored Var_SetExpand: variable name "" expands to empty string, with value "subst" - ignored +Capturing the output of command "echo 'shell-output'" Var_SetExpand: variable name "" expands to empty string, with value "shell-output" - ignored Var_SetExpand: variable name "${:U}" expands to empty string, with value "assigned indirectly" - ignored Var_AppendExpand: variable name "${:U}" expands to empty string, with value "appended indirectly" - ignored diff --git a/unit-tests/varname-makeflags.mk b/unit-tests/varname-makeflags.mk index 3b4fd91c3f57..f7840c2eb7a5 100644 --- a/unit-tests/varname-makeflags.mk +++ b/unit-tests/varname-makeflags.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname-makeflags.mk,v 1.3 2020/12/01 20:37:30 rillig Exp $ +# $NetBSD: varname-makeflags.mk,v 1.5 2022/01/16 18:16:06 sjg Exp $ # # Tests for the special MAKEFLAGS variable, which is basically just a normal # environment variable. It is closely related to .MAKEFLAGS but captures the @@ -23,4 +23,22 @@ . error .endif + +# In POSIX mode, the environment variable MAKEFLAGS can contain letters only, +# for compatibility. These letters are exploded to form regular options. +OUTPUT!= env MAKEFLAGS=ikrs ${MAKE} -f /dev/null -v .MAKEFLAGS +.if ${OUTPUT} != " -i -k -r -s -V .MAKEFLAGS" +. error +.endif + +# As soon as there is a single non-alphabetic character in the environment +# variable MAKEFLAGS, it is no longer split. In this example, the word +# "d0ikrs" is treated as a target, but the option '-v' prevents any targets +# from being built. +OUTPUT!= env MAKEFLAGS=d0ikrs ${MAKE} -r -f /dev/null -v .MAKEFLAGS +.if ${OUTPUT} != " -r -V .MAKEFLAGS" +. error ${OUTPUT} +.endif + + all: diff --git a/unit-tests/varname.mk b/unit-tests/varname.mk index f586c7602cb7..0fc908c36481 100644 --- a/unit-tests/varname.mk +++ b/unit-tests/varname.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname.mk,v 1.8 2020/11/02 22:59:48 rillig Exp $ +# $NetBSD: varname.mk,v 1.9 2022/01/27 10:42:02 rillig Exp $ # # Tests for special variables, such as .MAKE or .PARSEDIR. # And for variable names in general. @@ -41,4 +41,46 @@ ${VARNAME}= try3 .MAKEFLAGS: -d0 +# All variable names of a scope are stored in the same hash table, using a +# simple hash function. Ensure that HashEntry_KeyEquals handles collisions +# correctly and that the correct variable is looked up. The strings "0x" and +# "1Y" have the same hash code, as 31 * '0' + 'x' == 31 * '1' + 'Y'. +V.0x= 0x +V.1Y= 1Y +.if ${V.0x} != "0x" || ${V.1Y} != "1Y" +. error +.endif + +# The string "ASDZguv", when used as a prefix of a variable name, keeps the +# hash code unchanged, its own hash code is 0. +ASDZguvV.0x= 0x +ASDZguvV.1Y= 1Y +.if ${ASDZguvV.0x} != "0x" +. error +.elif ${ASDZguvV.1Y} != "1Y" +. error +.endif + +# Ensure that variables with the same hash code whose name is a prefix of the +# other can be accessed. In this case, the shorter variable name is defined +# first to make it appear later in the bucket of the hash table. +ASDZguv= once +ASDZguvASDZguv= twice +.if ${ASDZguv} != "once" +. error +.elif ${ASDZguvASDZguv} != "twice" +. error +.endif + +# Ensure that variables with the same hash code whose name is a prefix of the +# other can be accessed. In this case, the longer variable name is defined +# first to make it appear later in the bucket of the hash table. +ASDZguvASDZguv.param= twice +ASDZguv.param= once +.if ${ASDZguv.param} != "once" +. error +.elif ${ASDZguvASDZguv.param} != "twice" +. error +.endif + all: diff --git a/unit-tests/varparse-errors.exp b/unit-tests/varparse-errors.exp index 27589e0b21af..e47127447cda 100644 --- a/unit-tests/varparse-errors.exp +++ b/unit-tests/varparse-errors.exp @@ -1,5 +1,11 @@ make: "varparse-errors.mk" line 38: Unknown modifier "Z" make: "varparse-errors.mk" line 46: Unknown modifier "Z" +make: Bad modifier ":OX" for variable "" +make: "varparse-errors.mk" line 68: Undefined variable "${:U:OX" +make: Bad modifier ":OX" for variable "" +make: Bad modifier ":OX" for variable "" +make: "varparse-errors.mk" line 68: Undefined variable "${:U:OX" +make: Bad modifier ":OX" for variable "" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varparse-errors.mk b/unit-tests/varparse-errors.mk index f0947bb9410a..51a403fa898f 100644 --- a/unit-tests/varparse-errors.mk +++ b/unit-tests/varparse-errors.mk @@ -1,4 +1,4 @@ -# $NetBSD: varparse-errors.mk,v 1.4 2021/03/15 12:15:03 rillig Exp $ +# $NetBSD: varparse-errors.mk,v 1.5 2022/01/24 22:59:49 rillig Exp $ # Tests for parsing and evaluating all kinds of variable expressions. # @@ -48,4 +48,24 @@ VAR.${:U:Z}post= unknown modifier with text in the variable name . error .endif +# Demonstrate an edge case in which the 'static' for 'errorReported' in +# Var_Subst actually makes a difference, preventing "a plethora of messages". +# Given that this is an edge case and the error message is wrong and thus +# misleading anyway, that piece of code is probably not necessary. The wrong +# condition was added in var.c 1.185 from 2014-05-19. +# +# To trigger this difference, the variable assignment must use the assignment +# operator ':=' to make VarEvalMode_ShouldKeepUndef return true. There must +# be 2 expressions that create a parse error, which in this case is ':OX'. +# These expressions must be nested in some way. The below expressions are +# minimal, that is, removing any part of it destroys the effect. +# +# Without the 'static', there would be one more message like this: +# Undefined variable "${:U:OX" +# +#.MAKEFLAGS: -dv +IND= ${:OX} +_:= ${:U:OX:U${IND}} ${:U:OX:U${IND}} +#.MAKEFLAGS: -d0 + all: diff --git a/unit-tests/varquote.mk b/unit-tests/varquote.mk index fb8b1066ac15..3d5e8a7f32e9 100644 --- a/unit-tests/varquote.mk +++ b/unit-tests/varquote.mk @@ -1,10 +1,10 @@ -# $NetBSD: varquote.mk,v 1.4 2018/12/16 18:53:34 christos Exp $ +# $NetBSD: varquote.mk,v 1.5 2021/12/28 10:47:00 rillig Exp $ # # Test VAR:q modifier .if !defined(REPROFLAGS) -REPROFLAGS+= -fdebug-prefix-map=\$$NETBSDSRCDIR=/usr/src -REPROFLAGS+= -fdebug-regex-map='/usr/src/(.*)/obj$$=/usr/obj/\1' +REPROFLAGS+= -fdebug-prefix-map=\$$NETBSDSRCDIR=/usr/src +REPROFLAGS+= -fdebug-regex-map='/usr/src/(.*)/obj$$=/usr/obj/\1' all: @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:S/\$/&&/g:Q} @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:q} diff --git a/util.c b/util.c index dab18e2ece53..eeda3d8f8a0c 100644 --- a/util.c +++ b/util.c @@ -1,9 +1,9 @@ -/* $NetBSD: util.c,v 1.76 2021/02/03 08:00:36 rillig Exp $ */ +/* $NetBSD: util.c,v 1.78 2021/12/15 12:58:01 rillig Exp $ */ /* * Missing stuff from OS's * - * $Id: util.c,v 1.49 2021/10/14 19:26:52 sjg Exp $ + * $Id: util.c,v 1.50 2021/12/21 18:47:24 sjg Exp $ */ #include @@ -13,7 +13,7 @@ #include "make.h" -MAKE_RCSID("$NetBSD: util.c,v 1.76 2021/02/03 08:00:36 rillig Exp $"); +MAKE_RCSID("$NetBSD: util.c,v 1.78 2021/12/15 12:58:01 rillig Exp $"); #if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR) extern int errno, sys_nerr; @@ -22,12 +22,12 @@ extern char *sys_errlist[]; char * strerror(int e) { - static char buf[100]; - if (e < 0 || e >= sys_nerr) { - snprintf(buf, sizeof buf, "Unknown error %d", e); - return buf; - } else - return sys_errlist[e]; + static char buf[100]; + if (e < 0 || e >= sys_nerr) { + snprintf(buf, sizeof buf, "Unknown error %d", e); + return buf; + } else + return sys_errlist[e]; } #endif @@ -57,9 +57,9 @@ findenv(const char *name, int *offset) char * getenv(const char *name) { - int offset; + int offset; - return findenv(name, &offset); + return findenv(name, &offset); } int @@ -73,7 +73,7 @@ unsetenv(const char *name) return -1; } - while (findenv(name, &offset)) { /* if set multiple times */ + while (findenv(name, &offset)) { /* if set multiple times */ for (p = &environ[offset];; p++) if (!(*p = *(p + 1))) break; @@ -94,7 +94,7 @@ setenv(const char *name, const char *value, int rewrite) return -1; } - if (*value == '=') /* no `=' in value */ + if (*value == '=') /* no `=' in value */ value++; l_value = strlen(value); @@ -160,16 +160,15 @@ main(int argc, char *argv[]) static char * strrcpy(char *ptr, char *str) { - int len = strlen(str); + int len = strlen(str); - while (len != 0) - *--ptr = str[--len]; + while (len != 0) + *--ptr = str[--len]; - return ptr; -} /* end strrcpy */ + return ptr; +} - -char *sys_siglist[] = { +char *sys_siglist[] = { "Signal 0", "Hangup", /* SIGHUP */ "Interrupt", /* SIGINT */ @@ -218,7 +217,7 @@ char *sys_siglist[] = { int killpg(int pid, int sig) { - return kill(-pid, sig); + return kill(-pid, sig); } #if !defined(BSD) && !defined(d_fileno) @@ -393,7 +392,7 @@ vsnprintf(char *s, size_t n, const char *fmt, va_list args) fakebuf._cnt++; putc('\0', &fakebuf); if (fakebuf._cnt < 0) - fakebuf._cnt = 0; + fakebuf._cnt = 0; return n - fakebuf._cnt - 1; #else #ifndef _PATH_DEVNULL diff --git a/var.c b/var.c index 4542a2f9a2ed..53d325d0d95a 100644 --- a/var.c +++ b/var.c @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.973 2021/12/12 20:45:48 sjg Exp $ */ +/* $NetBSD: var.c,v 1.1009 2022/02/04 23:43:10 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -103,7 +103,6 @@ * Var_Parse Parse a variable expression such as ${VAR:Mpattern}. * * Var_Delete - * Var_DeleteExpand * Delete a variable. * * Var_ReexportVars @@ -148,7 +147,7 @@ #include "metachar.h" /* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: var.c,v 1.973 2021/12/12 20:45:48 sjg Exp $"); +MAKE_RCSID("$NetBSD: var.c,v 1.1009 2022/02/04 23:43:10 rillig Exp $"); /* * Variables are defined using one of the VAR=value assignments. Their @@ -161,10 +160,11 @@ MAKE_RCSID("$NetBSD: var.c,v 1.973 2021/12/12 20:45:48 sjg Exp $"); * Scope variables are stored in a GNode.scope. The only way to undefine * a scope variable is using the .undef directive. In particular, it must * not be possible to undefine a variable during the evaluation of an - * expression, or Var.name might point nowhere. + * expression, or Var.name might point nowhere. (There is another, + * unintended way to undefine a scope variable, see varmod-loop-delete.mk.) * - * Environment variables are temporary. They are returned by VarFind, and - * after using them, they must be freed using VarFreeEnv. + * Environment variables are short-lived. They are returned by VarFind, and + * after using them, they must be freed using VarFreeShortLived. * * Undefined variables occur during evaluation of variable expressions such * as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers. @@ -181,14 +181,20 @@ typedef struct Var { Buffer val; /* The variable came from the command line. */ - bool fromCmd: 1; + bool fromCmd:1; + + /* + * The variable is short-lived. + * These variables are not registered in any GNode, therefore they + * must be freed after use. + */ + bool shortLived:1; /* * The variable comes from the environment. - * These variables are not registered in any GNode, therefore they - * must be freed as soon as they are not used anymore. + * Appending to its value moves the variable to the global scope. */ - bool fromEnv: 1; + bool fromEnvironment:1; /* * The variable value cannot be changed anymore, and the variable @@ -197,19 +203,19 @@ typedef struct Var { * * See VAR_SET_READONLY. */ - bool readOnly: 1; + bool readOnly:1; /* * The variable's value is currently being used by Var_Parse or * Var_Subst. This marker is used to avoid endless recursion. */ - bool inUse: 1; + bool inUse:1; /* * The variable is exported to the environment, to be used by child * processes. */ - bool exported: 1; + bool exported:1; /* * At the point where this variable was exported, it contained an @@ -217,7 +223,7 @@ typedef struct Var { * process is started, it needs to be exported again, in the hope * that the referenced variable can then be resolved. */ - bool reexport: 1; + bool reexport:1; } Var; /* @@ -250,10 +256,10 @@ typedef enum UnexportWhat { /* Flags for pattern matching in the :S and :C modifiers */ typedef struct PatternFlags { - bool subGlobal: 1; /* 'g': replace as often as possible */ - bool subOnce: 1; /* '1': replace only once */ - bool anchorStart: 1; /* '^': match only at start of word */ - bool anchorEnd: 1; /* '$': match only at end of word */ + bool subGlobal:1; /* 'g': replace as often as possible */ + bool subOnce:1; /* '1': replace only once */ + bool anchorStart:1; /* '^': match only at start of word */ + bool anchorEnd:1; /* '$': match only at end of word */ } PatternFlags; /* SepBuf builds a string from words interleaved with separators. */ @@ -317,7 +323,8 @@ static bool save_dollars = false; * be simpler or more complex than the current implementation. * * Each target has its own scope, containing the 7 target-local variables - * .TARGET, .ALLSRC, etc. No other variables are in these scopes. + * .TARGET, .ALLSRC, etc. Variables set on dependency lines also go in + * this scope. */ GNode *SCOPE_CMDLINE; @@ -337,7 +344,8 @@ static const char VarEvalMode_Name[][32] = { static Var * -VarNew(FStr name, const char *value, bool fromEnv, bool readOnly) +VarNew(FStr name, const char *value, + bool shortLived, bool fromEnvironment, bool readOnly) { size_t value_len = strlen(value); Var *var = bmake_malloc(sizeof *var); @@ -345,7 +353,8 @@ VarNew(FStr name, const char *value, bool fromEnv, bool readOnly) Buf_InitSize(&var->val, value_len + 1); Buf_AddBytes(&var->val, value, value_len); var->fromCmd = false; - var->fromEnv = fromEnv; + var->shortLived = shortLived; + var->fromEnvironment = fromEnvironment; var->readOnly = readOnly; var->inUse = false; var->exported = false; @@ -399,8 +408,8 @@ GNode_FindVar(GNode *scope, Substring varname, unsigned int hash) * * 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. + * If the variable is short-lived (such as environment variables), it + * must be freed using VarFreeShortLived after use. */ static Var * VarFindSubstring(Substring name, GNode *scope, bool elsewhere) @@ -439,7 +448,7 @@ VarFindSubstring(Substring name, GNode *scope, bool elsewhere) envName = Substring_Str(name); envValue = getenv(envName.str); if (envValue != NULL) - return VarNew(envName, envValue, true, false); + return VarNew(envName, envValue, true, true, false); FStr_Done(&envName); if (opts.checkEnvFirst && scope != SCOPE_GLOBAL) { @@ -463,11 +472,11 @@ VarFind(const char *name, GNode *scope, bool elsewhere) return VarFindSubstring(Substring_InitStr(name), scope, elsewhere); } -/* If the variable is an environment variable, free it, including its value. */ +/* If the variable is short-lived, free it, including its value. */ static void -VarFreeEnv(Var *v) +VarFreeShortLived(Var *v) { - if (!v->fromEnv) + if (!v->shortLived) return; FStr_Done(&v->name); @@ -481,7 +490,7 @@ VarAdd(const char *name, const char *value, GNode *scope, VarSetFlags flags) { HashEntry *he = HashTable_CreateEntry(&scope->vars, name, NULL); Var *v = VarNew(FStr_InitRefer(/* aliased to */ he->key), value, - false, (flags & VAR_SET_READONLY) != 0); + false, false, (flags & VAR_SET_READONLY) != 0); HashEntry_Set(he, v); DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, value); return v; @@ -520,27 +529,6 @@ Var_Delete(GNode *scope, const char *varname) free(v); } -/* - * Remove a variable from a scope, freeing all related memory as well. - * The variable name is expanded once. - */ -void -Var_DeleteExpand(GNode *scope, const char *name) -{ - FStr varname = FStr_InitRefer(name); - - if (strchr(varname.str, '$') != NULL) { - char *expanded; - (void)Var_Subst(varname.str, SCOPE_GLOBAL, VARE_WANTRES, - &expanded); - /* TODO: handle errors */ - varname = FStr_InitOwn(expanded); - } - - Var_Delete(scope, varname.str); - FStr_Done(&varname); -} - /* * Undefine one or more variables from the global scope. * The argument is expanded exactly once and then split into words. @@ -948,7 +936,7 @@ ExistsInCmdline(const char *name, const char *val) return true; } - VarFreeEnv(v); + VarFreeShortLived(v); return false; } @@ -998,7 +986,7 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val, scope->name, name, val); return; } - Buf_Empty(&v->val); + Buf_Clear(&v->val); Buf_AddStr(&v->val, val); DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, val); @@ -1023,8 +1011,10 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val, if (!opts.varNoExportEnv) setenv(name, val, 1); /* XXX: What about .MAKE.EXPORTED? */ - /* XXX: Why not just mark the variable for needing export, - * as in ExportVarPlain? */ + /* + * XXX: Why not just mark the variable for needing export, as + * in ExportVarPlain? + */ Global_Append(MAKEOVERRIDES, name); } @@ -1033,35 +1023,7 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val, save_dollars = ParseBoolean(val, save_dollars); if (v != NULL) - VarFreeEnv(v); -} - -/* See Var_Set for documentation. */ -void -Var_SetExpandWithFlags(GNode *scope, const char *name, const char *val, - VarSetFlags flags) -{ - const char *unexpanded_name = name; - FStr varname = FStr_InitRefer(name); - - assert(val != NULL); - - if (strchr(varname.str, '$') != NULL) { - char *expanded; - (void)Var_Subst(varname.str, scope, VARE_WANTRES, &expanded); - /* TODO: handle errors */ - varname = FStr_InitOwn(expanded); - } - - if (varname.str[0] == '\0') { - DEBUG2(VAR, - "Var_SetExpand: variable name \"%s\" expands " - "to empty string, with value \"%s\" - ignored\n", - unexpanded_name, val); - } else - Var_SetWithFlags(scope, varname.str, val, flags); - - FStr_Done(&varname); + VarFreeShortLived(v); } void @@ -1084,7 +1046,22 @@ Var_Set(GNode *scope, const char *name, const char *val) void Var_SetExpand(GNode *scope, const char *name, const char *val) { - Var_SetExpandWithFlags(scope, name, val, VAR_SET_NONE); + const char *unexpanded_name = name; + FStr varname = FStr_InitRefer(name); + + assert(val != NULL); + + Var_Expand(&varname, scope, VARE_WANTRES); + + if (varname.str[0] == '\0') { + DEBUG2(VAR, + "Var_SetExpand: variable name \"%s\" expands " + "to empty string, with value \"%s\" - ignored\n", + unexpanded_name, val); + } else + Var_SetWithFlags(scope, varname.str, val, VAR_SET_NONE); + + FStr_Done(&varname); } void @@ -1093,12 +1070,6 @@ Global_Set(const char *name, const char *value) Var_Set(SCOPE_GLOBAL, name, value); } -void -Global_SetExpand(const char *name, const char *value) -{ - Var_SetExpand(SCOPE_GLOBAL, name, value); -} - void Global_Delete(const char *name) { @@ -1129,17 +1100,18 @@ Var_Append(GNode *scope, const char *name, const char *val) DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, v->val.data); - if (v->fromEnv) { + if (v->fromEnvironment) { /* - * If the original variable came from the environment, - * we have to install it in the global scope (we - * could place it in the environment, but then we - * should provide a way to export other variables...) + * The variable originally came from the environment. + * Install it in the global scope (we could place it + * in the environment, but then we should provide a + * way to export other variables...) */ - v->fromEnv = false; + v->fromEnvironment = false; + v->shortLived = false; /* * This is the only place where a variable is - * created whose v->name is not the same as + * created in a scope, where v->name does not alias * scope->vars->key. */ HashTable_Set(&scope->vars, name, v); @@ -1174,22 +1146,14 @@ Var_AppendExpand(GNode *scope, const char *name, const char *val) assert(val != NULL); - if (strchr(name, '$') != NULL) { - char *expanded; - (void)Var_Subst(name, scope, VARE_WANTRES, &expanded); - /* TODO: handle errors */ - xname = FStr_InitOwn(expanded); - if (expanded[0] == '\0') { - DEBUG2(VAR, - "Var_AppendExpand: variable name \"%s\" expands " - "to empty string, with value \"%s\" - ignored\n", - name, val); - FStr_Done(&xname); - return; - } - } - - Var_Append(scope, xname.str, val); + Var_Expand(&xname, scope, VARE_WANTRES); + if (xname.str != name && xname.str[0] == '\0') + DEBUG2(VAR, + "Var_AppendExpand: variable name \"%s\" expands " + "to empty string, with value \"%s\" - ignored\n", + name, val); + else + Var_Append(scope, xname.str, val); FStr_Done(&xname); } @@ -1207,7 +1171,7 @@ Var_Exists(GNode *scope, const char *name) if (v == NULL) return false; - VarFreeEnv(v); + VarFreeShortLived(v); return true; } @@ -1225,13 +1189,7 @@ Var_ExistsExpand(GNode *scope, const char *name) FStr varname = FStr_InitRefer(name); bool exists; - if (strchr(varname.str, '$') != NULL) { - char *expanded; - (void)Var_Subst(varname.str, scope, VARE_WANTRES, &expanded); - /* TODO: handle errors */ - varname = FStr_InitOwn(expanded); - } - + Var_Expand(&varname, scope, VARE_WANTRES); exists = Var_Exists(scope, varname.str); FStr_Done(&varname); return exists; @@ -1258,13 +1216,13 @@ Var_Value(GNode *scope, const char *name) if (v == NULL) return FStr_InitRefer(NULL); - if (!v->fromEnv) + if (!v->shortLived) return FStr_InitRefer(v->val.data); - /* Since environment variables are short-lived, free it now. */ - FStr_Done(&v->name); - value = Buf_DoneData(&v->val); - free(v); + value = v->val.data; + v->val.data = NULL; + VarFreeShortLived(v); + return FStr_InitOwn(value); } @@ -1475,7 +1433,6 @@ ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data) { const struct ModifyWord_SysVSubstArgs *args = data; FStr rhs; - char *rhsExp; const char *percent; if (Substring_IsEmpty(word)) @@ -1487,11 +1444,7 @@ ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data) goto no_match; rhs = FStr_InitRefer(args->rhs); - if (strchr(rhs.str, '$') != NULL) { - (void)Var_Subst(args->rhs, args->scope, VARE_WANTRES, &rhsExp); - /* TODO: handle errors */ - rhs = FStr_InitOwn(rhsExp); - } + Var_Expand(&rhs, args->scope, VARE_WANTRES); percent = args->lhsPercent ? strchr(rhs.str, '%') : NULL; @@ -1605,58 +1558,55 @@ VarREError(int reerr, const regex_t *pat, const char *str) free(errbuf); } +/* In the modifier ':C', replace a backreference from \0 to \9. */ +static void +RegexReplaceBackref(char ref, SepBuf *buf, const char *wp, + const regmatch_t *m, size_t nsub) +{ + unsigned int n = ref - '0'; + + if (n >= nsub) + Error("No subexpression \\%u", n); + else if (m[n].rm_so == -1) { + if (opts.strict) + Error("No match for subexpression \\%u", n); + } else { + SepBuf_AddBytesBetween(buf, + wp + (size_t)m[n].rm_so, + wp + (size_t)m[n].rm_eo); + } +} + /* - * Replacement of regular expressions is not specified by POSIX, therefore - * re-implement it here. + * The regular expression matches the word; now add the replacement to the + * buffer, taking back-references from 'wp'. */ static void -RegexReplace(const char *replace, SepBuf *buf, const char *wp, +RegexReplace(Substring replace, SepBuf *buf, const char *wp, const regmatch_t *m, size_t nsub) { const char *rp; - unsigned int n; - for (rp = replace; *rp != '\0'; rp++) { - if (*rp == '\\' && (rp[1] == '&' || rp[1] == '\\')) { - SepBuf_AddBytes(buf, rp + 1, 1); - rp++; - continue; - } - - if (*rp == '&') { + for (rp = replace.start; rp != replace.end; rp++) { + if (*rp == '\\' && rp + 1 != replace.end && + (rp[1] == '&' || rp[1] == '\\')) + SepBuf_AddBytes(buf, ++rp, 1); + else if (*rp == '\\' && rp + 1 != replace.end && + ch_isdigit(rp[1])) + RegexReplaceBackref(*++rp, buf, wp, m, nsub); + else if (*rp == '&') { SepBuf_AddBytesBetween(buf, wp + (size_t)m[0].rm_so, wp + (size_t)m[0].rm_eo); - continue; - } - - if (*rp != '\\' || !ch_isdigit(rp[1])) { + } else SepBuf_AddBytes(buf, rp, 1); - continue; - } - - /* \0 to \9 backreference */ - n = rp[1] - '0'; - rp++; - - if (n >= nsub) { - Error("No subexpression \\%u", n); - } else if (m[n].rm_so == -1) { - if (opts.strict) { - Error("No match for subexpression \\%u", n); - } - } else { - SepBuf_AddBytesBetween(buf, - wp + (size_t)m[n].rm_so, - wp + (size_t)m[n].rm_eo); - } } } struct ModifyWord_SubstRegexArgs { regex_t re; size_t nsub; - const char *replace; + Substring replace; PatternFlags pflags; bool matched; }; @@ -1686,7 +1636,7 @@ ModifyWord_SubstRegex(Substring word, SepBuf *buf, void *data) if (xrv != REG_NOMATCH) VarREError(xrv, &args->re, "Unexpected regex error"); no_match: - SepBuf_AddStr(buf, wp); + SepBuf_AddBytesBetween(buf, wp, word.end); return; ok: @@ -1836,7 +1786,9 @@ SubstringWords_JoinFree(SubstringWords words) for (i = 0; i < words.len; i++) { if (i != 0) { - /* XXX: Use ch->sep instead of ' ', for consistency. */ + /* + * XXX: Use ch->sep instead of ' ', for consistency. + */ Buf_AddByte(&buf, ' '); } Buf_AddBytesBetween(&buf, @@ -1867,7 +1819,7 @@ VarQuote(const char *str, bool quoteDollar, LazyBuf *buf) LazyBuf_AddStr(buf, newline); continue; } - if (ch_isspace(*p) || is_shell_metachar((unsigned char)*p)) + if (ch_isspace(*p) || ch_is_shell_meta(*p)) LazyBuf_Add(buf, '\\'); LazyBuf_Add(buf, *p); if (quoteDollar && *p == '$') @@ -1940,15 +1892,15 @@ VarHash(const char *str) } static char * -VarStrftime(const char *fmt, bool zulu, time_t tim) +VarStrftime(const char *fmt, time_t t, bool gmt) { char buf[BUFSIZ]; - if (tim == 0) - time(&tim); + if (t == 0) + time(&t); if (*fmt == '\0') fmt = "%c"; - strftime(buf, sizeof buf, fmt, zulu ? gmtime(&tim) : localtime(&tim)); + strftime(buf, sizeof buf, fmt, gmt ? gmtime(&t) : localtime(&t)); buf[sizeof buf - 1] = '\0'; return bmake_strdup(buf); @@ -2036,9 +1988,9 @@ static const char ExprDefined_Name[][10] = { }; #if __STDC_VERSION__ >= 199901L -#define const_member const +#define const_member const #else -#define const_member /* no const possible */ +#define const_member /* no const possible */ #endif /* An expression based on a variable, such as $@ or ${VAR:Mpattern:Q}. */ @@ -2234,11 +2186,15 @@ ParseModifierPartSubst( VarEvalMode emode, ModChain *ch, LazyBuf *part, - /* For the first part of the modifier ':S', set anchorEnd if the last - * character of the pattern is a $. */ + /* + * For the first part of the modifier ':S', set anchorEnd if the last + * character of the pattern is a $. + */ PatternFlags *out_pflags, - /* For the second part of the :S modifier, allow ampersands to be - * escaped and replace unescaped ampersands with subst->lhs. */ + /* + * For the second part of the :S modifier, allow ampersands to be escaped + * and replace unescaped ampersands with subst->lhs. + */ struct ModifyWord_SubstArgs *subst ) { @@ -2508,9 +2464,11 @@ ApplyModifier_Defined(const char **pp, ModChain *ch) LazyBuf_Init(&buf, p); while (!IsDelimiter(*p, ch) && *p != '\0') { - /* XXX: This code is similar to the one in Var_Parse. - * See if the code can be merged. - * See also ApplyModifier_Match and ParseModifierPart. */ + /* + * XXX: This code is similar to the one in Var_Parse. See if + * the code can be merged. See also ApplyModifier_Match and + * ParseModifierPart. + */ /* Escaped delimiter or other special character */ /* See Buf_AddEscaped in for.c. */ @@ -2586,66 +2544,36 @@ TryParseTime(const char **pp, time_t *out_time) return true; } -/* :gmtime */ +/* :gmtime and :localtime */ static ApplyModifierResult -ApplyModifier_Gmtime(const char **pp, ModChain *ch) +ApplyModifier_Time(const char **pp, ModChain *ch) { Expr *expr; - time_t utc; - + time_t t; + const char *args; const char *mod = *pp; - if (!ModMatchEq(mod, "gmtime", ch)) - return AMR_UNKNOWN; + bool gmt = mod[0] == 'g'; - if (mod[6] == '=') { - const char *p = mod + 7; - if (!TryParseTime(&p, &utc)) { + if (!ModMatchEq(mod, gmt ? "gmtime" : "localtime", ch)) + return AMR_UNKNOWN; + args = mod + (gmt ? 6 : 9); + + if (args[0] == '=') { + const char *p = args + 1; + if (!TryParseTime(&p, &t)) { Parse_Error(PARSE_FATAL, - "Invalid time value at \"%s\"", mod + 7); + "Invalid time value at \"%s\"", p); return AMR_CLEANUP; } *pp = p; } else { - utc = 0; - *pp = mod + 6; + t = 0; + *pp = args; } expr = ch->expr; if (Expr_ShouldEval(expr)) - Expr_SetValueOwn(expr, - VarStrftime(Expr_Str(expr), true, utc)); - - return AMR_OK; -} - -/* :localtime */ -static ApplyModifierResult -ApplyModifier_Localtime(const char **pp, ModChain *ch) -{ - Expr *expr; - time_t utc; - - const char *mod = *pp; - if (!ModMatchEq(mod, "localtime", ch)) - return AMR_UNKNOWN; - - if (mod[9] == '=') { - const char *p = mod + 10; - if (!TryParseTime(&p, &utc)) { - Parse_Error(PARSE_FATAL, - "Invalid time value at \"%s\"", mod + 10); - return AMR_CLEANUP; - } - *pp = p; - } else { - utc = 0; - *pp = mod + 9; - } - - expr = ch->expr; - if (Expr_ShouldEval(expr)) - Expr_SetValueOwn(expr, - VarStrftime(Expr_Str(expr), false, utc)); + Expr_SetValueOwn(expr, VarStrftime(Expr_Str(expr), t, gmt)); return AMR_OK; } @@ -2700,7 +2628,6 @@ static ApplyModifierResult ApplyModifier_ShellCommand(const char **pp, ModChain *ch) { Expr *expr = ch->expr; - const char *errfmt; VarParseResult res; LazyBuf cmdBuf; FStr cmd; @@ -2711,14 +2638,18 @@ ApplyModifier_ShellCommand(const char **pp, ModChain *ch) return AMR_CLEANUP; cmd = LazyBuf_DoneGet(&cmdBuf); - - errfmt = NULL; - if (Expr_ShouldEval(expr)) - Expr_SetValueOwn(expr, Cmd_Exec(cmd.str, &errfmt)); - else + if (Expr_ShouldEval(expr)) { + char *output, *error; + output = Cmd_Exec(cmd.str, &error); + Expr_SetValueOwn(expr, output); + if (error != NULL) { + /* XXX: why still return AMR_OK? */ + Error("%s", error); + free(error); + } + } else Expr_SetValueRefer(expr, ""); - if (errfmt != NULL) - Error(errfmt, cmd.str); /* XXX: why still return AMR_OK? */ + FStr_Done(&cmd); Expr_Define(expr); @@ -2767,7 +2698,9 @@ ApplyModifier_Range(const char **pp, ModChain *ch) for (i = 0; i < n; i++) { if (i != 0) { - /* XXX: Use ch->sep instead of ' ', for consistency. */ + /* + * XXX: Use ch->sep instead of ' ', for consistency. + */ Buf_AddByte(&buf, ' '); } Buf_AddInt(&buf, 1 + (int)i); @@ -2858,7 +2791,7 @@ ParseModifier_Match(const char **pp, const ModChain *ch, static ApplyModifierResult ApplyModifier_Match(const char **pp, ModChain *ch) { - const char mod = **pp; + char mod = **pp; char *pattern; ParseModifier_Match(pp, ch, &pattern); @@ -2956,7 +2889,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch) int error; VarParseResult res; LazyBuf reBuf, replaceBuf; - FStr re, replace; + FStr re; char delim = (*pp)[1]; if (delim == '\0') { @@ -2977,8 +2910,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch) FStr_Done(&re); return AMR_CLEANUP; } - replace = LazyBuf_DoneGet(&replaceBuf); - args.replace = replace.str; + args.replace = LazyBuf_Get(&replaceBuf); args.pflags = PatternFlags_None(); args.matched = false; @@ -2986,7 +2918,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch) ParsePatternFlags(pp, &args.pflags, &oneBigWord); if (!ModChain_ShouldEval(ch)) { - FStr_Done(&replace); + LazyBuf_Done(&replaceBuf); FStr_Done(&re); return AMR_OK; } @@ -2994,7 +2926,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch) error = regcomp(&args.re, re.str, REG_EXTENDED); if (error != 0) { VarREError(error, &args.re, "Regex compilation error"); - FStr_Done(&replace); + LazyBuf_Done(&replaceBuf); FStr_Done(&re); return AMR_CLEANUP; } @@ -3006,7 +2938,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch) ModifyWords(ch, ModifyWord_SubstRegex, &args, oneBigWord); regfree(&args.re); - FStr_Done(&replace); + LazyBuf_Done(&replaceBuf); FStr_Done(&re); return AMR_OK; } @@ -3304,15 +3236,12 @@ ApplyModifier_Words(const char **pp, ModChain *ch) return AMR_BAD; } -#ifndef NUM_TYPE -# ifdef HAVE_LONG_LONG_INT -# define NUM_TYPE long long -# elif defined(_INT64_T_DECLARED) || defined(int64_t) -# define NUM_TYPE int64_t -# else -# define NUM_TYPE long -# define strtoll strtol -# endif +#if __STDC__ >= 199901L || defined(HAVE_LONG_LONG_INT) +# define NUM_TYPE long long +# define PARSE_NUM_TYPE strtoll +#else +# define NUM_TYPE long +# define PARSE_NUM_TYPE strtol #endif static NUM_TYPE @@ -3321,7 +3250,7 @@ num_val(Substring s) NUM_TYPE val; char *ep; - val = strtoll(s.start, &ep, 0); + val = PARSE_NUM_TYPE(s.start, &ep, 0); if (ep != s.start) { switch (*ep) { case 'K': @@ -3417,9 +3346,8 @@ ApplyModifier_Order(const char **pp, ModChain *ch) else goto bad; *pp += 3; - } else { + } else goto bad; - } if (!ModChain_ShouldEval(ch)) return AMR_OK; @@ -3446,54 +3374,55 @@ ApplyModifier_IfElse(const char **pp, ModChain *ch) { Expr *expr = ch->expr; VarParseResult res; - LazyBuf buf; - FStr then_expr, else_expr; + LazyBuf thenBuf; + LazyBuf elseBuf; - bool value = false; VarEvalMode then_emode = VARE_PARSE_ONLY; VarEvalMode else_emode = VARE_PARSE_ONLY; - CondEvalResult cond_rc = COND_PARSE; /* just not COND_INVALID */ + CondResult cond_rc = CR_TRUE; /* just not CR_ERROR */ if (Expr_ShouldEval(expr)) { - cond_rc = Cond_EvalCondition(expr->name, &value); - if (cond_rc != COND_INVALID && value) + cond_rc = Cond_EvalCondition(expr->name); + if (cond_rc == CR_TRUE) then_emode = expr->emode; - if (cond_rc != COND_INVALID && !value) + if (cond_rc == CR_FALSE) else_emode = expr->emode; } - (*pp)++; /* skip past the '?' */ - res = ParseModifierPart(pp, ':', then_emode, ch, &buf); + (*pp)++; /* skip past the '?' */ + res = ParseModifierPart(pp, ':', then_emode, ch, &thenBuf); if (res != VPR_OK) return AMR_CLEANUP; - then_expr = LazyBuf_DoneGet(&buf); - res = ParseModifierPart(pp, ch->endc, else_emode, ch, &buf); + res = ParseModifierPart(pp, ch->endc, else_emode, ch, &elseBuf); if (res != VPR_OK) { - FStr_Done(&then_expr); + LazyBuf_Done(&thenBuf); return AMR_CLEANUP; } - else_expr = LazyBuf_DoneGet(&buf); (*pp)--; /* Go back to the ch->endc. */ - if (cond_rc == COND_INVALID) { - Error("Bad conditional expression '%s' in '%s?%s:%s'", - expr->name, expr->name, then_expr.str, else_expr.str); - FStr_Done(&then_expr); - FStr_Done(&else_expr); + if (cond_rc == CR_ERROR) { + Substring thenExpr = LazyBuf_Get(&thenBuf); + Substring elseExpr = LazyBuf_Get(&elseBuf); + Error("Bad conditional expression '%s' in '%s?%.*s:%.*s'", + expr->name, expr->name, + (int)Substring_Length(thenExpr), thenExpr.start, + (int)Substring_Length(elseExpr), elseExpr.start); + LazyBuf_Done(&thenBuf); + LazyBuf_Done(&elseBuf); return AMR_CLEANUP; } if (!Expr_ShouldEval(expr)) { - FStr_Done(&then_expr); - FStr_Done(&else_expr); - } else if (value) { - Expr_SetValue(expr, then_expr); - FStr_Done(&else_expr); + LazyBuf_Done(&thenBuf); + LazyBuf_Done(&elseBuf); + } else if (cond_rc == CR_TRUE) { + Expr_SetValue(expr, LazyBuf_DoneGet(&thenBuf)); + LazyBuf_Done(&elseBuf); } else { - FStr_Done(&then_expr); - Expr_SetValue(expr, else_expr); + LazyBuf_Done(&thenBuf); + Expr_SetValue(expr, LazyBuf_DoneGet(&elseBuf)); } Expr_Define(expr); return AMR_OK; @@ -3534,27 +3463,18 @@ ApplyModifier_Assign(const char **pp, ModChain *ch) const char *op = mod + 1; if (op[0] == '=') - goto ok; - if ((op[0] == '!' || op[0] == '+' || op[0] == '?') && op[1] == '=') - goto ok; + goto found_op; + if ((op[0] == '+' || op[0] == '?' || op[0] == '!') && op[1] == '=') + goto found_op; return AMR_UNKNOWN; /* "::" */ -ok: +found_op: if (expr->name[0] == '\0') { *pp = mod + 1; return AMR_BAD; } - switch (op[0]) { - case '+': - case '?': - case '!': - *pp = mod + 3; - break; - default: - *pp = mod + 2; - break; - } + *pp = mod + (op[0] == '+' || op[0] == '?' || op[0] == '!' ? 3 : 2); res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &buf); if (res != VPR_OK) @@ -3572,31 +3492,25 @@ ApplyModifier_Assign(const char **pp, ModChain *ch) if (gv == NULL) scope = SCOPE_GLOBAL; else - VarFreeEnv(gv); + VarFreeShortLived(gv); } - switch (op[0]) { - case '+': + if (op[0] == '+') Var_Append(scope, expr->name, val.str); - break; - case '!': { - const char *errfmt; - char *cmd_output = Cmd_Exec(val.str, &errfmt); - if (errfmt != NULL) - Error(errfmt, val.str); - else - Var_Set(scope, expr->name, cmd_output); - free(cmd_output); - break; - } - case '?': - if (expr->defined == DEF_REGULAR) - break; - /* FALLTHROUGH */ - default: + else if (op[0] == '!') { + char *output, *error; + output = Cmd_Exec(val.str, &error); + if (error != NULL) { + Error("%s", error); + free(error); + } else + Var_Set(scope, expr->name, output); + free(output); + } else if (op[0] == '?' && expr->defined == DEF_REGULAR) { + /* Do nothing. */ + } else Var_Set(scope, expr->name, val.str); - break; - } + Expr_SetValueRefer(expr, ""); done: @@ -3731,7 +3645,9 @@ ApplyModifier_SysV(const char **pp, ModChain *ch) if (res != VPR_OK) return AMR_CLEANUP; - /* The SysV modifier lasts until the end of the variable expression. */ + /* + * The SysV modifier lasts until the end of the variable expression. + */ res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &rhsBuf); if (res != VPR_OK) { LazyBuf_Done(&lhsBuf); @@ -3775,10 +3691,12 @@ ApplyModifier_SunShell(const char **pp, ModChain *ch) *pp = p + 2; if (Expr_ShouldEval(expr)) { - const char *errfmt; - char *output = Cmd_Exec(Expr_Str(expr), &errfmt); - if (errfmt != NULL) - Error(errfmt, Expr_Str(expr)); + char *output, *error; + output = Cmd_Exec(Expr_Str(expr), &error); + if (error != NULL) { + Error("%s", error); + free(error); + } Expr_SetValueOwn(expr, output); } @@ -3867,15 +3785,14 @@ ApplyModifier(const char **pp, ModChain *ch) case 'E': return ApplyModifier_WordFunc(pp, ch, ModifyWord_Suffix); case 'g': - return ApplyModifier_Gmtime(pp, ch); + case 'l': + return ApplyModifier_Time(pp, ch); case 'H': return ApplyModifier_WordFunc(pp, ch, ModifyWord_Head); case 'h': return ApplyModifier_Hash(pp, ch); case 'L': return ApplyModifier_Literal(pp, ch); - case 'l': - return ApplyModifier_Localtime(pp, ch); case 'M': case 'N': return ApplyModifier_Match(pp, ch); @@ -4106,7 +4023,7 @@ ApplyModifiers( } *pp = p; - assert(Expr_Str(expr) != NULL); /* Use var_Error or varUndefined. */ + assert(Expr_Str(expr) != NULL); /* Use var_Error or varUndefined. */ return; bad_modifier: @@ -4417,17 +4334,18 @@ ParseVarnameLong( v = VarFindSubstring(name, scope, true); - /* At this point, p points just after the variable name, - * either at ':' or at endc. */ + /* + * At this point, p points just after the variable name, either at + * ':' or at endc. + */ - if (v == NULL) { - if (Substring_Equals(name, ".SUFFIXES")) - v = VarNew(Substring_Str(name), - Suff_NamesStr(), false, true); - else - v = FindLocalLegacyVar(name, scope, - out_true_extraModifiers); - } + if (v == NULL && Substring_Equals(name, ".SUFFIXES")) { + char *suffixes = Suff_NamesStr(); + v = VarNew(FStr_InitRefer(".SUFFIXES"), suffixes, + true, false, true); + free(suffixes); + } else if (v == NULL) + v = FindLocalLegacyVar(name, scope, out_true_extraModifiers); if (v == NULL) { /* @@ -4442,6 +4360,7 @@ ParseVarnameLong( *out_false_pp = p; *out_false_res = EvalUndefined(dynamic, start, p, name, emode, out_false_val); + LazyBuf_Done(&varname); return false; } @@ -4459,7 +4378,8 @@ ParseVarnameLong( * is still undefined, Var_Parse will return an empty string * instead of the actually computed value. */ - v = VarNew(LazyBuf_DoneGet(&varname), "", false, false); + v = VarNew(LazyBuf_DoneGet(&varname), "", + true, false, false); *out_true_exprDefined = DEF_UNDEF; } else LazyBuf_Done(&varname); @@ -4472,20 +4392,6 @@ ParseVarnameLong( return true; } -/* Free the environment variable now since we own it. */ -static void -FreeEnvVar(Var *v, Expr *expr) -{ - char *varValue = Buf_DoneData(&v->val); - if (expr->value.str == varValue) - expr->value.freeIt = varValue; - else - free(varValue); - - FStr_Done(&v->name); - free(v); -} - #if __STDC_VERSION__ >= 199901L #define Expr_Literal(name, value, emode, scope, defined) \ { name, value, emode, scope, defined } @@ -4626,8 +4532,14 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val) } expr.name = v->name.str; - if (v->inUse) + if (v->inUse) { + if (scope->fname != NULL) { + fprintf(stderr, "In a command near "); + PrintLocation(stderr, false, + scope->fname, scope->lineno); + } Fatal("Variable %s is recursive.", v->name.str); + } /* * XXX: This assignment creates an alias to the current value of the @@ -4644,8 +4556,8 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val) * Before applying any modifiers, expand any nested expressions from * the variable value. */ - if (strchr(Expr_Str(&expr), '$') != NULL && - VarEvalMode_ShouldEval(emode)) { + if (VarEvalMode_ShouldEval(emode) && + strchr(Expr_Str(&expr), '$') != NULL) { char *expanded; VarEvalMode nested_emode = emode; if (opts.strict) @@ -4664,7 +4576,7 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val) } if (haveModifier) { - p++; /* Skip initial colon. */ + p++; /* Skip initial colon. */ ApplyModifiers(&expr, &p, startc, endc); } @@ -4673,31 +4585,30 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val) *pp = p; - if (v->fromEnv) { - FreeEnvVar(v, &expr); - - } else if (expr.defined != DEF_REGULAR) { - if (expr.defined == DEF_UNDEF) { - if (dynamic) { - Expr_SetValueOwn(&expr, - bmake_strsedup(start, p)); - } else { - /* - * The expression is still undefined, - * therefore discard the actual value and - * return an error marker instead. - */ - Expr_SetValueRefer(&expr, - emode == VARE_UNDEFERR - ? var_Error : varUndefined); - } + if (expr.defined == DEF_UNDEF) { + if (dynamic) + Expr_SetValueOwn(&expr, bmake_strsedup(start, p)); + else { + /* + * The expression is still undefined, therefore + * discard the actual value and return an error marker + * instead. + */ + Expr_SetValueRefer(&expr, + emode == VARE_UNDEFERR + ? var_Error : varUndefined); } - /* XXX: This is not standard memory management. */ - if (expr.value.str != v->val.data) - Buf_Done(&v->val); - FStr_Done(&v->name); - free(v); } + + if (v->shortLived) { + if (expr.value.str == v->val.data) { + /* move ownership */ + expr.value.freeIt = v->val.data; + v->val.data = NULL; + } + VarFreeShortLived(v); + } + *out_val = expr.value; return VPR_OK; /* XXX: Is not correct in all cases */ } @@ -4726,7 +4637,7 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, if (val.str == var_Error || val.str == varUndefined) { if (!VarEvalMode_ShouldKeepUndef(emode)) { p = nested_p; - } else if (emode == VARE_UNDEFERR || val.str == var_Error) { + } else if (val.str == var_Error) { /* * XXX: This condition is wrong. If val == var_Error, @@ -4748,10 +4659,12 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, p = nested_p; *inout_errorReported = true; } else { - /* Copy the initial '$' of the undefined expression, + /* + * 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. */ + * expand nested expressions if already possible. See + * unit-tests/varparse-undef-partial.mk. + */ Buf_AddByte(buf, *p); p++; } @@ -4798,9 +4711,11 @@ Var_Subst(const char *str, GNode *scope, VarEvalMode emode, char **out_res) const char *p = str; Buffer res; - /* Set true if an error has already been reported, - * to prevent a plethora of messages when recursing */ - /* XXX: Why is the 'static' necessary here? */ + /* + * Set true if an error has already been reported, to prevent a + * plethora of messages when recursing + */ + /* See varparse-errors.mk for why the 'static' is necessary here. */ static bool errorReported; Buf_Init(&res); @@ -4819,6 +4734,19 @@ Var_Subst(const char *str, GNode *scope, VarEvalMode emode, char **out_res) return VPR_OK; } +void +Var_Expand(FStr *str, GNode *scope, VarEvalMode emode) +{ + char *expanded; + + if (strchr(str->str, '$') == NULL) + return; + (void)Var_Subst(str->str, scope, emode, &expanded); + /* TODO: handle errors */ + FStr_Done(str); + *str = FStr_InitOwn(expanded); +} + /* Initialize the variables module. */ void Var_Init(void)