aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon J. Gerraty <sjg@FreeBSD.org>2021-01-14 01:24:34 +0000
committerSimon J. Gerraty <sjg@FreeBSD.org>2021-01-14 01:24:34 +0000
commit8e11a9b4250be3c3379c45fa820bff78d99d5946 (patch)
treeea4954dbe7647b6211a20458b2a881e3d943639f
parent1b65f0bd2bda7121a90f8cb4c1cacaa20f1b681d (diff)
downloadsrc-vendor/NetBSD/bmake/20210110.tar.gz
src-vendor/NetBSD/bmake/20210110.zip
Import bmake-20210110vendor/NetBSD/bmake/20210110
Quite a lot of churn on style, but lots of good work refactoring complicated functions and lots more unit-tests. Thanks mostly to rillig at NetBSD Some interesting entries from ChangeLog o .MAKE.{UID,GID} represent uid and gid running make. o allow env var MAKE_OBJDIR_CHECK_WRITABLE=no to skip writable checks in InitObjdir. Explicit .OBJDIR target always allows read-only directory. o add more unit tests for META MODE Change-Id: I4d3bcf08b4c864d98b343f602efe5a75dbfa7a94
-rw-r--r--ChangeLog241
-rw-r--r--FILES64
-rw-r--r--LICENSE4
-rw-r--r--PSD.doc/tutorial.ms10
-rw-r--r--VERSION2
-rw-r--r--arch.c1323
-rw-r--r--bmake.118
-rw-r--r--bmake.cat1248
-rw-r--r--buf.c139
-rw-r--r--buf.h28
-rw-r--r--compat.c932
-rw-r--r--cond.c1611
-rw-r--r--dir.c2047
-rw-r--r--dir.h36
-rwxr-xr-xenum.c11
-rwxr-xr-xenum.h69
-rw-r--r--filemon/filemon.h6
-rw-r--r--filemon/filemon_dev.c6
-rw-r--r--filemon/filemon_ktrace.c79
-rw-r--r--for.c633
-rw-r--r--hash.c41
-rw-r--r--hash.h72
-rwxr-xr-ximport.sh87
-rw-r--r--job.c3876
-rw-r--r--job.h84
-rw-r--r--lst.c304
-rw-r--r--lst.h88
-rw-r--r--main.c364
-rw-r--r--make-conf.h20
-rw-r--r--make.118
-rw-r--r--make.c1825
-rw-r--r--make.h812
-rw-r--r--make_malloc.c7
-rw-r--r--make_malloc.h12
-rw-r--r--meta.c359
-rw-r--r--meta.h4
-rw-r--r--metachar.h4
-rw-r--r--mk/ChangeLog39
-rw-r--r--mk/dirdeps-options.mk5
-rw-r--r--mk/dirdeps-targets.mk7
-rw-r--r--mk/dirdeps.mk25
-rw-r--r--mk/init.mk17
-rwxr-xr-x[-rw-r--r--]mk/install-mk4
-rw-r--r--mk/meta.subdir.mk4
-rw-r--r--[-rwxr-xr-x]mk/mkopt.sh0
-rw-r--r--mk/own.mk6
-rw-r--r--mk/sys.mk6
-rw-r--r--nonints.h278
-rw-r--r--[-rwxr-xr-x]os.sh0
-rw-r--r--parse.c4058
-rw-r--r--pathnames.h15
-rw-r--r--str.c15
-rw-r--r--suff.c2897
-rw-r--r--targ.c568
-rw-r--r--trace.c16
-rw-r--r--trace.h6
-rw-r--r--unit-tests/Makefile159
-rw-r--r--unit-tests/cmd-errors-jobs.exp9
-rw-r--r--unit-tests/cmd-errors-jobs.mk32
-rw-r--r--unit-tests/cmd-errors.mk4
-rw-r--r--unit-tests/cmdline.mk2
-rw-r--r--unit-tests/compat-error.exp15
-rw-r--r--unit-tests/compat-error.mk37
-rw-r--r--unit-tests/cond-eof.exp9
-rw-r--r--unit-tests/cond-eof.mk20
-rw-r--r--unit-tests/cond-func-empty.mk27
-rw-r--r--unit-tests/cond-func-exists.mk11
-rw-r--r--unit-tests/cond-func-make-main.exp3
-rw-r--r--unit-tests/cond-func-make-main.mk62
-rw-r--r--unit-tests/cond-short.exp12
-rw-r--r--unit-tests/cond-short.mk64
-rw-r--r--unit-tests/cond-token-string.exp2
-rw-r--r--unit-tests/dep-percent.exp5
-rw-r--r--unit-tests/depsrc-meta.exp4
-rw-r--r--unit-tests/depsrc-meta.mk27
-rw-r--r--unit-tests/depsrc-optional.exp18
-rw-r--r--unit-tests/depsrc.exp3
-rw-r--r--unit-tests/depsrc.mk17
-rw-r--r--unit-tests/deptgt-begin-fail-indirect.exp6
-rw-r--r--unit-tests/deptgt-begin-fail-indirect.mk16
-rw-r--r--unit-tests/deptgt-begin-fail.exp6
-rw-r--r--unit-tests/deptgt-begin-fail.mk10
-rw-r--r--unit-tests/deptgt-end-fail-all.exp7
-rw-r--r--unit-tests/deptgt-end-fail-all.mk19
-rw-r--r--unit-tests/deptgt-end-fail-indirect.exp7
-rw-r--r--unit-tests/deptgt-end-fail-indirect.mk16
-rw-r--r--unit-tests/deptgt-end-fail.exp163
-rw-r--r--unit-tests/deptgt-end-fail.mk69
-rw-r--r--unit-tests/deptgt-suffixes.exp26
-rw-r--r--unit-tests/deptgt-suffixes.mk23
-rw-r--r--unit-tests/deptgt.exp1
-rw-r--r--unit-tests/deptgt.mk10
-rw-r--r--unit-tests/directive-elif.exp32
-rw-r--r--unit-tests/directive-elif.mk126
-rw-r--r--unit-tests/directive-else.exp16
-rw-r--r--unit-tests/directive-else.mk15
-rw-r--r--unit-tests/directive-endfor.exp4
-rw-r--r--unit-tests/directive-endfor.mk9
-rw-r--r--unit-tests/directive-endif.exp9
-rw-r--r--unit-tests/directive-endif.mk28
-rw-r--r--unit-tests/directive-error.mk6
-rw-r--r--unit-tests/directive-export-env.mk4
-rw-r--r--unit-tests/directive-export-impl.exp56
-rw-r--r--unit-tests/directive-export-impl.mk62
-rw-r--r--unit-tests/directive-export-literal.mk4
-rw-r--r--unit-tests/directive-export.exp5
-rw-r--r--unit-tests/directive-export.mk16
-rw-r--r--unit-tests/directive-for-errors.exp22
-rw-r--r--unit-tests/directive-for-errors.mk75
-rw-r--r--unit-tests/directive-for-escape.exp74
-rw-r--r--unit-tests/directive-for-escape.mk96
-rw-r--r--unit-tests/directive-for-lines.exp10
-rw-r--r--unit-tests/directive-for-lines.mk32
-rw-r--r--unit-tests/directive-for-null.exp10
-rw-r--r--unit-tests/directive-for-null.mk19
-rwxr-xr-xunit-tests/directive-for.exp7
-rwxr-xr-xunit-tests/directive-for.mk9
-rw-r--r--unit-tests/directive-if.exp6
-rw-r--r--unit-tests/directive-if.mk14
-rwxr-xr-xunit-tests/directive-include.mk5
-rw-r--r--unit-tests/directive-info.exp23
-rw-r--r--unit-tests/directive-info.mk18
-rw-r--r--unit-tests/directive-misspellings.exp45
-rw-r--r--unit-tests/directive-misspellings.mk79
-rw-r--r--unit-tests/directive-undef.exp3
-rw-r--r--unit-tests/directive-undef.mk85
-rw-r--r--unit-tests/directive-unexport-env.exp19
-rw-r--r--unit-tests/directive-unexport-env.mk19
-rw-r--r--unit-tests/directive-unexport.exp13
-rw-r--r--unit-tests/directive-unexport.mk12
-rw-r--r--unit-tests/directive-warning.exp16
-rw-r--r--unit-tests/directive-warning.mk10
-rw-r--r--unit-tests/jobs-error-indirect.exp8
-rw-r--r--unit-tests/jobs-error-indirect.mk21
-rw-r--r--unit-tests/jobs-error-nested-make.exp11
-rw-r--r--unit-tests/jobs-error-nested-make.mk20
-rw-r--r--unit-tests/jobs-error-nested.exp15
-rw-r--r--unit-tests/jobs-error-nested.mk20
-rwxr-xr-xunit-tests/make-exported.mk2
-rw-r--r--unit-tests/meta-cmd-cmp.exp37
-rw-r--r--unit-tests/meta-cmd-cmp.mk52
-rw-r--r--unit-tests/modmisc.exp1
-rw-r--r--unit-tests/modmisc.mk29
-rw-r--r--unit-tests/opt-chdir.exp4
-rw-r--r--unit-tests/opt-debug-errors.exp5
-rw-r--r--unit-tests/opt-debug-graph1.exp36
-rw-r--r--unit-tests/opt-debug-jobs.exp4
-rw-r--r--unit-tests/opt-debug-lint.exp3
-rw-r--r--unit-tests/opt-debug-lint.mk15
-rw-r--r--unit-tests/opt-file.exp13
-rw-r--r--unit-tests/opt-file.mk101
-rw-r--r--unit-tests/opt-jobs-no-action.exp61
-rw-r--r--unit-tests/opt-jobs-no-action.mk102
-rw-r--r--unit-tests/opt-keep-going-multiple.exp9
-rw-r--r--unit-tests/opt-keep-going-multiple.mk21
-rw-r--r--unit-tests/opt-keep-going.exp5
-rw-r--r--unit-tests/opt-keep-going.mk6
-rw-r--r--unit-tests/opt-no-action-runflags.exp34
-rw-r--r--unit-tests/opt-no-action-runflags.mk32
-rw-r--r--unit-tests/opt.exp4
-rw-r--r--unit-tests/posix.exp5
-rw-r--r--unit-tests/qequals.exp2
-rw-r--r--unit-tests/qequals.mk8
-rwxr-xr-xunit-tests/sh-dots.exp12
-rw-r--r--unit-tests/sh-errctl.exp27
-rw-r--r--unit-tests/sh-errctl.mk26
-rw-r--r--unit-tests/sh-flags.exp4325
-rw-r--r--unit-tests/sh-flags.mk138
-rw-r--r--unit-tests/sh-jobs.exp7
-rw-r--r--unit-tests/sh-jobs.mk34
-rw-r--r--unit-tests/sh-meta-chars.mk17
-rw-r--r--unit-tests/shell-csh.mk6
-rw-r--r--unit-tests/suff-add-later.exp8
-rw-r--r--unit-tests/suff-clear-regular.exp5
-rw-r--r--unit-tests/suff-clear-regular.mk3
-rw-r--r--unit-tests/suff-clear-single.exp5
-rw-r--r--unit-tests/suff-incomplete.exp42
-rw-r--r--unit-tests/suff-incomplete.mk31
-rw-r--r--unit-tests/suff-lookup.exp21
-rw-r--r--unit-tests/suff-main-several.exp141
-rw-r--r--unit-tests/suff-main-several.mk42
-rw-r--r--unit-tests/suff-phony.exp13
-rw-r--r--unit-tests/suff-phony.mk21
-rw-r--r--unit-tests/suff-rebuild.exp76
-rw-r--r--unit-tests/suff-rebuild.mk23
-rw-r--r--unit-tests/suff-self.exp5
-rw-r--r--unit-tests/suff-transform-debug.exp62
-rw-r--r--unit-tests/suff-transform-debug.mk12
-rw-r--r--unit-tests/suff-transform-endless.exp44
-rw-r--r--unit-tests/suff-transform-endless.mk9
-rw-r--r--unit-tests/suff-transform-expand.exp5
-rw-r--r--unit-tests/suff-transform-select.exp45
-rw-r--r--unit-tests/suff-transform-select.mk7
-rw-r--r--unit-tests/use-inference.exp5
-rw-r--r--unit-tests/use-inference.mk6
-rw-r--r--unit-tests/var-op-default.mk72
-rw-r--r--unit-tests/var-op-expand.exp9
-rw-r--r--unit-tests/var-op-expand.mk191
-rw-r--r--unit-tests/vardebug.exp2
-rw-r--r--unit-tests/varmisc.mk14
-rw-r--r--unit-tests/varmod-defined.exp18
-rw-r--r--unit-tests/varmod-edge.exp41
-rw-r--r--unit-tests/varmod-extension.exp1
-rw-r--r--unit-tests/varmod-extension.mk4
-rw-r--r--unit-tests/varmod-gmtime.exp40
-rw-r--r--unit-tests/varmod-gmtime.mk197
-rw-r--r--unit-tests/varmod-head.exp1
-rw-r--r--unit-tests/varmod-head.mk4
-rw-r--r--unit-tests/varmod-ifelse.exp4
-rw-r--r--unit-tests/varmod-ifelse.mk20
-rw-r--r--unit-tests/varmod-indirect.exp59
-rw-r--r--unit-tests/varmod-indirect.mk157
-rw-r--r--unit-tests/varmod-localtime.exp40
-rw-r--r--unit-tests/varmod-localtime.mk196
-rw-r--r--unit-tests/varmod-range.exp9
-rw-r--r--unit-tests/varmod-root.exp1
-rw-r--r--unit-tests/varmod-root.mk4
-rw-r--r--unit-tests/varmod-subst-regex.exp2
-rw-r--r--unit-tests/varmod-subst-regex.mk7
-rw-r--r--unit-tests/varmod-sysv.exp4
-rw-r--r--unit-tests/varmod-sysv.mk16
-rw-r--r--unit-tests/varmod-tail.exp1
-rw-r--r--unit-tests/varmod-tail.mk4
-rw-r--r--unit-tests/varmod-to-many-words.mk16
-rw-r--r--unit-tests/varmod-to-one-word.mk16
-rw-r--r--unit-tests/varmod-to-separator.exp2
-rw-r--r--unit-tests/varmod.exp2
-rw-r--r--unit-tests/varmod.mk4
-rw-r--r--unit-tests/varname-dot-makeflags.exp3
-rw-r--r--unit-tests/varname-dot-makeflags.mk15
-rwxr-xr-xunit-tests/varname-dot-shell.exp2
-rw-r--r--unit-tests/varname-make_print_var_on_error-jobs.exp5
-rw-r--r--unit-tests/varname-make_print_var_on_error-jobs.mk15
-rw-r--r--unit-tests/varname-make_print_var_on_error.exp3
-rw-r--r--unit-tests/varname-make_print_var_on_error.mk15
-rw-r--r--unit-tests/varname-makeflags.mk24
-rw-r--r--unit-tests/varparse-dynamic.mk12
-rw-r--r--unit-tests/varparse-errors.exp6
-rw-r--r--unit-tests/varparse-errors.mk18
-rw-r--r--util.c91
-rw-r--r--var.c6010
241 files changed, 24256 insertions, 14514 deletions
diff --git a/ChangeLog b/ChangeLog
index ac723511d75a..5cf7f1f45384 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,8 +1,247 @@
+2021-01-10 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20210110
+ Merge with NetBSD make, pick up
+ o fix lint warnings
+ o consistently use boolean expressions in conditions
+
+2021-01-08 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20210108
+ Merge with NetBSD make, pick up
+ o job.c: back to polling token pipe if we want a token
+ o main.c: always print 'stopped in' on first call
+ The execption is if we bail because of an abort token
+ in which case just exit 6.
+
+2021-01-01 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20210101
+ Merge with NetBSD make, pick up
+ o Happy New Year!
+ o rename CmdOpts.lint to strict
+ o exit 2 on technical errors
+ o replace pointers in controlling conditions with booleans
+ o replace global preserveUndefined with VARE_KEEP_UNDEF
+ o compat.c: re-export variables from the actual make process
+ if using vfork this is the effect anyway
+ o cond.c: clean up VarParseResult constants
+ o for.c: fix undefined behavior in SubstVarLong
+ make control flow in SubstVarLong of .for loops more obvious
+ clean up SubstVarShort in .for loops
+ extract ForSubstBody from ForReadMore
+ clean up ForReadMore
+ simplify termination condition for .for loop
+ add error handling for .for loop items
+ job.c: re-export variables from the actual make process
+ parse.c: remove mmap for loading files, only allow files < 1 GiB
+ fix edge case in := with undefined in variable name
+ skip variable expansion in ParseDependencyTargetWord
+ var.c: split ExportVar into separate functions
+ clean up code in extracted ExportVar functions
+ remove dead code from ApplyModifiersIndirect
+ split Var_Subst into easily understandable functions
+ clean up VarParseResult constants
+
+2020-12-25 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * main.c: use .MAKE.DEPENDFILE as set by makefiles
+
+2020-12-22 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201222
+ Merge with NetBSD make, pick up
+ o make DEBUG macro return boolean
+ o parse.c: fix assertion failure for files without trailing newline
+ o var.c: allow .undef to undefine multiple variables at once
+ remove excess newline from parse errors
+
+2020-12-21 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201221
+ Merge with NetBSD make, pick up
+ o some unit-test updates
+
+2020-12-20 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201220
+ Merge with NetBSD make, pick up
+ o more unit tests
+ o return FStr from Var_Parse and Var_Value
+ o spell nonexistent consistently
+ o add str_basename to reduce duplicate code
+ o compat.c: fix .ERROR_TARGET in compat -k mode
+ extract InitSignals from Compat_Run
+ extract UseShell from Compat_RunCommand
+ o cond.c: error out if an '.endif' or '.else' contain extraneous text
+ o for.c: rename ForIterate to ForReadMore
+ o hash.c: clean up hash function for HashTable
+ o lst.c: rename Vector.priv_cap to cap
+ o main.c: remove constant parameter from MakeMode
+ o make.c: use symbolic time for 0 in Make_Recheck
+ extract MakeChildren from MakeStartJobs
+ o parse.c: clean up memory handling in VarAssign_EvalShell, Parse_DoVar
+ fix error message for .info/.warning/.error without argument
+ extract Var_Undef from ParseDirective
+ extract ParseSkippedBranches, ParseForLoop from ParseReadLine
+ rename mode constants for ParseGetLine to be more expressive
+ reduce debugging details in Parse_SetInput
+ fix line numbers in .for loops
+ split ParseGetLine into separate functions
+ fix garbled output for failed shell command
+ var.c: remove redundant assignment in ApplyModifier_SysV
+ error out on unknown variable modifiers at parse time
+ remove wrong error message for indirect modifier in lint mode
+ extract ApplySingleModifier from ApplyModifiers
+ use FStr for memory management in Var_SetWithFlags
+ extract SetVar from Var_SetWithFlags
+ use FStr in VarNew
+ extract string functions from ApplyModifier_To
+ error out if .undef has not exactly 1 argument
+ extract Var_DeleteVar from Var_Delete
+ extract Var_Undef from ParseDirective
+ clean up memory management for expanding variable expressions
+
+2020-12-12 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * avoid %zu
+
+ * lst.c: avoid anonymous union
+
+ * VERSION (_MAKE_VERSION): 20201212
+ Merge with NetBSD make, pick up
+ o more unit tests
+ o inline Targ_Ignore and Targ_Silent
+ o split JobFlags into separate fields
+ o remove const from function parameters (left overs from refactoring)
+ o eliminate boolean argument of Var_Export
+ o make API of Buf_Init simpler
+ o rename ParseRunOptions to ParseCommandFlags
+ o replace *line with line[0]
+ o compat.c: fix wrong exit status for multiple failed main targets
+ refactor Compat_Run to show the error condition more clearly
+ don't make .END if the main targets already failed (-k mode)
+ fix exit status in -k mode if a dependency fails
+ o for.c: clean up Buf_AddEscaped in .for loops
+ o job.c: extract ShellWriter_ErrOn from JobPrintCommand
+ make Job_Touch simpler
+ refactor JobFinish
+ rename Shell.exitFlag to errFlag
+ move Job.xtraced to ShellWriter
+ make printing of shell commands independent from the job
+ rename shell flags in struct Shell
+ extract JobOpenTmpFile from JobStart
+ rename RunFlags to CommandFlags
+ split various Job.* into separate fields
+ rename commandShell to shell
+ extract InitShellNameAndPath from Shell_Init
+ replace signal handling macros with local functions
+ replace macro MESSAGE with local function
+ parse.c: error out on null bytes in makefiles
+ error out on misspelled directives
+ rename IFile.nextbuf to readMore
+ fix undefined behavior in ParseEOF
+ str.c: remove redundant call to strlen in Str_Words
+ var.c: error out on misspelled .unexport-env
+ error out on misspelled .export directives
+ extract ExportVars from Var_Export
+ extract ExportVarsExpand from Var_Export
+ eliminate boolean argument of Var_Export
+ fix undefined behavior when exporting ${:U }
+ rename Var_ExportVars to Var_ReexportVars
+ rename Var_Export1 to ExportVar
+
+2020-12-06 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201206
+ Merge with NetBSD make, pick up
+ o more unit tests
+ o inline macros for debug logging
+ o use consistent variable names for list nodes
+ o define constants for enum zero-values
+ o dir.c: use fixed format for debug output of the directory cache
+ remove Dir_InitDir
+ o lst.c: inline Lst_Enqueue, Vector_Done
+ o meta.c: remove unused parameter from meta_needed
+ o parse.c: rename parse functions
+ o suff.c: extract ExpandChildrenRegular from ExpandChildren
+ o targ.c: don't concatenate identifiers in Targ_PrintType
+ o var.c: remove comment decoration
+ extract UnexportVars from Var_UnExport
+ extract GetVarnamesToUnexport from Var_UnExport
+ extract UnexportEnv from Var_UnExport
+ extract UnexportVar from Var_UnExport
+ move CleanEnv to UnexportVars
+ replace pointer comparisons with enum
+ add FStr to var.c to make memory handling simpler
+ use FStr in Var_UnExport
+ move type definitions in var.c to the top
+ extract FreeEnvVar from Var_Parse
+ extract ShuffleStrings from ApplyModifier_Order
+
+2020-11-30 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201130
+ Merge with NetBSD make, pick up
+ o add unit tests for META MODE
+ o reduce memory allocation for dirSearchPath, GNode.parents,
+ GNode.children, OpenDirs
+ o reduce pointer indirection for GNode.cohorts and
+ GNode.implicitParents
+ o remove pointer indirection from GNode.commands
+ o inline Lst_ForEachUntil in meta mode
+ o dir.c: fix memory leak for lstat cache in -DCLEANUP mode
+ clean up memory management for CachedDirs
+ fix the reference count of dotLast going negative
+ add debug logging for OpenDirs_Done
+ extract CacheNewDir from Dir_AddDir
+ add debug logging for reference counting of CachedDir
+ rename some Dir functions to SearchPath
+ o job.c: rename some global variables
+ o main.c: reduce memory allocation in ReadBuiltinRules
+ reduce memory allocation in CmdOpts.create, CmdOpts.variables,
+ CmdOpts.makefiles
+ Add .MAKE.UID and .MAKE.GID
+ o make.c: reduce memory allocation for/in toBeMade,
+ Make_ProcessWait, Make_ExpandUse
+ o meta.c: reduce memory allocation in meta_oodate
+ o parse.c: reduce memory allocations for parsing dependencies and
+ targets
+ o suff.c: reduce memory allocation in suffix handling
+
+2020-11-24 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201124
+ Merge with NetBSD make, pick up
+ o .MAKE.{UID,GID} represent uid and gid running make.
+ o fix error handling for .BEGIN and .END dependency in -k mode
+ o fix missing "Stop." after failed .END node in -k mode
+ o use properly typed comparisons in boolean contexts
+ o replace a few HashTable_CreateEntry with HashTable_Set
+ o add HashSet type
+ o compat.c: split Compat_Make into smaller functions
+ extract DebugFailedTarget from Compat_RunCommand
+ o dir.c: refactor Dir_UpdateMTime
+ migrate CachedDir.files from HashTable to HashSet
+ o make.c: add high-level API for GNode.made
+
+2020-11-22 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201122
+ Merge with NetBSD make, pick up
+ o rename GNode.context to vars
+ o suff.c: cleanup and refactor
+ rename some functions and vars to better reflect usage
+ add high-level API for CandidateSearcher
+ o targ.c: add more debug logging for suffix handling
+ o more unit tests
+ o add debug logging for setting and resetting the main target
+
2020-11-17 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20201117
Merge with NetBSD make, pick up
- o fix some unit-tests when dash is .SHELL
+ o fix some unit-tests when .SHELL is dash
o rename Targ_NewGN to GNode_New
o make some GNode functions const
o main.c: call Targ_Init before Var_Init
diff --git a/FILES b/FILES
index dedc29767658..8e2513e1c183 100644
--- a/FILES
+++ b/FILES
@@ -75,6 +75,8 @@ unit-tests/archive-suffix.exp
unit-tests/archive-suffix.mk
unit-tests/archive.exp
unit-tests/archive.mk
+unit-tests/cmd-errors-jobs.exp
+unit-tests/cmd-errors-jobs.mk
unit-tests/cmd-errors-lint.exp
unit-tests/cmd-errors-lint.mk
unit-tests/cmd-errors.exp
@@ -87,6 +89,8 @@ unit-tests/cmdline.exp
unit-tests/cmdline.mk
unit-tests/comment.exp
unit-tests/comment.mk
+unit-tests/compat-error.exp
+unit-tests/compat-error.mk
unit-tests/cond-cmp-numeric-eq.exp
unit-tests/cond-cmp-numeric-eq.mk
unit-tests/cond-cmp-numeric-ge.exp
@@ -105,6 +109,8 @@ unit-tests/cond-cmp-string.exp
unit-tests/cond-cmp-string.mk
unit-tests/cond-cmp-unary.exp
unit-tests/cond-cmp-unary.mk
+unit-tests/cond-eof.exp
+unit-tests/cond-eof.mk
unit-tests/cond-func-commands.exp
unit-tests/cond-func-commands.mk
unit-tests/cond-func-defined.exp
@@ -113,6 +119,8 @@ unit-tests/cond-func-empty.exp
unit-tests/cond-func-empty.mk
unit-tests/cond-func-exists.exp
unit-tests/cond-func-exists.mk
+unit-tests/cond-func-make-main.exp
+unit-tests/cond-func-make-main.mk
unit-tests/cond-func-make.exp
unit-tests/cond-func-make.mk
unit-tests/cond-func-target.exp
@@ -213,12 +221,22 @@ unit-tests/depsrc-wait.exp
unit-tests/depsrc-wait.mk
unit-tests/depsrc.exp
unit-tests/depsrc.mk
+unit-tests/deptgt-begin-fail-indirect.exp
+unit-tests/deptgt-begin-fail-indirect.mk
+unit-tests/deptgt-begin-fail.exp
+unit-tests/deptgt-begin-fail.mk
unit-tests/deptgt-begin.exp
unit-tests/deptgt-begin.mk
unit-tests/deptgt-default.exp
unit-tests/deptgt-default.mk
unit-tests/deptgt-delete_on_error.exp
unit-tests/deptgt-delete_on_error.mk
+unit-tests/deptgt-end-fail-all.exp
+unit-tests/deptgt-end-fail-all.mk
+unit-tests/deptgt-end-fail-indirect.exp
+unit-tests/deptgt-end-fail-indirect.mk
+unit-tests/deptgt-end-fail.exp
+unit-tests/deptgt-end-fail.mk
unit-tests/deptgt-end-jobs.exp
unit-tests/deptgt-end-jobs.mk
unit-tests/deptgt-end.exp
@@ -279,6 +297,8 @@ unit-tests/directive-elifnmake.exp
unit-tests/directive-elifnmake.mk
unit-tests/directive-else.exp
unit-tests/directive-else.mk
+unit-tests/directive-endfor.exp
+unit-tests/directive-endfor.mk
unit-tests/directive-endif.exp
unit-tests/directive-endif.mk
unit-tests/directive-error.exp
@@ -287,12 +307,22 @@ unit-tests/directive-export-env.exp
unit-tests/directive-export-env.mk
unit-tests/directive-export-gmake.exp
unit-tests/directive-export-gmake.mk
+unit-tests/directive-export-impl.exp
+unit-tests/directive-export-impl.mk
unit-tests/directive-export-literal.exp
unit-tests/directive-export-literal.mk
unit-tests/directive-export.exp
unit-tests/directive-export.mk
+unit-tests/directive-for-errors.exp
+unit-tests/directive-for-errors.mk
+unit-tests/directive-for-escape.exp
+unit-tests/directive-for-escape.mk
unit-tests/directive-for-generating-endif.exp
unit-tests/directive-for-generating-endif.mk
+unit-tests/directive-for-lines.exp
+unit-tests/directive-for-lines.mk
+unit-tests/directive-for-null.exp
+unit-tests/directive-for-null.mk
unit-tests/directive-for.exp
unit-tests/directive-for.mk
unit-tests/directive-hyphen-include.exp
@@ -315,6 +345,8 @@ unit-tests/directive-include.exp
unit-tests/directive-include.mk
unit-tests/directive-info.exp
unit-tests/directive-info.mk
+unit-tests/directive-misspellings.exp
+unit-tests/directive-misspellings.mk
unit-tests/directive-sinclude.exp
unit-tests/directive-sinclude.mk
unit-tests/directive-undef.exp
@@ -365,10 +397,18 @@ unit-tests/job-flags.exp
unit-tests/job-flags.mk
unit-tests/job-output-long-lines.exp
unit-tests/job-output-long-lines.mk
+unit-tests/jobs-error-indirect.exp
+unit-tests/jobs-error-indirect.mk
+unit-tests/jobs-error-nested-make.exp
+unit-tests/jobs-error-nested-make.mk
+unit-tests/jobs-error-nested.exp
+unit-tests/jobs-error-nested.mk
unit-tests/lint.exp
unit-tests/lint.mk
unit-tests/make-exported.exp
unit-tests/make-exported.mk
+unit-tests/meta-cmd-cmp.exp
+unit-tests/meta-cmd-cmp.mk
unit-tests/moderrs.exp
unit-tests/moderrs.mk
unit-tests/modmatch.exp
@@ -447,14 +487,20 @@ unit-tests/opt-include-dir.exp
unit-tests/opt-include-dir.mk
unit-tests/opt-jobs-internal.exp
unit-tests/opt-jobs-internal.mk
+unit-tests/opt-jobs-no-action.exp
+unit-tests/opt-jobs-no-action.mk
unit-tests/opt-jobs.exp
unit-tests/opt-jobs.mk
+unit-tests/opt-keep-going-multiple.exp
+unit-tests/opt-keep-going-multiple.mk
unit-tests/opt-keep-going.exp
unit-tests/opt-keep-going.mk
unit-tests/opt-m-include-dir.exp
unit-tests/opt-m-include-dir.mk
unit-tests/opt-no-action-at-all.exp
unit-tests/opt-no-action-at-all.mk
+unit-tests/opt-no-action-runflags.exp
+unit-tests/opt-no-action-runflags.mk
unit-tests/opt-no-action.exp
unit-tests/opt-no-action.mk
unit-tests/opt-query.exp
@@ -491,12 +537,14 @@ unit-tests/posix.exp
unit-tests/posix.mk
unit-tests/posix1.exp
unit-tests/posix1.mk
-unit-tests/qequals.exp
-unit-tests/qequals.mk
unit-tests/recursive.exp
unit-tests/recursive.mk
unit-tests/sh-dots.exp
unit-tests/sh-dots.mk
+unit-tests/sh-errctl.exp
+unit-tests/sh-errctl.mk
+unit-tests/sh-flags.exp
+unit-tests/sh-flags.mk
unit-tests/sh-jobs-error.exp
unit-tests/sh-jobs-error.mk
unit-tests/sh-jobs.exp
@@ -529,14 +577,22 @@ unit-tests/suff-clear-regular.exp
unit-tests/suff-clear-regular.mk
unit-tests/suff-clear-single.exp
unit-tests/suff-clear-single.mk
+unit-tests/suff-incomplete.exp
+unit-tests/suff-incomplete.mk
unit-tests/suff-lookup.exp
unit-tests/suff-lookup.mk
+unit-tests/suff-main-several.exp
+unit-tests/suff-main-several.mk
unit-tests/suff-main.exp
unit-tests/suff-main.mk
+unit-tests/suff-phony.exp
+unit-tests/suff-phony.mk
unit-tests/suff-rebuild.exp
unit-tests/suff-rebuild.mk
unit-tests/suff-self.exp
unit-tests/suff-self.mk
+unit-tests/suff-transform-debug.exp
+unit-tests/suff-transform-debug.mk
unit-tests/suff-transform-endless.exp
unit-tests/suff-transform-endless.mk
unit-tests/suff-transform-expand.exp
@@ -607,6 +663,8 @@ unit-tests/varmod-head.exp
unit-tests/varmod-head.mk
unit-tests/varmod-ifelse.exp
unit-tests/varmod-ifelse.mk
+unit-tests/varmod-indirect.exp
+unit-tests/varmod-indirect.mk
unit-tests/varmod-l-name-to-value.exp
unit-tests/varmod-l-name-to-value.mk
unit-tests/varmod-localtime.exp
@@ -721,6 +779,8 @@ unit-tests/varname-dot-make-ppid.exp
unit-tests/varname-dot-make-ppid.mk
unit-tests/varname-dot-make-save_dollars.exp
unit-tests/varname-dot-make-save_dollars.mk
+unit-tests/varname-dot-makeflags.exp
+unit-tests/varname-dot-makeflags.mk
unit-tests/varname-dot-makeoverrides.exp
unit-tests/varname-dot-makeoverrides.mk
unit-tests/varname-dot-newline.exp
diff --git a/LICENSE b/LICENSE
index 283dd20cccd0..0d460e91d230 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,11 +2,11 @@ The individual files in this distribution are copyright their
original contributors or assignees.
Including:
- Copyright (c) 1993-2020, Simon J Gerraty
+ Copyright (c) 1993-2021, Simon J Gerraty
Copyright (c) 2020, Roland Illig <rillig@NetBSD.org>
Copyright (c) 2009-2016, Juniper Networks, Inc.
Copyright (c) 2009, John Birrell.
- Copyright (c) 1997-2020 The NetBSD Foundation, Inc.
+ Copyright (c) 1997-2021 The NetBSD Foundation, Inc.
Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
Copyright (c) 1989 by Berkeley Softworks
Copyright (c) 1988, 1989, 1990, 1992, 1993
diff --git a/PSD.doc/tutorial.ms b/PSD.doc/tutorial.ms
index 814a09a62146..da17f950f2b5 100644
--- a/PSD.doc/tutorial.ms
+++ b/PSD.doc/tutorial.ms
@@ -1,4 +1,4 @@
-.\" $NetBSD: tutorial.ms,v 1.13 2017/03/01 13:05:11 kre Exp $
+.\" $NetBSD: tutorial.ms,v 1.14 2020/12/18 15:47:34 rillig Exp $
.\" Copyright (c) 1988, 1989, 1993
.\" The Regents of the University of California. All rights reserved.
.\"
@@ -1918,15 +1918,15 @@ Suff_FindDeps (jive.c)
applying .l -> .c to "jive.l"
Suff_FindDeps (jive.l)
Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date
-Examining jive.c...non-existent...out-of-date
+Examining jive.c...nonexistent...out-of-date
--- jive.c ---
lex jive.l
\&.\|.\|. meaningless lex output deleted .\|.\|.
mv lex.yy.c jive.c
-Examining jive.o...non-existent...out-of-date
+Examining jive.o...nonexistent...out-of-date
--- jive.o ---
cc -c jive.c
-Examining jive.out...non-existent...out-of-date
+Examining jive.out...nonexistent...out-of-date
--- jive.out ---
cc -o jive.out jive.o
.DE
@@ -2871,7 +2871,7 @@ current directory. While people have suggested that PMake should read
the directories each time, my experience suggests that the caching seldom
causes problems. In addition, not caching the directories slows things
down enormously because of PMake's attempts to apply transformation
-rules through non-existent files \*- the number of extra file-system
+rules through nonexistent files \*- the number of extra file-system
searches is truly staggering, especially if many files without
suffixes are used and the null suffix isn't changed from
.CW .out .
diff --git a/VERSION b/VERSION
index 6dfd755ffdc8..70e0f68a60b0 100644
--- a/VERSION
+++ b/VERSION
@@ -1,2 +1,2 @@
# keep this compatible with sh and make
-_MAKE_VERSION=20201117
+_MAKE_VERSION=20210110
diff --git a/arch.c b/arch.c
index 81552dee2bb9..037f6bc548cf 100644
--- a/arch.c
+++ b/arch.c
@@ -1,4 +1,4 @@
-/* $NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $ */
+/* $NetBSD: arch.c,v 1.193 2021/01/09 16:06:09 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -68,7 +68,8 @@
* SUCH DAMAGE.
*/
-/* Manipulate libraries, archives and their members.
+/*
+ * Manipulate libraries, archives and their members.
*
* The first time an archive is referenced, all of its members' headers are
* read and cached and the archive closed again. All cached archives are kept
@@ -146,19 +147,19 @@ struct ar_hdr {
#include "dir.h"
/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $");
+MAKE_RCSID("$NetBSD: arch.c,v 1.193 2021/01/09 16:06:09 rillig Exp $");
typedef struct List ArchList;
typedef struct ListNode ArchListNode;
-static ArchList *archives; /* The archives we've already examined */
+static ArchList archives; /* The archives we've already examined */
typedef struct Arch {
- char *name; /* Name of archive */
- HashTable members; /* All the members of the archive described
+ char *name; /* Name of archive */
+ HashTable members; /* All the members of the archive described
* by <name, struct ar_hdr *> key/value pairs */
- char *fnametab; /* Extended name table strings */
- size_t fnamesize; /* Size of the string table */
+ char *fnametab; /* Extended name table strings */
+ size_t fnamesize; /* Size of the string table */
} Arch;
static FILE *ArchFindMember(const char *, const char *,
@@ -200,240 +201,244 @@ static int ArchSVR4Entry(Arch *, char *, size_t, FILE *);
static void
ArchFree(void *ap)
{
- Arch *a = ap;
- HashIter hi;
-
- /* Free memory from hash entries */
- HashIter_Init(&hi, &a->members);
- while (HashIter_Next(&hi) != NULL)
- free(hi.entry->value);
-
- free(a->name);
- free(a->fnametab);
- HashTable_Done(&a->members);
- free(a);
+ Arch *a = ap;
+ HashIter hi;
+
+ /* Free memory from hash entries */
+ HashIter_Init(&hi, &a->members);
+ while (HashIter_Next(&hi) != NULL)
+ free(hi.entry->value);
+
+ free(a->name);
+ free(a->fnametab);
+ HashTable_Done(&a->members);
+ free(a);
}
#endif
/*
* Parse an archive specification such as "archive.a(member1 member2.${EXT})",
- * adding nodes for the expanded members to nodeLst. Nodes are created as
+ * adding nodes for the expanded members to gns. Nodes are created as
* necessary.
*
* Input:
* pp The start of the specification.
- * nodeLst The list on which to place the nodes.
+ * gns The list on which to place the nodes.
* ctxt The context in which to expand variables.
*
* Output:
* return TRUE if it was a valid specification.
* *pp Points to the first non-space after the archive spec.
- * *nodeLst Nodes for the members have been added.
*/
Boolean
-Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
+Arch_ParseArchive(char **pp, GNodeList *gns, GNode *ctxt)
{
- char *cp; /* Pointer into line */
- GNode *gn; /* New node */
- char *libName; /* Library-part of specification */
- char *libName_freeIt = NULL;
- char *memName; /* Member-part of specification */
- char saveChar; /* Ending delimiter of member-name */
- Boolean expandLibName; /* Whether the parsed libName contains
+ char *cp; /* Pointer into line */
+ GNode *gn; /* New node */
+ MFStr libName; /* Library-part of specification */
+ char *memName; /* Member-part of specification */
+ char saveChar; /* Ending delimiter of member-name */
+ Boolean expandLibName; /* Whether the parsed libName contains
* variable expressions that need to be
* expanded */
- libName = *pp;
- expandLibName = FALSE;
-
- for (cp = libName; *cp != '(' && *cp != '\0';) {
- if (*cp == '$') {
- /*
- * Variable spec, so call the Var module to parse the puppy
- * so we can safely advance beyond it...
- */
- const char *nested_p = cp;
- void *result_freeIt;
- const char *result;
- Boolean isError;
-
- /* XXX: is expanded twice: once here and once below */
- (void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR,
- &result, &result_freeIt);
- /* TODO: handle errors */
- isError = result == var_Error;
- free(result_freeIt);
- if (isError)
- return FALSE;
-
- expandLibName = TRUE;
- cp += nested_p - cp;
- } else
- cp++;
- }
-
- *cp++ = '\0';
- if (expandLibName) {
- (void)Var_Subst(libName, ctxt, VARE_WANTRES | VARE_UNDEFERR, &libName);
- /* TODO: handle errors */
- libName_freeIt = libName;
- }
-
-
- for (;;) {
- /*
- * First skip to the start of the member's name, mark that
- * place and skip to the end of it (either white-space or
- * a close paren).
- */
- Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */
-
- pp_skip_whitespace(&cp);
-
- memName = cp;
- while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) {
- if (*cp == '$') {
- /*
- * Variable spec, so call the Var module to parse the puppy
- * so we can safely advance beyond it...
- */
- void *freeIt;
- const char *result;
- Boolean isError;
- const char *nested_p = cp;
-
- (void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR,
- &result, &freeIt);
- /* TODO: handle errors */
- isError = result == var_Error;
- free(freeIt);
-
- if (isError)
- return FALSE;
-
- doSubst = TRUE;
- cp += nested_p - cp;
- } else {
- cp++;
- }
- }
-
- /*
- * If the specification ends without a closing parenthesis,
- * chances are there's something wrong (like a missing backslash),
- * so it's better to return failure than allow such things to happen
- */
- if (*cp == '\0') {
- Parse_Error(PARSE_FATAL, "No closing parenthesis in archive specification");
- return FALSE;
+ libName = MFStr_InitRefer(*pp);
+ expandLibName = FALSE;
+
+ for (cp = libName.str; *cp != '(' && *cp != '\0';) {
+ if (*cp == '$') {
+ /* Expand nested variable expressions. */
+ /* XXX: This code can probably be shortened. */
+ const char *nested_p = cp;
+ FStr result;
+ Boolean isError;
+
+ /* XXX: is expanded twice: once here and once below */
+ (void)Var_Parse(&nested_p, ctxt,
+ VARE_WANTRES | VARE_UNDEFERR, &result);
+ /* TODO: handle errors */
+ isError = result.str == var_Error;
+ FStr_Done(&result);
+ if (isError)
+ return FALSE;
+
+ expandLibName = TRUE;
+ cp += nested_p - cp;
+ } else
+ cp++;
}
- /*
- * If we didn't move anywhere, we must be done
- */
- if (cp == memName) {
- break;
+ *cp++ = '\0';
+ if (expandLibName) {
+ char *expanded;
+ (void)Var_Subst(libName.str, ctxt,
+ VARE_WANTRES | VARE_UNDEFERR, &expanded);
+ /* TODO: handle errors */
+ libName = MFStr_InitOwn(expanded);
}
- saveChar = *cp;
- *cp = '\0';
- /*
- * XXX: This should be taken care of intelligently by
- * SuffExpandChildren, both for the archive and the member portions.
- */
- /*
- * If member contains variables, try and substitute for them.
- * This will slow down archive specs with dynamic sources, of course,
- * since we'll be (non-)substituting them three times, but them's
- * the breaks -- we need to do this since SuffExpandChildren calls
- * us, otherwise we could assume the thing would be taken care of
- * later.
- */
- if (doSubst) {
- char *buf;
- char *sacrifice;
- char *oldMemName = memName;
-
- (void)Var_Subst(memName, ctxt, VARE_WANTRES | VARE_UNDEFERR,
- &memName);
- /* TODO: handle errors */
-
- /*
- * Now form an archive spec and recurse to deal with nested
- * variables and multi-word variable values.... The results
- * are just placed at the end of the nodeLst we're returning.
- */
- buf = sacrifice = str_concat4(libName, "(", memName, ")");
-
- if (strchr(memName, '$') != NULL &&
- strcmp(memName, oldMemName) == 0) {
+ for (;;) {
/*
- * Must contain dynamic sources, so we can't deal with it now.
- * Just create an ARCHV node for the thing and let
- * SuffExpandChildren handle it...
+ * First skip to the start of the member's name, mark that
+ * place and skip to the end of it (either white-space or
+ * a close paren).
*/
- gn = Targ_GetNode(buf);
- gn->type |= OP_ARCHV;
- Lst_Append(nodeLst, gn);
-
- } else if (!Arch_ParseArchive(&sacrifice, nodeLst, ctxt)) {
- /* Error in nested call. */
- free(buf);
- return FALSE;
- }
- free(buf);
+ Boolean doSubst = FALSE;
+
+ pp_skip_whitespace(&cp);
+
+ memName = cp;
+ while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) {
+ if (*cp == '$') {
+ /* Expand nested variable expressions. */
+ /* XXX: This code can probably be shortened. */
+ FStr result;
+ Boolean isError;
+ const char *nested_p = cp;
+
+ (void)Var_Parse(&nested_p, ctxt,
+ VARE_WANTRES | VARE_UNDEFERR,
+ &result);
+ /* TODO: handle errors */
+ isError = result.str == var_Error;
+ FStr_Done(&result);
+
+ if (isError)
+ return FALSE;
+
+ doSubst = TRUE;
+ cp += nested_p - cp;
+ } else {
+ cp++;
+ }
+ }
- } else if (Dir_HasWildcards(memName)) {
- StringList *members = Lst_New();
- Dir_Expand(memName, dirSearchPath, members);
+ /*
+ * If the specification ends without a closing parenthesis,
+ * chances are there's something wrong (like a missing
+ * backslash), so it's better to return failure than allow
+ * such things to happen
+ */
+ if (*cp == '\0') {
+ Parse_Error(PARSE_FATAL,
+ "No closing parenthesis "
+ "in archive specification");
+ return FALSE;
+ }
- while (!Lst_IsEmpty(members)) {
- char *member = Lst_Dequeue(members);
- char *fullname = str_concat4(libName, "(", member, ")");
- free(member);
+ /*
+ * If we didn't move anywhere, we must be done
+ */
+ if (cp == memName)
+ break;
- gn = Targ_GetNode(fullname);
- free(fullname);
+ saveChar = *cp;
+ *cp = '\0';
- gn->type |= OP_ARCHV;
- Lst_Append(nodeLst, gn);
- }
- Lst_Free(members);
+ /*
+ * XXX: This should be taken care of intelligently by
+ * SuffExpandChildren, both for the archive and the member
+ * portions.
+ */
+ /*
+ * If member contains variables, try and substitute for them.
+ * This will slow down archive specs with dynamic sources, of
+ * course, since we'll be (non-)substituting them three
+ * times, but them's the breaks -- we need to do this since
+ * SuffExpandChildren calls us, otherwise we could assume the
+ * thing would be taken care of later.
+ */
+ if (doSubst) {
+ char *fullName;
+ char *p;
+ char *unexpandedMemName = memName;
+
+ (void)Var_Subst(memName, ctxt,
+ VARE_WANTRES | VARE_UNDEFERR,
+ &memName);
+ /* TODO: handle errors */
+
+ /*
+ * Now form an archive spec and recurse to deal with
+ * nested variables and multi-word variable values.
+ */
+ fullName = str_concat4(libName.str, "(", memName, ")");
+ p = fullName;
+
+ if (strchr(memName, '$') != NULL &&
+ strcmp(memName, unexpandedMemName) == 0) {
+ /*
+ * Must contain dynamic sources, so we can't
+ * deal with it now. Just create an ARCHV node
+ * for the thing and let SuffExpandChildren
+ * handle it.
+ */
+ gn = Targ_GetNode(fullName);
+ gn->type |= OP_ARCHV;
+ Lst_Append(gns, gn);
+
+ } else if (!Arch_ParseArchive(&p, gns, ctxt)) {
+ /* Error in nested call. */
+ free(fullName);
+ /* XXX: does unexpandedMemName leak? */
+ return FALSE;
+ }
+ free(fullName);
+ /* XXX: does unexpandedMemName leak? */
+
+ } else if (Dir_HasWildcards(memName)) {
+ StringList members = LST_INIT;
+ Dir_Expand(memName, &dirSearchPath, &members);
+
+ while (!Lst_IsEmpty(&members)) {
+ char *member = Lst_Dequeue(&members);
+ char *fullname = str_concat4(libName.str, "(",
+ member, ")");
+ free(member);
+
+ gn = Targ_GetNode(fullname);
+ free(fullname);
+
+ gn->type |= OP_ARCHV;
+ Lst_Append(gns, gn);
+ }
+ Lst_Done(&members);
+
+ } else {
+ char *fullname = str_concat4(libName.str, "(", memName,
+ ")");
+ gn = Targ_GetNode(fullname);
+ free(fullname);
+
+ /*
+ * We've found the node, but have to make sure the
+ * rest of the world knows it's an archive member,
+ * without having to constantly check for parentheses,
+ * so we type the thing with the OP_ARCHV bit before
+ * we place it on the end of the provided list.
+ */
+ gn->type |= OP_ARCHV;
+ Lst_Append(gns, gn);
+ }
+ if (doSubst)
+ free(memName);
- } else {
- char *fullname = str_concat4(libName, "(", memName, ")");
- gn = Targ_GetNode(fullname);
- free(fullname);
-
- /*
- * We've found the node, but have to make sure the rest of the
- * world knows it's an archive member, without having to
- * constantly check for parentheses, so we type the thing with
- * the OP_ARCHV bit before we place it on the end of the
- * provided list.
- */
- gn->type |= OP_ARCHV;
- Lst_Append(nodeLst, gn);
+ *cp = saveChar;
}
- if (doSubst) {
- free(memName);
- }
-
- *cp = saveChar;
- }
- free(libName_freeIt);
+ MFStr_Done(&libName);
- cp++; /* skip the ')' */
- /* We promised that pp would be set up at the next non-space. */
- pp_skip_whitespace(&cp);
- *pp = cp;
- return TRUE;
+ cp++; /* skip the ')' */
+ /* We promised that pp would be set up at the next non-space. */
+ pp_skip_whitespace(&cp);
+ *pp = cp;
+ return TRUE;
}
-/* Locate a member of an archive, given the path of the archive and the path
+/*
+ * Locate a member of an archive, given the path of the archive and the path
* of the desired member.
*
* Input:
@@ -449,264 +454,261 @@ Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
static struct ar_hdr *
ArchStatMember(const char *archive, const char *member, Boolean addToCache)
{
-#define AR_MAX_NAME_LEN (sizeof arh.AR_NAME - 1)
- FILE *arch; /* Stream to archive */
- size_t size; /* Size of archive member */
- char magic[SARMAG];
- ArchListNode *ln;
- Arch *ar; /* Archive descriptor */
- struct ar_hdr arh; /* archive-member header for reading archive */
- char memName[MAXPATHLEN + 1];
- /* Current member name while hashing. */
-
- /*
- * Because of space constraints and similar things, files are archived
- * using their basename, not the entire path.
- */
- const char *lastSlash = strrchr(member, '/');
- if (lastSlash != NULL)
- member = lastSlash + 1;
-
- for (ln = archives->first; ln != NULL; ln = ln->next) {
- const Arch *a = ln->datum;
- if (strcmp(a->name, archive) == 0)
- break;
- }
-
- if (ln != NULL) {
- struct ar_hdr *hdr;
-
- ar = ln->datum;
- hdr = HashTable_FindValue(&ar->members, member);
- if (hdr != NULL)
- return hdr;
-
- {
- /* Try truncated name */
- char copy[AR_MAX_NAME_LEN + 1];
- size_t len = strlen(member);
-
- if (len > AR_MAX_NAME_LEN) {
- len = AR_MAX_NAME_LEN;
- snprintf(copy, sizeof copy, "%s", member);
- hdr = HashTable_FindValue(&ar->members, copy);
- }
- return hdr;
- }
- }
+#define AR_MAX_NAME_LEN (sizeof arh.ar_name - 1)
+ FILE *arch;
+ size_t size; /* Size of archive member */
+ char magic[SARMAG];
+ ArchListNode *ln;
+ Arch *ar; /* Archive descriptor */
+ struct ar_hdr arh; /* archive-member header for reading archive */
+ char memName[MAXPATHLEN + 1];
+ /* Current member name while hashing. */
- if (!addToCache) {
/*
- * Caller doesn't want the thing cached, just use ArchFindMember
- * to read the header for the member out and close down the stream
- * again. Since the archive is not to be cached, we assume there's
- * no need to allocate extra room for the header we're returning,
- * so just declare it static.
+ * Because of space constraints and similar things, files are archived
+ * using their basename, not the entire path.
*/
- static struct ar_hdr sarh;
+ member = str_basename(member);
- arch = ArchFindMember(archive, member, &sarh, "r");
- if (arch == NULL)
- return NULL;
+ for (ln = archives.first; ln != NULL; ln = ln->next) {
+ const Arch *a = ln->datum;
+ if (strcmp(a->name, archive) == 0)
+ break;
+ }
- fclose(arch);
- return &sarh;
- }
-
- /*
- * We don't have this archive on the list yet, so we want to find out
- * everything that's in it and cache it so we can get at it quickly.
- */
- arch = fopen(archive, "r");
- if (arch == NULL)
- return NULL;
+ if (ln != NULL) {
+ struct ar_hdr *hdr;
- /*
- * We use the ARMAG string to make sure this is an archive we
- * can handle...
- */
- if (fread(magic, SARMAG, 1, arch) != 1 ||
- strncmp(magic, ARMAG, SARMAG) != 0) {
- (void)fclose(arch);
- return NULL;
- }
+ ar = ln->datum;
+ hdr = HashTable_FindValue(&ar->members, member);
+ if (hdr != NULL)
+ return hdr;
- ar = bmake_malloc(sizeof *ar);
- ar->name = bmake_strdup(archive);
- ar->fnametab = NULL;
- ar->fnamesize = 0;
- HashTable_Init(&ar->members);
- memName[AR_MAX_NAME_LEN] = '\0';
+ {
+ /* Try truncated name */
+ char copy[AR_MAX_NAME_LEN + 1];
+ size_t len = strlen(member);
- while (fread(&arh, sizeof arh, 1, arch) == 1) {
- char *nameend;
+ if (len > AR_MAX_NAME_LEN) {
+ snprintf(copy, sizeof copy, "%s", member);
+ hdr = HashTable_FindValue(&ar->members, copy);
+ }
+ return hdr;
+ }
+ }
+
+ if (!addToCache) {
+ /*
+ * Caller doesn't want the thing cached, just use
+ * ArchFindMember to read the header for the member out and
+ * close down the stream again. Since the archive is not to be
+ * cached, we assume there's no need to allocate extra room
+ * for the header we're returning, so just declare it static.
+ */
+ static struct ar_hdr sarh;
- /* If the header is bogus, there's no way we can recover. */
- if (strncmp(arh.AR_FMAG, ARFMAG, sizeof arh.AR_FMAG) != 0)
- goto badarch;
+ arch = ArchFindMember(archive, member, &sarh, "r");
+ if (arch == NULL)
+ return NULL;
+
+ fclose(arch);
+ return &sarh;
+ }
/*
- * We need to advance the stream's pointer to the start of the
- * next header. Files are padded with newlines to an even-byte
- * boundary, so we need to extract the size of the file from the
- * 'size' field of the header and round it up during the seek.
+ * We don't have this archive on the list yet, so we want to find out
+ * everything that's in it and cache it so we can get at it quickly.
*/
- arh.AR_SIZE[sizeof arh.AR_SIZE - 1] = '\0';
- size = (size_t)strtol(arh.AR_SIZE, NULL, 10);
-
- memcpy(memName, arh.AR_NAME, sizeof arh.AR_NAME);
- nameend = memName + AR_MAX_NAME_LEN;
- while (nameend > memName && *nameend == ' ')
- nameend--;
- nameend[1] = '\0';
+ arch = fopen(archive, "r");
+ if (arch == NULL)
+ return NULL;
-#ifdef SVR4ARCHIVES
/*
- * svr4 names are slash terminated. Also svr4 extended AR format.
+ * We use the ARMAG string to make sure this is an archive we
+ * can handle...
*/
- if (memName[0] == '/') {
- /*
- * svr4 magic mode; handle it
- */
- switch (ArchSVR4Entry(ar, memName, size, arch)) {
- case -1: /* Invalid data */
- goto badarch;
- case 0: /* List of files entry */
- continue;
- default: /* Got the entry */
- break;
- }
- } else {
- if (nameend[0] == '/')
- nameend[0] = '\0';
+ if (fread(magic, SARMAG, 1, arch) != 1 ||
+ strncmp(magic, ARMAG, SARMAG) != 0) {
+ (void)fclose(arch);
+ return NULL;
}
+
+ ar = bmake_malloc(sizeof *ar);
+ ar->name = bmake_strdup(archive);
+ ar->fnametab = NULL;
+ ar->fnamesize = 0;
+ HashTable_Init(&ar->members);
+ memName[AR_MAX_NAME_LEN] = '\0';
+
+ while (fread(&arh, sizeof arh, 1, arch) == 1) {
+ char *nameend;
+
+ /* If the header is bogus, there's no way we can recover. */
+ if (strncmp(arh.AR_FMAG, ARFMAG, sizeof arh.AR_FMAG) != 0)
+ goto badarch;
+
+ /*
+ * We need to advance the stream's pointer to the start of the
+ * next header. Files are padded with newlines to an even-byte
+ * boundary, so we need to extract the size of the file from
+ * the 'size' field of the header and round it up during the
+ * seek.
+ */
+ arh.AR_SIZE[sizeof arh.AR_SIZE - 1] = '\0';
+ size = (size_t)strtol(arh.AR_SIZE, NULL, 10);
+
+ memcpy(memName, arh.AR_NAME, sizeof arh.AR_NAME);
+ nameend = memName + AR_MAX_NAME_LEN;
+ while (nameend > memName && *nameend == ' ')
+ nameend--;
+ nameend[1] = '\0';
+
+#ifdef SVR4ARCHIVES
+ /*
+ * svr4 names are slash-terminated.
+ * Also svr4 extended the AR format.
+ */
+ if (memName[0] == '/') {
+ /* svr4 magic mode; handle it */
+ switch (ArchSVR4Entry(ar, memName, size, arch)) {
+ case -1: /* Invalid data */
+ goto badarch;
+ case 0: /* List of files entry */
+ continue;
+ default: /* Got the entry */
+ break;
+ }
+ } else {
+ if (nameend[0] == '/')
+ nameend[0] = '\0';
+ }
#endif
#ifdef AR_EFMT1
- /*
- * BSD 4.4 extended AR format: #1/<namelen>, with name as the
- * first <namelen> bytes of the file
- */
- if (strncmp(memName, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
- ch_isdigit(memName[sizeof AR_EFMT1 - 1])) {
-
- int elen = atoi(memName + sizeof AR_EFMT1 - 1);
-
- if ((unsigned int)elen > MAXPATHLEN)
- goto badarch;
- if (fread(memName, (size_t)elen, 1, arch) != 1)
- goto badarch;
- memName[elen] = '\0';
- if (fseek(arch, -elen, SEEK_CUR) != 0)
- goto badarch;
- if (DEBUG(ARCH) || DEBUG(MAKE))
- debug_printf("ArchStatMember: Extended format entry for %s\n",
- memName);
- }
+ /*
+ * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+ * first <namelen> bytes of the file
+ */
+ if (strncmp(memName, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
+ ch_isdigit(memName[sizeof AR_EFMT1 - 1])) {
+
+ int elen = atoi(memName + sizeof AR_EFMT1 - 1);
+
+ if ((unsigned int)elen > MAXPATHLEN)
+ goto badarch;
+ if (fread(memName, (size_t)elen, 1, arch) != 1)
+ goto badarch;
+ memName[elen] = '\0';
+ if (fseek(arch, -elen, SEEK_CUR) != 0)
+ goto badarch;
+ if (DEBUG(ARCH) || DEBUG(MAKE))
+ debug_printf(
+ "ArchStatMember: "
+ "Extended format entry for %s\n",
+ memName);
+ }
#endif
- {
- struct ar_hdr *cached_hdr = bmake_malloc(sizeof *cached_hdr);
- memcpy(cached_hdr, &arh, sizeof arh);
- HashTable_Set(&ar->members, memName, cached_hdr);
- }
+ {
+ struct ar_hdr *cached_hdr = bmake_malloc(
+ sizeof *cached_hdr);
+ memcpy(cached_hdr, &arh, sizeof arh);
+ HashTable_Set(&ar->members, memName, cached_hdr);
+ }
- if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0)
- goto badarch;
- }
+ if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0)
+ goto badarch;
+ }
- fclose(arch);
+ fclose(arch);
- Lst_Append(archives, ar);
+ Lst_Append(&archives, ar);
- /*
- * Now that the archive has been read and cached, we can look into
- * the addToCache table to find the desired member's header.
- */
- return HashTable_FindValue(&ar->members, member);
+ /*
+ * Now that the archive has been read and cached, we can look into
+ * the addToCache table to find the desired member's header.
+ */
+ return HashTable_FindValue(&ar->members, member);
badarch:
- fclose(arch);
- HashTable_Done(&ar->members);
- free(ar->fnametab);
- free(ar);
- return NULL;
+ fclose(arch);
+ HashTable_Done(&ar->members);
+ free(ar->fnametab);
+ free(ar);
+ return NULL;
}
#ifdef SVR4ARCHIVES
-/*-
- *-----------------------------------------------------------------------
- * ArchSVR4Entry --
- * Parse an SVR4 style entry that begins with a slash.
- * If it is "//", then load the table of filenames
- * If it is "/<offset>", then try to substitute the long file name
- * from offset of a table previously read.
- * If a table is read, the file pointer is moved to the next archive
- * member.
+/*
+ * Parse an SVR4 style entry that begins with a slash.
+ * If it is "//", then load the table of filenames.
+ * If it is "/<offset>", then try to substitute the long file name
+ * from offset of a table previously read.
+ * If a table is read, the file pointer is moved to the next archive member.
*
* Results:
* -1: Bad data in archive
* 0: A table was loaded from the file
* 1: Name was successfully substituted from table
* 2: Name was not successfully substituted from table
- *-----------------------------------------------------------------------
*/
static int
ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
{
#define ARLONGNAMES1 "//"
#define ARLONGNAMES2 "/ARFILENAMES"
- size_t entry;
- char *ptr, *eptr;
+ size_t entry;
+ char *ptr, *eptr;
- if (strncmp(inout_name, ARLONGNAMES1, sizeof ARLONGNAMES1 - 1) == 0 ||
- strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) {
+ if (strncmp(inout_name, ARLONGNAMES1, sizeof ARLONGNAMES1 - 1) == 0 ||
+ strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) {
- if (ar->fnametab != NULL) {
- DEBUG0(ARCH, "Attempted to redefine an SVR4 name table\n");
- return -1;
+ if (ar->fnametab != NULL) {
+ DEBUG0(ARCH,
+ "Attempted to redefine an SVR4 name table\n");
+ return -1;
+ }
+
+ /*
+ * This is a table of archive names, so we build one for
+ * ourselves
+ */
+ ar->fnametab = bmake_malloc(size);
+ ar->fnamesize = size;
+
+ if (fread(ar->fnametab, size, 1, arch) != 1) {
+ DEBUG0(ARCH, "Reading an SVR4 name table failed\n");
+ return -1;
+ }
+ eptr = ar->fnametab + size;
+ for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++)
+ if (*ptr == '/') {
+ entry++;
+ *ptr = '\0';
+ }
+ DEBUG1(ARCH, "Found svr4 archive name table with %lu entries\n",
+ (unsigned long)entry);
+ return 0;
}
- /*
- * This is a table of archive names, so we build one for
- * ourselves
- */
- ar->fnametab = bmake_malloc(size);
- ar->fnamesize = size;
+ if (inout_name[1] == ' ' || inout_name[1] == '\0')
+ return 2;
- if (fread(ar->fnametab, size, 1, arch) != 1) {
- DEBUG0(ARCH, "Reading an SVR4 name table failed\n");
- return -1;
+ entry = (size_t)strtol(&inout_name[1], &eptr, 0);
+ if ((*eptr != ' ' && *eptr != '\0') || eptr == &inout_name[1]) {
+ DEBUG1(ARCH, "Could not parse SVR4 name %s\n", inout_name);
+ return 2;
+ }
+ if (entry >= ar->fnamesize) {
+ DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n",
+ inout_name, (unsigned long)ar->fnamesize);
+ return 2;
}
- eptr = ar->fnametab + size;
- for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++)
- if (*ptr == '/') {
- entry++;
- *ptr = '\0';
- }
- DEBUG1(ARCH, "Found svr4 archive name table with %lu entries\n",
- (unsigned long)entry);
- return 0;
- }
-
- if (inout_name[1] == ' ' || inout_name[1] == '\0')
- return 2;
-
- entry = (size_t)strtol(&inout_name[1], &eptr, 0);
- if ((*eptr != ' ' && *eptr != '\0') || eptr == &inout_name[1]) {
- DEBUG1(ARCH, "Could not parse SVR4 name %s\n", inout_name);
- return 2;
- }
- if (entry >= ar->fnamesize) {
- DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n",
- inout_name, (unsigned long)ar->fnamesize);
- return 2;
- }
-
- DEBUG2(ARCH, "Replaced %s with %s\n", inout_name, &ar->fnametab[entry]);
-
- snprintf(inout_name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]);
- return 1;
+
+ DEBUG2(ARCH, "Replaced %s with %s\n", inout_name, &ar->fnametab[entry]);
+
+ snprintf(inout_name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]);
+ return 1;
}
#endif
@@ -715,29 +717,30 @@ static Boolean
ArchiveMember_HasName(const struct ar_hdr *hdr,
const char *name, size_t namelen)
{
- const size_t ar_name_len = sizeof hdr->AR_NAME;
- const char *ar_name = hdr->AR_NAME;
+ const size_t ar_name_len = sizeof hdr->AR_NAME;
+ const char *ar_name = hdr->AR_NAME;
- if (strncmp(ar_name, name, namelen) != 0)
- return FALSE;
+ if (strncmp(ar_name, name, namelen) != 0)
+ return FALSE;
- if (namelen >= ar_name_len)
- return namelen == ar_name_len;
+ if (namelen >= ar_name_len)
+ return namelen == ar_name_len;
- /* hdr->AR_NAME is space-padded to the right. */
- if (ar_name[namelen] == ' ')
- return TRUE;
+ /* hdr->AR_NAME is space-padded to the right. */
+ if (ar_name[namelen] == ' ')
+ return TRUE;
- /* In archives created by GNU binutils 2.27, the member names end with
- * a slash. */
- if (ar_name[namelen] == '/' &&
- (namelen == ar_name_len || ar_name[namelen + 1] == ' '))
- return TRUE;
+ /* In archives created by GNU binutils 2.27, the member names end with
+ * a slash. */
+ if (ar_name[namelen] == '/' &&
+ (namelen == ar_name_len || ar_name[namelen + 1] == ' '))
+ return TRUE;
- return FALSE;
+ return FALSE;
}
-/* Locate a member of an archive, given the path of the archive and the path
+/*
+ * Locate a member of an archive, given the path of the archive and the path
* of the desired member.
*
* Input:
@@ -759,130 +762,130 @@ static FILE *
ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
const char *mode)
{
- FILE *arch; /* Stream to archive */
- int size; /* Size of archive member */
- char magic[SARMAG];
- size_t len, tlen;
- const char *lastSlash;
-
- arch = fopen(archive, mode);
- if (arch == NULL)
- return NULL;
+ FILE *arch; /* Stream to archive */
+ int size; /* Size of archive member */
+ char magic[SARMAG];
+ size_t len;
- /*
- * We use the ARMAG string to make sure this is an archive we
- * can handle...
- */
- if (fread(magic, SARMAG, 1, arch) != 1 ||
- strncmp(magic, ARMAG, SARMAG) != 0) {
- fclose(arch);
- return NULL;
- }
-
- /*
- * Because of space constraints and similar things, files are archived
- * using their basename, not the entire path.
- */
- lastSlash = strrchr(member, '/');
- if (lastSlash != NULL)
- member = lastSlash + 1;
-
- len = tlen = strlen(member);
- if (len > sizeof out_arh->AR_NAME) {
- tlen = sizeof out_arh->AR_NAME;
- }
-
- while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) {
-
- if (strncmp(out_arh->AR_FMAG, ARFMAG, sizeof out_arh->AR_FMAG) != 0) {
- /*
- * The header is bogus, so the archive is bad
- * and there's no way we can recover...
- */
- fclose(arch);
- return NULL;
- }
+ arch = fopen(archive, mode);
+ if (arch == NULL)
+ return NULL;
- DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n",
- archive,
- (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME,
- (int)sizeof out_arh->ar_date, out_arh->ar_date);
-
- if (ArchiveMember_HasName(out_arh, member, len)) {
- /*
- * To make life easier for callers that want to update the
- * archive, we reposition the file at the start
- * of the header we just read before we return the stream.
- * In a more general situation, it might be better to leave
- * the file at the actual member, rather than its header, but
- * not here.
- */
- if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) != 0) {
+ /*
+ * We use the ARMAG string to make sure this is an archive we
+ * can handle...
+ */
+ if (fread(magic, SARMAG, 1, arch) != 1 ||
+ strncmp(magic, ARMAG, SARMAG) != 0) {
fclose(arch);
return NULL;
- }
- return arch;
}
-#ifdef AR_EFMT1
/*
- * BSD 4.4 extended AR format: #1/<namelen>, with name as the
- * first <namelen> bytes of the file
+ * Because of space constraints and similar things, files are archived
+ * using their basename, not the entire path.
*/
- if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
- ch_isdigit(out_arh->AR_NAME[sizeof AR_EFMT1 - 1]))
- {
- int elen = atoi(&out_arh->AR_NAME[sizeof AR_EFMT1 - 1]);
- char ename[MAXPATHLEN + 1];
+ member = str_basename(member);
- if ((unsigned int)elen > MAXPATHLEN) {
- fclose(arch);
- return NULL;
- }
- if (fread(ename, (size_t)elen, 1, arch) != 1) {
- fclose(arch);
- return NULL;
- }
- ename[elen] = '\0';
- if (DEBUG(ARCH) || DEBUG(MAKE))
- debug_printf("ArchFindMember: Extended format entry for %s\n",
- ename);
- if (strncmp(ename, member, len) == 0) {
- /* Found as extended name */
- if (fseek(arch, -(long)sizeof(struct ar_hdr) - elen,
- SEEK_CUR) != 0) {
- fclose(arch);
- return NULL;
+ len = strlen(member);
+
+ while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) {
+
+ if (strncmp(out_arh->AR_FMAG, ARFMAG,
+ sizeof out_arh->AR_FMAG) != 0) {
+ /*
+ * The header is bogus, so the archive is bad
+ * and there's no way we can recover...
+ */
+ fclose(arch);
+ return NULL;
+ }
+
+ DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n",
+ archive,
+ (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME,
+ (int)sizeof out_arh->ar_date, out_arh->ar_date);
+
+ if (ArchiveMember_HasName(out_arh, member, len)) {
+ /*
+ * To make life easier for callers that want to update
+ * the archive, we reposition the file at the start of
+ * the header we just read before we return the
+ * stream. In a more general situation, it might be
+ * better to leave the file at the actual member,
+ * rather than its header, but not here.
+ */
+ if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) !=
+ 0) {
+ fclose(arch);
+ return NULL;
+ }
+ return arch;
+ }
+
+#ifdef AR_EFMT1
+ /*
+ * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+ * first <namelen> bytes of the file
+ */
+ if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) ==
+ 0 &&
+ (ch_isdigit(out_arh->AR_NAME[sizeof AR_EFMT1 - 1]))) {
+ int elen = atoi(&out_arh->AR_NAME[sizeof AR_EFMT1 - 1]);
+ char ename[MAXPATHLEN + 1];
+
+ if ((unsigned int)elen > MAXPATHLEN) {
+ fclose(arch);
+ return NULL;
+ }
+ if (fread(ename, (size_t)elen, 1, arch) != 1) {
+ fclose(arch);
+ return NULL;
+ }
+ ename[elen] = '\0';
+ if (DEBUG(ARCH) || DEBUG(MAKE))
+ debug_printf(
+ "ArchFindMember: "
+ "Extended format entry for %s\n",
+ ename);
+ if (strncmp(ename, member, len) == 0) {
+ /* Found as extended name */
+ if (fseek(arch,
+ -(long)sizeof(struct ar_hdr) - elen,
+ SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
+ return arch;
+ }
+ if (fseek(arch, -elen, SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
}
- return arch;
- }
- if (fseek(arch, -elen, SEEK_CUR) != 0) {
- fclose(arch);
- return NULL;
- }
- }
#endif
- /*
- * This isn't the member we're after, so we need to advance the
- * stream's pointer to the start of the next header. Files are
- * padded with newlines to an even-byte boundary, so we need to
- * extract the size of the file from the 'size' field of the
- * header and round it up during the seek.
- */
- out_arh->AR_SIZE[sizeof out_arh->AR_SIZE - 1] = '\0';
- size = (int)strtol(out_arh->AR_SIZE, NULL, 10);
- if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) {
- fclose(arch);
- return NULL;
+ /*
+ * This isn't the member we're after, so we need to advance the
+ * stream's pointer to the start of the next header. Files are
+ * padded with newlines to an even-byte boundary, so we need to
+ * extract the size of the file from the 'size' field of the
+ * header and round it up during the seek.
+ */
+ out_arh->AR_SIZE[sizeof out_arh->AR_SIZE - 1] = '\0';
+ size = (int)strtol(out_arh->AR_SIZE, NULL, 10);
+ if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
}
- }
- fclose(arch);
- return NULL;
+ fclose(arch);
+ return NULL;
}
-/* Touch a member of an archive, on disk.
+/*
+ * Touch a member of an archive, on disk.
* The GNode's modification time is left as-is.
*
* The st_mtime of the entire archive is also changed.
@@ -897,97 +900,107 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
void
Arch_Touch(GNode *gn)
{
- FILE *f;
- struct ar_hdr arh;
+ FILE *f;
+ struct ar_hdr arh;
- f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+");
- if (f == NULL)
- return;
+ f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh,
+ "r+");
+ if (f == NULL)
+ return;
- snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
- (void)fwrite(&arh, sizeof arh, 1, f);
- fclose(f); /* TODO: handle errors */
+ snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
+ (void)fwrite(&arh, sizeof arh, 1, f);
+ fclose(f); /* TODO: handle errors */
}
-/* Given a node which represents a library, touch the thing, making sure that
+/*
+ * Given a node which represents a library, touch the thing, making sure that
* the table of contents is also touched.
*
* Both the modification time of the library and of the RANLIBMAG member are
- * set to 'now'. */
+ * set to 'now'.
+ */
+/*ARGSUSED*/
void
Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED)
{
#ifdef RANLIBMAG
- FILE *f;
- struct ar_hdr arh; /* Header describing table of contents */
- struct utimbuf times;
+ FILE *f;
+ struct ar_hdr arh; /* Header describing table of contents */
+ struct utimbuf times;
- f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+");
- if (f == NULL)
- return;
+ f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+");
+ if (f == NULL)
+ return;
- snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
- (void)fwrite(&arh, sizeof arh, 1, f);
- fclose(f); /* TODO: handle errors */
+ snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
+ (void)fwrite(&arh, sizeof arh, 1, f);
+ fclose(f); /* TODO: handle errors */
- times.actime = times.modtime = now;
- utime(gn->path, &times); /* TODO: handle errors */
+ times.actime = times.modtime = now;
+ utime(gn->path, &times); /* TODO: handle errors */
#endif
}
-/* Update the mtime of the GNode with the mtime from the archive member on
- * disk (or in the cache). */
+/*
+ * Update the mtime of the GNode with the mtime from the archive member on
+ * disk (or in the cache).
+ */
void
Arch_UpdateMTime(GNode *gn)
{
- struct ar_hdr *arh;
+ struct ar_hdr *arh;
- arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE);
- if (arh != NULL)
- gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10);
- else
- gn->mtime = 0;
+ arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE);
+ if (arh != NULL)
+ gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10);
+ else
+ gn->mtime = 0;
}
-/* Given a non-existent archive member's node, update gn->mtime from its
- * archived form, if it exists. */
+/*
+ * Given a nonexistent archive member's node, update gn->mtime from its
+ * archived form, if it exists.
+ */
void
Arch_UpdateMemberMTime(GNode *gn)
{
- GNodeListNode *ln;
-
- for (ln = gn->parents->first; ln != NULL; ln = ln->next) {
- GNode *pgn = ln->datum;
-
- if (pgn->type & OP_ARCHV) {
- /*
- * If the parent is an archive specification and is being made
- * and its member's name matches the name of the node we were
- * given, record the modification time of the parent in the
- * child. We keep searching its parents in case some other
- * parent requires this child to exist...
- */
- const char *nameStart = strchr(pgn->name, '(') + 1;
- const char *nameEnd = strchr(nameStart, ')');
- size_t nameLen = (size_t)(nameEnd - nameStart);
-
- if ((pgn->flags & REMAKE) &&
- strncmp(nameStart, gn->name, nameLen) == 0) {
- Arch_UpdateMTime(pgn);
- gn->mtime = pgn->mtime;
- }
- } else if (pgn->flags & REMAKE) {
- /*
- * Something which isn't a library depends on the existence of
- * this target, so it needs to exist.
- */
- gn->mtime = 0;
- break;
+ GNodeListNode *ln;
+
+ for (ln = gn->parents.first; ln != NULL; ln = ln->next) {
+ GNode *pgn = ln->datum;
+
+ if (pgn->type & OP_ARCHV) {
+ /*
+ * If the parent is an archive specification and is
+ * being made and its member's name matches the name
+ * of the node we were given, record the modification
+ * time of the parent in the child. We keep searching
+ * its parents in case some other parent requires this
+ * child to exist.
+ */
+ const char *nameStart = strchr(pgn->name, '(') + 1;
+ const char *nameEnd = strchr(nameStart, ')');
+ size_t nameLen = (size_t)(nameEnd - nameStart);
+
+ if ((pgn->flags & REMAKE) &&
+ strncmp(nameStart, gn->name, nameLen) == 0) {
+ Arch_UpdateMTime(pgn);
+ gn->mtime = pgn->mtime;
+ }
+ } else if (pgn->flags & REMAKE) {
+ /*
+ * Something which isn't a library depends on the
+ * existence of this target, so it needs to exist.
+ */
+ gn->mtime = 0;
+ break;
+ }
}
- }
}
-/* Search for a library along the given search path.
+/*
+ * Search for a library along the given search path.
*
* The node's 'path' field is set to the found path (including the
* actual file name, not -l...). If the system can handle the -L
@@ -1004,18 +1017,19 @@ Arch_UpdateMemberMTime(GNode *gn)
void
Arch_FindLib(GNode *gn, SearchPath *path)
{
- char *libName = str_concat3("lib", gn->name + 2, ".a");
- gn->path = Dir_FindFile(libName, path);
- free(libName);
+ char *libName = str_concat3("lib", gn->name + 2, ".a");
+ gn->path = Dir_FindFile(libName, path);
+ free(libName);
#ifdef LIBRARIES
- Var_Set(TARGET, gn->name, gn);
+ Var_Set(TARGET, gn->name, gn);
#else
- Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn);
+ Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn);
#endif
}
-/* Decide if a node with the OP_LIB attribute is out-of-date. Called from
+/*
+ * Decide if a node with the OP_LIB attribute is out-of-date. Called from
* GNode_IsOODate to make its life easier.
* The library is cached if it hasn't been already.
*
@@ -1047,50 +1061,53 @@ Arch_FindLib(GNode *gn, SearchPath *path)
Boolean
Arch_LibOODate(GNode *gn)
{
- Boolean oodate;
-
- if (gn->type & OP_PHONY) {
- oodate = TRUE;
- } else if (!GNode_IsTarget(gn) && Lst_IsEmpty(gn->children)) {
- oodate = FALSE;
- } else if ((!Lst_IsEmpty(gn->children) && gn->youngestChild == NULL) ||
- (gn->mtime > now) ||
- (gn->youngestChild != NULL &&
- gn->mtime < gn->youngestChild->mtime)) {
- oodate = TRUE;
- } else {
-#ifdef RANLIBMAG
- struct ar_hdr *arh; /* Header for __.SYMDEF */
- int modTimeTOC; /* The table-of-contents's mod time */
-
- arh = ArchStatMember(gn->path, RANLIBMAG, FALSE);
-
- if (arh != NULL) {
- modTimeTOC = (int)strtol(arh->ar_date, NULL, 10);
-
- if (DEBUG(ARCH) || DEBUG(MAKE))
- debug_printf("%s modified %s...",
- RANLIBMAG, Targ_FmtTime(modTimeTOC));
- oodate = gn->youngestChild == NULL ||
- gn->youngestChild->mtime > modTimeTOC;
+ Boolean oodate;
+
+ if (gn->type & OP_PHONY) {
+ oodate = TRUE;
+ } else if (!GNode_IsTarget(gn) && Lst_IsEmpty(&gn->children)) {
+ oodate = FALSE;
+ } else if ((!Lst_IsEmpty(&gn->children) && gn->youngestChild == NULL) ||
+ (gn->mtime > now) ||
+ (gn->youngestChild != NULL &&
+ gn->mtime < gn->youngestChild->mtime)) {
+ oodate = TRUE;
} else {
- /* A library without a table of contents is out-of-date. */
- if (DEBUG(ARCH) || DEBUG(MAKE))
- debug_printf("no toc...");
- oodate = TRUE;
- }
+#ifdef RANLIBMAG
+ struct ar_hdr *arh; /* Header for __.SYMDEF */
+ int modTimeTOC; /* The table-of-contents' mod time */
+
+ arh = ArchStatMember(gn->path, RANLIBMAG, FALSE);
+
+ if (arh != NULL) {
+ modTimeTOC = (int)strtol(arh->ar_date, NULL, 10);
+
+ if (DEBUG(ARCH) || DEBUG(MAKE))
+ debug_printf("%s modified %s...",
+ RANLIBMAG,
+ Targ_FmtTime(modTimeTOC));
+ oodate = gn->youngestChild == NULL ||
+ gn->youngestChild->mtime > modTimeTOC;
+ } else {
+ /*
+ * A library without a table of contents is out-of-date.
+ */
+ if (DEBUG(ARCH) || DEBUG(MAKE))
+ debug_printf("no toc...");
+ oodate = TRUE;
+ }
#else
- oodate = FALSE;
+ oodate = FALSE;
#endif
- }
- return oodate;
+ }
+ return oodate;
}
/* Initialize the archives module. */
void
Arch_Init(void)
{
- archives = Lst_New();
+ Lst_Init(&archives);
}
/* Clean up the archives module. */
@@ -1098,26 +1115,26 @@ void
Arch_End(void)
{
#ifdef CLEANUP
- Lst_Destroy(archives, ArchFree);
+ Lst_DoneCall(&archives, ArchFree);
#endif
}
Boolean
Arch_IsLib(GNode *gn)
{
- static const char armag[] = "!<arch>\n";
- char buf[sizeof armag - 1];
- int fd;
+ static const char armag[] = "!<arch>\n";
+ char buf[sizeof armag - 1];
+ int fd;
- if ((fd = open(gn->path, O_RDONLY)) == -1)
- return FALSE;
+ if ((fd = open(gn->path, O_RDONLY)) == -1)
+ return FALSE;
- if (read(fd, buf, sizeof buf) != sizeof buf) {
- (void)close(fd);
- return FALSE;
- }
+ if (read(fd, buf, sizeof buf) != sizeof buf) {
+ (void)close(fd);
+ return FALSE;
+ }
- (void)close(fd);
+ (void)close(fd);
- return memcmp(buf, armag, sizeof buf) == 0;
+ return memcmp(buf, armag, sizeof buf) == 0;
}
diff --git a/bmake.1 b/bmake.1
index cbd1ae9f8677..f35c14cc1eee 100644
--- a/bmake.1
+++ b/bmake.1
@@ -1,4 +1,4 @@
-.\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig Exp $
+.\" $NetBSD: make.1,v 1.295 2020/12/23 13:49:12 rillig Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
-.Dd November 14, 2020
+.Dd December 22, 2020
.Dt BMAKE 1
.Os
.Sh NAME
@@ -1019,6 +1019,12 @@ If set to false,
becomes
.Ql $
per normal evaluation rules.
+.It Va .MAKE.UID
+The user-id running
+.Nm .
+.It Va .MAKE.GID
+The group-id running
+.Nm .
.It Va MAKE_PRINT_VAR_ON_ERROR
When
.Nm
@@ -1097,7 +1103,7 @@ to that directory before executing any targets.
.Pp
Except in the case of an explicit
.Ql Ic .OBJDIR
-target,
+target,
.Nm
will check that the specified directory is writable and ignore it if not.
This check can be skipped by setting the environment variable
@@ -1743,9 +1749,9 @@ The same as
except that variables in the value are not expanded.
.It Ic .info Ar message
The message is printed along with the name of the makefile and line number.
-.It Ic .undef Ar variable
-Un-define the specified global variable.
-Only global variables may be un-defined.
+.It Ic .undef Ar variable ...
+Un-define the specified global variables.
+Only global variables can be un-defined.
.It Ic .unexport Ar variable ...
The opposite of
.Ql .export .
diff --git a/bmake.cat1 b/bmake.cat1
index 46e07ba9cc4f..9ed1dcdd9018 100644
--- a/bmake.cat1
+++ b/bmake.cat1
@@ -45,10 +45,10 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
`-' they are added to the MAKEFLAGS environment variable and will
be processed by any child make processes. By default, debugging
information is printed to standard error, but this can be changed
- using the F debugging flag. The debugging output is always
- unbuffered; in addition, if debugging is enabled but debugging
- output is not directed to standard output, then the standard out-
- put is line buffered. Flags is one or more of the following:
+ using the F debugging flag. The debugging output is always un-
+ buffered; in addition, if debugging is enabled but debugging out-
+ put is not directed to standard output, then the standard output
+ is line buffered. Flags is one or more of the following:
A Print all possible debugging information; equivalent to
specifying all of the debugging flags.
@@ -178,8 +178,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
can be used multiple times to form a search path. This path will
override the default system include path: /usr/share/mk. Fur-
thermore the system include path will be appended to the search
- path used for "file"-style include statements (see the -I
- option).
+ path used for "file"-style include statements (see the -I op-
+ tion).
If a file or directory name in the -m argument (or the
MAKESYSPATH environment variable) starts with the string ".../"
@@ -232,9 +232,9 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
.MAKE.EXPAND_VARIABLES is set to true and the -dV option has not
been used to override it. Note that loop-local and target-local
variables, as well as values taken temporarily by global vari-
- ables during makefile processing, are not accessible via this
- option. The -dv debug mode can be used to see these at the cost
- of generating substantial extraneous output.
+ ables during makefile processing, are not accessible via this op-
+ tion. The -dv debug mode can be used to see these at the cost of
+ generating substantial extraneous output.
-v variable
Like -V but the variable is always expanded to its complete
@@ -247,8 +247,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
-X Don't export variables passed on the command line to the environ-
ment individually. Variables passed on the command line are
- still exported via the MAKEFLAGS environment variable. This
- option may be useful on systems which have a small limit on the
+ still exported via the MAKEFLAGS environment variable. This op-
+ tion may be useful on systems which have a small limit on the
size of command arguments.
variable=value
@@ -268,9 +268,9 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
FILE DEPENDENCY SPECIFICATIONS
Dependency lines consist of one or more targets, an operator, and zero or
- more sources. This creates a relationship where the targets ``depend''
- on the sources and are customarily created from them. A target is con-
- sidered out-of-date if it does not exist, or if its modification time is
+ more sources. This creates a relationship where the targets "depend" on
+ the sources and are customarily created from them. A target is consid-
+ ered out-of-date if it does not exist, or if its modification time is
less than that of any of its sources. An out-of-date target will be re-
created, but not until all sources have been examined and themselves re-
created as needed. Three operators may be used:
@@ -285,20 +285,20 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
out of date.
:: Any dependency line may have attached shell commands, but each one
- is handled independently: its sources are considered and the
- attached shell commands are run if the target is out of date with
- respect to (only) those sources. Thus, different groups of the
- attached shell commands may be run depending on the circumstances.
+ is handled independently: its sources are considered and the at-
+ tached shell commands are run if the target is out of date with re-
+ spect to (only) those sources. Thus, different groups of the at-
+ tached shell commands may be run depending on the circumstances.
Furthermore, unlike :, for dependency lines with no sources, the
attached shell commands are always run. Also unlike :, the target
will not be removed if bmake is interrupted.
- All dependency lines mentioning a particular target must use the same
- operator.
+ All dependency lines mentioning a particular target must use the same op-
+ erator.
Targets and sources may contain the shell wildcard values `?', `*', `[]',
and `{}'. The values `?', `*', and `[]' may only be used as part of the
- final component of the target or source, and must be used to describe
- existing files. The value `{}' need not necessarily be used to describe
+ final component of the target or source, and must be used to describe ex-
+ isting files. The value `{}' need not necessarily be used to describe
existing files. Expansion is in directory order, not alphabetically as
done in the shell.
@@ -306,8 +306,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
Each target may have associated with it one or more lines of shell com-
mands, normally used to create the target. Each of the lines in this
script must be preceded by a tab. (For historical reasons, spaces are
- not accepted.) While targets can appear in many dependency lines if
- desired, by default only one of these rules may be followed by a creation
+ not accepted.) While targets can appear in many dependency lines if de-
+ sired, by default only one of these rules may be followed by a creation
script. If the `::' operator is used, however, all rules may include
scripts and the scripts are executed in the order found.
@@ -333,10 +333,10 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
failed.
Makefiles should be written so that the mode of bmake operation does not
- change their behavior. For example, any command which needs to use
- ``cd'' or ``chdir'' without potentially changing the directory for subse-
- quent commands should be put in parentheses so it executes in a subshell.
- To force the use of one shell, escape the line breaks so as to make the
+ change their behavior. For example, any command which needs to use "cd"
+ or "chdir" without potentially changing the directory for subsequent com-
+ mands should be put in parentheses so it executes in a subshell. To
+ force the use of one shell, escape the line breaks so as to make the
whole script one command. For example:
avoid-chdir-side-effects:
@@ -373,13 +373,13 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
not expanded. This can cause problems when variable modifiers
are used.
- != Expand the value and pass it to the shell for execution and
- assign the result to the variable. Any newlines in the result
- are replaced with spaces.
+ != Expand the value and pass it to the shell for execution and as-
+ sign the result to the variable. Any newlines in the result are
+ replaced with spaces.
- Any white-space before the assigned value is removed; if the value is
- being appended, a single space is inserted between the previous contents
- of the variable and the appended value.
+ Any white-space before the assigned value is removed; if the value is be-
+ ing appended, a single space is inserted between the previous contents of
+ the variable and the appended value.
Variables are expanded by surrounding the variable name with either curly
braces (`{}') or parentheses (`()') and preceding it with a dollar sign
@@ -403,7 +403,7 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
2. Variables in shell commands are expanded when the shell command is
executed.
- 3. ``.for'' loop index variables are expanded on each loop iteration.
+ 3. ".for" loop index variables are expanded on each loop iteration.
Note that other variables are not expanded inside loops so the fol-
lowing example code:
@@ -423,9 +423,9 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
1 2 3
3 3 3
- Because while ${a} contains ``1 2 3'' after the loop is executed,
- ${b} contains ``${j} ${j} ${j}'' which expands to ``3 3 3'' since
- after the loop completes ${j} contains ``3''.
+ Because while ${a} contains "1 2 3" after the loop is executed, ${b}
+ contains "${j} ${j} ${j}" which expands to "3 3 3" since after the
+ loop completes ${j} contains "3".
Variable classes
The four different classes of variables (in order of increasing prece-
@@ -454,8 +454,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
.IMPSRC In suffix-transformation rules, the name/path of the
source from which the target is to be transformed (the
- ``implied'' source); also known as `<'. It is not
- defined in explicit rules.
+ "implied" source); also known as `<'. It is not defined
+ in explicit rules.
.MEMBER The name of the archive member; also known as `%'.
@@ -546,10 +546,10 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
.MAKE.LEVEL The recursion depth of bmake. The initial instance of
bmake will be 0, and an incremented value is put into the
- environment to be seen by the next generation. This
- allows tests like: .if ${.MAKE.LEVEL} == 0 to protect
- things which should only be evaluated in the initial
- instance of bmake.
+ environment to be seen by the next generation. This al-
+ lows tests like: .if ${.MAKE.LEVEL} == 0 to protect
+ things which should only be evaluated in the initial in-
+ stance of bmake.
.MAKE.MAKEFILE_PREFERENCE
The ordered list of makefile names (default `makefile',
@@ -647,9 +647,9 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
Building ${.TARGET:H:tA}/${.TARGET:T}
.MAKEOVERRIDES This variable is used to record the names of variables
- assigned to on the command line, so that they may be
- exported as part of `MAKEFLAGS'. This behavior can be
- disabled by assigning an empty value to `.MAKEOVERRIDES'
+ assigned to on the command line, so that they may be ex-
+ ported as part of `MAKEFLAGS'. This behavior can be dis-
+ abled by assigning an empty value to `.MAKEOVERRIDES'
within a makefile. Extra variables can be exported from
a makefile by appending their names to `.MAKEOVERRIDES'.
`MAKEFLAGS' is re-exported whenever `.MAKEOVERRIDES' is
@@ -668,8 +668,12 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
value should be a boolean that controls whether `$$' are
preserved when doing `:=' assignments. The default is
false, for backwards compatibility. Set to true for com-
- patability with other makes. If set to false, `$$'
- becomes `$' per normal evaluation rules.
+ patability with other makes. If set to false, `$$' be-
+ comes `$' per normal evaluation rules.
+
+ .MAKE.UID The user-id running bmake.
+
+ .MAKE.GID The group-id running bmake.
MAKE_PRINT_VAR_ON_ERROR
When bmake stops due to an error, it sets `.ERROR_TARGET'
@@ -733,8 +737,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
.PARSEFILE The basename of the current `Makefile' being parsed.
This variable and `.PARSEDIR' are both set only while the
`Makefiles' are being parsed. If you want to retain
- their current values, assign them to a variable using
- assignment with expansion: (`:=').
+ their current values, assign them to a variable using as-
+ signment with expansion: (`:=').
.PATH A variable that represents the list of directories that
bmake will search for files. The search list should be
@@ -756,14 +760,14 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
.TARGETS The list of targets explicitly specified on the command
line, if any.
- VPATH Colon-separated (``:'') lists of directories that bmake
+ VPATH Colon-separated (":") lists of directories that bmake
will search for files. The variable is supported for
compatibility with old make programs only, use `.PATH'
instead.
Variable modifiers
Variable expansion may be modified to select or modify each word of the
- variable (where a ``word'' is white-space delimited sequence of charac-
+ variable (where a "word" is white-space delimited sequence of charac-
ters). The general format of a variable expansion is as follows:
${variable[:modifier[:...]]}
@@ -831,8 +835,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
passed safely to the shell.
:q Quotes every shell meta-character in the variable, and also doubles
- `$' characters so that it can be passed safely through recursive
- invocations of bmake. This is equivalent to: `:S/\$/&&/g:Q'.
+ `$' characters so that it can be passed safely through recursive in-
+ vocations of bmake. This is equivalent to: `:S/\$/&&/g:Q'.
:R Replaces each word in the variable with everything but its suffix.
@@ -872,11 +876,11 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
:S/old_string/new_string/[1gW]
Modifies the first occurrence of old_string in each word of the
- variable's value, replacing it with new_string. If a `g' is
- appended to the last delimiter of the pattern, all occurrences in
- each word are replaced. If a `1' is appended to the last delimiter
- of the pattern, only the first occurrence is affected. If a `W' is
- appended to the last delimiter of the pattern, then the value is
+ variable's value, replacing it with new_string. If a `g' is ap-
+ pended to the last delimiter of the pattern, all occurrences in each
+ word are replaced. If a `1' is appended to the last delimiter of
+ the pattern, only the first occurrence is affected. If a `W' is ap-
+ pended to the last delimiter of the pattern, then the value is
treated as a single word (possibly containing embedded white space).
If old_string begins with a caret (`^'), old_string is anchored at
the beginning of each word. If old_string ends with a dollar sign
@@ -927,8 +931,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
This is the AT&T System V UNIX style variable substitution. It must
be the last modifier specified. If old_string or new_string do not
contain the pattern matching character % then it is assumed that
- they are anchored at the end of each word, so only suffixes or
- entire words may be replaced. Otherwise % is the substring of
+ they are anchored at the end of each word, so only suffixes or en-
+ tire words may be replaced. Otherwise % is the substring of
old_string to be replaced in new_string. If only old_string con-
tains the pattern matching character %, and old_string matches, then
the result is the new_string. If only the new_string contains the
@@ -971,8 +975,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
:Unewval
If the variable is undefined, newval is the value. If the variable
is defined, the existing value is returned. This is another ODE
- make feature. It is handy for setting per-target CFLAGS for
- instance:
+ make feature. It is handy for setting per-target CFLAGS for in-
+ stance:
${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}}
If a value is only required if the variable is undefined, use:
${VAR:D:Unewval}
@@ -1027,8 +1031,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
gers (where index 1 represents the first word), and backwards using
negative integers (where index -1 represents the last word).
- The range is subjected to variable expansion, and the expanded
- result is then interpreted as follows:
+ The range is subjected to variable expansion, and the expanded re-
+ sult is then interpreted as follows:
index Selects a single word from the value.
@@ -1037,8 +1041,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
`:[2..-1]' selects all words from the second word to the last
word. If start is greater than end, then the words are out-
put in reverse order. For example, `:[-1..1]' selects all
- the words from last to first. If the list is already
- ordered, then this effectively reverses the list, but it is
+ the words from last to first. If the list is already or-
+ dered, then this effectively reverses the list, but it is
more efficient to use `:Or' instead of `:O:[-1..1]'.
* Causes subsequent modifiers to treat the value as a single
@@ -1059,18 +1063,18 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
are identified by a line beginning with a single dot (`.') character.
Files are included with either .include <file> or .include "file". Vari-
ables between the angle brackets or double quotes are expanded to form
- the file name. If angle brackets are used, the included makefile is
- expected to be in the system makefile directory. If double quotes are
+ the file name. If angle brackets are used, the included makefile is ex-
+ pected to be in the system makefile directory. If double quotes are
used, the including makefile's directory and any directories specified
using the -I option are searched before the system makefile directory.
For compatibility with other versions of bmake `include file ...' is also
accepted.
- If the include statement is written as .-include or as .sinclude then
- errors locating and/or opening include files are ignored.
+ If the include statement is written as .-include or as .sinclude then er-
+ rors locating and/or opening include files are ignored.
- If the include statement is written as .dinclude not only are errors
- locating and/or opening include files ignored, but stale dependencies
+ If the include statement is written as .dinclude not only are errors lo-
+ cating and/or opening include files ignored, but stale dependencies
within the included file will be ignored just like .MAKE.DEPENDFILE.
Conditional expressions are also preceded by a single dot as the first
@@ -1087,8 +1091,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
flag, so should be used with caution. For compatibility with
other bmake programs `export variable=value' is also accepted.
- Appending a variable name to .MAKE.EXPORTED is equivalent to
- exporting a variable.
+ Appending a variable name to .MAKE.EXPORTED is equivalent to ex-
+ porting a variable.
.export-env variable ...
The same as `.export', except that the variable is not appended
@@ -1103,9 +1107,9 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
The message is printed along with the name of the makefile and
line number.
- .undef variable
- Un-define the specified global variable. Only global variables
- may be un-defined.
+ .undef variable ...
+ Un-define the specified global variables. Only global variables
+ can be un-defined.
.unexport variable ...
The opposite of `.export'. The specified global variable will be
@@ -1172,7 +1176,7 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
|| Logical OR.
- && Logical AND; of higher precedence than ``||''.
+ && Logical AND; of higher precedence than "||".
As in C, bmake will only evaluate a conditional as far as is necessary to
determine its value. Parentheses may be used to change the order of
@@ -1185,9 +1189,9 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
the variable has been defined.
make Takes a target name as an argument and evaluates to true if the
- target was specified as part of bmake's command line or was
- declared the default target (either implicitly or explicitly,
- see .MAIN) before the line containing the conditional.
+ target was specified as part of bmake's command line or was de-
+ clared the default target (either implicitly or explicitly, see
+ .MAIN) before the line containing the conditional.
empty Takes a variable, with possible modifiers, and evaluates to true
if the expansion of the variable would result in an empty
@@ -1204,10 +1208,10 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
Takes a target name as an argument and evaluates to true if the
target has been defined and has commands associated with it.
- Expression may also be an arithmetic or string comparison. Variable
- expansion is performed on both sides of the comparison, after which the
- numerical values are compared. A value is interpreted as hexadecimal if
- it is preceded by 0x, otherwise it is decimal; octal numbers are not sup-
+ Expression may also be an arithmetic or string comparison. Variable ex-
+ pansion is performed on both sides of the comparison, after which the nu-
+ merical values are compared. A value is interpreted as hexadecimal if it
+ is preceded by 0x, otherwise it is decimal; octal numbers are not sup-
ported. The standard C relational operators are all supported. If after
variable expansion, either the left or right hand side of a `==' or `!='
operator is not a numerical value, then string comparison is performed
@@ -1215,12 +1219,12 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
is assumed that the expanded variable is being compared against 0, or an
empty string in the case of a string comparison.
- When bmake is evaluating one of these conditional expressions, and it
- encounters a (white-space separated) word it doesn't recognize, either
- the ``make'' or ``defined'' expression is applied to it, depending on the
- form of the conditional. If the form is `.ifdef', `.ifndef', or `.if'
- the ``defined'' expression is applied. Similarly, if the form is
- `.ifmake' or `.ifnmake', the ``make'' expression is applied.
+ When bmake is evaluating one of these conditional expressions, and it en-
+ counters a (white-space separated) word it doesn't recognize, either the
+ "make" or "defined" expression is applied to it, depending on the form of
+ the conditional. If the form is `.ifdef', `.ifndef', or `.if' the
+ "defined" expression is applied. Similarly, if the form is `.ifmake' or
+ `.ifnmake', the "make" expression is applied.
If the conditional evaluates to true the parsing of the makefile contin-
ues as before. If it evaluates to false, the following lines are
@@ -1272,8 +1276,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
always changes. If the number of commands change, though, the
target will still be out of date. The same effect applies to
any command line that uses the variable .OODATE, which can be
- used for that purpose even when not otherwise needed or
- desired:
+ used for that purpose even when not otherwise needed or de-
+ sired:
skip-compare-for-some:
@@ -1303,8 +1307,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
.PRECIOUS
When bmake is interrupted, it normally removes any partially
- made targets. This source prevents the target from being
- removed.
+ made targets. This source prevents the target from being re-
+ moved.
.RECURSIVE
Synonym for .MAKE.
@@ -1324,10 +1328,10 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
.WAIT If .WAIT appears in a dependency line, the sources that precede
it are made before the sources that succeed it in the line.
- Since the dependents of files are not made until the file
- itself could be made, this also stops the dependents being
- built unless they are needed for another branch of the depen-
- dency tree. So given:
+ Since the dependents of files are not made until the file it-
+ self could be made, this also stops the dependents being built
+ unless they are needed for another branch of the dependency
+ tree. So given:
x: a .WAIT b
echo x
@@ -1361,14 +1365,14 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
make to delete targets whose commands fail. (By default, only
targets whose commands are interrupted during execution are
deleted. This is the historical behavior.) This setting can be
- used to help prevent half-finished or malformed targets from
- being left around and corrupting future rebuilds.
+ used to help prevent half-finished or malformed targets from be-
+ ing left around and corrupting future rebuilds.
- .END Any command lines attached to this target are executed after
- everything else is done.
+ .END Any command lines attached to this target are executed after ev-
+ erything else is done.
- .ERROR Any command lines attached to this target are executed when
- another target fails. The .ERROR_TARGET variable is set to the
+ .ERROR Any command lines attached to this target are executed when an-
+ other target fails. The .ERROR_TARGET variable is set to the
target that failed. See also MAKE_PRINT_VAR_ON_ERROR.
.IGNORE Mark each of the sources with the .IGNORE attribute. If no
@@ -1425,8 +1429,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
.PRECIOUS
Apply the .PRECIOUS attribute to any specified sources. If no
- sources are specified, the .PRECIOUS attribute is applied to
- every target in the file.
+ sources are specified, the .PRECIOUS attribute is applied to ev-
+ ery target in the file.
.SHELL Sets the shell that bmake will use to execute commands. The
sources are a set of field=value pairs.
@@ -1469,8 +1473,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
sources are specified, the .SILENT attribute is applied to every
command in the file.
- .STALE This target gets run when a dependency file contains stale
- entries, having .ALLSRC set to the name of that dependency file.
+ .STALE This target gets run when a dependency file contains stale en-
+ tries, having .ALLSRC set to the name of that dependency file.
.SUFFIXES
Each source specifies a suffix to bmake. If no sources are
@@ -1509,8 +1513,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
The way that .for loop variables are substituted changed after NetBSD 5.0
so that they still appear to be variable expansions. In particular this
- stops them being treated as syntax, and removes some obscure problems
- using them in .if statements.
+ stops them being treated as syntax, and removes some obscure problems us-
+ ing them in .if statements.
The way that parallel makes are scheduled changed in NetBSD 4.0 so that
.ORDER and .WAIT apply recursively to the dependent nodes. The algo-
@@ -1518,8 +1522,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
Other make dialects
Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not sup-
- port most of the features of bmake as described in this manual. Most
- notably:
+ port most of the features of bmake as described in this manual. Most no-
+ tably:
+o The .WAIT and .ORDER declarations and most functionality per-
taining to parallelization. (GNU make supports parallelization
@@ -1544,8 +1548,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
Some features are somewhat more portable, such as assignment with +=, ?=,
and !=. The .PATH functionality is based on an older feature VPATH found
- in GNU make and many versions of SVR4 make; however, historically its
- behavior is too ill-defined (and too buggy) to rely upon.
+ in GNU make and many versions of SVR4 make; however, historically its be-
+ havior is too ill-defined (and too buggy) to rely upon.
The $@ and $< variables are more or less universally portable, as is the
$(MAKE) variable. Basic use of suffix rules (for files only in the cur-
@@ -1562,11 +1566,11 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
A make command appeared in Version 7 AT&T UNIX. This make implementation
is based on Adam De Boor's pmake program which was written for Sprite at
Berkeley. It was designed to be a parallel distributed make running jobs
- on different machines using a daemon called ``customs''.
+ on different machines using a daemon called "customs".
- Historically the target/dependency ``FRC'' has been used to FoRCe
- rebuilding (since the target/dependency does not exist... unless someone
- creates an ``FRC'' file).
+ Historically the target/dependency "FRC" has been used to FoRCe rebuild-
+ ing (since the target/dependency does not exist... unless someone creates
+ an "FRC" file).
BUGS
The make syntax is difficult to parse without actually acting on the
@@ -1577,4 +1581,4 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
There is no way of escaping a space character in a filename.
-FreeBSD 11.3 November 14, 2020 FreeBSD 11.3
+FreeBSD 13.0 December 22, 2020 FreeBSD 13.0
diff --git a/buf.c b/buf.c
index 6cbf31a2bd60..b6a2e9192b60 100644
--- a/buf.c
+++ b/buf.c
@@ -1,4 +1,4 @@
-/* $NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig Exp $ */
+/* $NetBSD: buf.c,v 1.47 2020/12/30 10:03:16 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -69,146 +69,153 @@
* SUCH DAMAGE.
*/
-/* Automatically-expanding null-terminated buffers. */
+/* Automatically-expanding null-terminated character buffers. */
#include <limits.h>
#include "make.h"
/* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig Exp $");
+MAKE_RCSID("$NetBSD: buf.c,v 1.47 2020/12/30 10:03:16 rillig Exp $");
-/* Make space in the buffer for adding a single byte. */
+/* Make space in the buffer for adding at least 16 more bytes. */
void
-Buf_Expand_1(Buffer *buf)
+Buf_Expand(Buffer *buf)
{
- buf->cap += buf->cap > 16 ? buf->cap : 16;
- buf->data = bmake_realloc(buf->data, buf->cap);
+ buf->cap += buf->cap > 16 ? buf->cap : 16;
+ buf->data = bmake_realloc(buf->data, buf->cap);
}
/* Add the bytes to the buffer. */
void
Buf_AddBytes(Buffer *buf, const char *bytes, size_t bytes_len)
{
- size_t old_len = buf->len;
- char *end;
-
- if (__predict_false(old_len + bytes_len >= buf->cap)) {
- buf->cap += buf->cap > bytes_len + 16 ? buf->cap : bytes_len + 16;
- buf->data = bmake_realloc(buf->data, buf->cap);
- }
-
- end = buf->data + old_len;
- buf->len = old_len + bytes_len;
- memcpy(end, bytes, bytes_len);
- end[bytes_len] = '\0';
+ size_t old_len = buf->len;
+ char *end;
+
+ if (__predict_false(old_len + bytes_len >= buf->cap)) {
+ size_t minIncr = bytes_len + 16;
+ buf->cap += buf->cap > minIncr ? buf->cap : minIncr;
+ buf->data = bmake_realloc(buf->data, buf->cap);
+ }
+
+ end = buf->data + old_len;
+ buf->len = old_len + bytes_len;
+ memcpy(end, bytes, bytes_len);
+ end[bytes_len] = '\0';
}
/* Add the bytes between start and end to the buffer. */
void
Buf_AddBytesBetween(Buffer *buf, const char *start, const char *end)
{
- Buf_AddBytes(buf, start, (size_t)(end - start));
+ Buf_AddBytes(buf, start, (size_t)(end - start));
}
/* Add the string to the buffer. */
void
Buf_AddStr(Buffer *buf, const char *str)
{
- Buf_AddBytes(buf, str, strlen(str));
+ Buf_AddBytes(buf, str, strlen(str));
}
/* Add the number to the buffer. */
void
Buf_AddInt(Buffer *buf, int n)
{
- enum {
- bits = sizeof(int) * CHAR_BIT,
- max_octal_digits = (bits + 2) / 3,
- max_decimal_digits = /* at most */ max_octal_digits,
- max_sign_chars = 1,
- str_size = max_sign_chars + max_decimal_digits + 1
- };
- char str[str_size];
-
- size_t len = (size_t)snprintf(str, sizeof str, "%d", n);
- Buf_AddBytes(buf, str, len);
+ enum {
+ bits = sizeof(int) * CHAR_BIT,
+ max_octal_digits = (bits + 2) / 3,
+ max_decimal_digits = /* at most */ max_octal_digits,
+ max_sign_chars = 1,
+ str_size = max_sign_chars + max_decimal_digits + 1
+ };
+ char str[str_size];
+
+ size_t len = (size_t)snprintf(str, sizeof str, "%d", n);
+ Buf_AddBytes(buf, str, len);
}
-/* Get the data (usually a string) from the buffer.
+/*
+ * Get the data (usually a string) from the buffer.
* The returned data is valid until the next modifying operation
* on the buffer.
*
- * Returns the data and optionally the length of the data. */
+ * Returns the data and optionally the length of the data.
+ */
char *
Buf_GetAll(Buffer *buf, size_t *out_len)
{
- if (out_len != NULL)
- *out_len = buf->len;
- return buf->data;
+ if (out_len != NULL)
+ *out_len = buf->len;
+ return buf->data;
}
/* Mark the buffer as empty, so it can be filled with data again. */
void
Buf_Empty(Buffer *buf)
{
- buf->len = 0;
- buf->data[0] = '\0';
+ buf->len = 0;
+ buf->data[0] = '\0';
}
/* Initialize a buffer. */
void
Buf_InitSize(Buffer *buf, size_t cap)
{
- buf->cap = cap;
- buf->len = 0;
- buf->data = bmake_malloc(cap);
- buf->data[0] = '\0';
+ buf->cap = cap;
+ buf->len = 0;
+ buf->data = bmake_malloc(cap);
+ buf->data[0] = '\0';
}
void
Buf_Init(Buffer *buf)
{
- Buf_InitSize(buf, 256);
+ Buf_InitSize(buf, 256);
}
-/* Reset the buffer.
+/*
+ * Reset the buffer.
* If freeData is TRUE, the data from the buffer is freed as well.
- * Otherwise it is kept and returned. */
+ * Otherwise it is kept and returned.
+ */
char *
Buf_Destroy(Buffer *buf, Boolean freeData)
{
- char *data = buf->data;
- if (freeData) {
- free(data);
- data = NULL;
- }
+ char *data = buf->data;
+ if (freeData) {
+ free(data);
+ data = NULL;
+ }
- buf->cap = 0;
- buf->len = 0;
- buf->data = NULL;
+ buf->cap = 0;
+ buf->len = 0;
+ buf->data = NULL;
- return data;
+ return data;
}
#ifndef BUF_COMPACT_LIMIT
-# define BUF_COMPACT_LIMIT 128 /* worthwhile saving */
+# define BUF_COMPACT_LIMIT 128 /* worthwhile saving */
#endif
-/* Reset the buffer and return its data.
+/*
+ * Reset the buffer and return its data.
*
* If the buffer size is much greater than its content,
- * a new buffer will be allocated and the old one freed. */
+ * a new buffer will be allocated and the old one freed.
+ */
char *
Buf_DestroyCompact(Buffer *buf)
{
#if BUF_COMPACT_LIMIT > 0
- if (buf->cap - buf->len >= BUF_COMPACT_LIMIT) {
- /* We trust realloc to be smart */
- char *data = bmake_realloc(buf->data, buf->len + 1);
- data[buf->len] = '\0'; /* XXX: unnecessary */
- Buf_Destroy(buf, FALSE);
- return data;
- }
+ if (buf->cap - buf->len >= BUF_COMPACT_LIMIT) {
+ /* We trust realloc to be smart */
+ char *data = bmake_realloc(buf->data, buf->len + 1);
+ data[buf->len] = '\0'; /* XXX: unnecessary */
+ Buf_Destroy(buf, FALSE);
+ return data;
+ }
#endif
- return Buf_Destroy(buf, FALSE);
+ return Buf_Destroy(buf, FALSE);
}
diff --git a/buf.h b/buf.h
index 6ab3d3288a43..942b115c76e6 100644
--- a/buf.h
+++ b/buf.h
@@ -1,4 +1,4 @@
-/* $NetBSD: buf.h,v 1.36 2020/11/10 00:32:12 rillig Exp $ */
+/* $NetBSD: buf.h,v 1.38 2020/12/28 15:42:53 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -81,9 +81,9 @@
/* An automatically growing null-terminated buffer of characters. */
typedef struct Buffer {
- size_t cap; /* Allocated size of the buffer, including the null */
- size_t len; /* Number of bytes in buffer, excluding the null */
- char *data; /* The buffer itself (always null-terminated) */
+ size_t cap; /* Allocated size of the buffer, including the null */
+ size_t len; /* Number of bytes in buffer, excluding the null */
+ char *data; /* The buffer itself (always null-terminated) */
} Buffer;
/* If we aren't on NetBSD, __predict_false() might not be defined. */
@@ -91,31 +91,31 @@ typedef struct Buffer {
#define __predict_false(x) (x)
#endif
-void Buf_Expand_1(Buffer *);
+void Buf_Expand(Buffer *);
/* Buf_AddByte adds a single byte to a buffer. */
MAKE_INLINE void
Buf_AddByte(Buffer *buf, char byte)
{
- size_t old_len = buf->len++;
- char *end;
- if (__predict_false(old_len + 1 >= buf->cap))
- Buf_Expand_1(buf);
- end = buf->data + old_len;
- end[0] = byte;
- end[1] = '\0';
+ size_t old_len = buf->len++;
+ char *end;
+ if (__predict_false(old_len + 1 >= buf->cap))
+ Buf_Expand(buf);
+ end = buf->data + old_len;
+ end[0] = byte;
+ end[1] = '\0';
}
MAKE_INLINE size_t
Buf_Len(const Buffer *buf)
{
- return buf->len;
+ return buf->len;
}
MAKE_INLINE Boolean
Buf_EndsWith(const Buffer *buf, char ch)
{
- return buf->len > 0 && buf->data[buf->len - 1] == ch;
+ return buf->len > 0 && buf->data[buf->len - 1] == ch;
}
void Buf_AddBytes(Buffer *, const char *, size_t);
diff --git a/compat.c b/compat.c
index 2307e9f2d935..6c7238318959 100644
--- a/compat.c
+++ b/compat.c
@@ -1,4 +1,4 @@
-/* $NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $ */
+/* $NetBSD: compat.c,v 1.219 2021/01/10 21:20:46 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.183 2020/11/15 22:31:03 rillig Exp $");
+MAKE_RCSID("$NetBSD: compat.c,v 1.219 2021/01/10 21:20:46 rillig Exp $");
static GNode *curTarg = NULL;
static pid_t compatChild;
@@ -112,16 +112,17 @@ static int compatSigno;
static void
CompatDeleteTarget(GNode *gn)
{
- if (gn != NULL && !Targ_Precious(gn)) {
- const char *file = GNode_VarTarget(gn);
+ if (gn != NULL && !Targ_Precious(gn)) {
+ const char *file = GNode_VarTarget(gn);
- if (!opts.noExecute && eunlink(file) != -1) {
- Error("*** %s removed", file);
+ if (!opts.noExecute && eunlink(file) != -1) {
+ Error("*** %s removed", file);
+ }
}
- }
}
-/* Interrupt the creation of the current target and remove it if it ain't
+/*
+ * Interrupt the creation of the current target and remove it if it ain't
* precious. Then exit.
*
* If .INTERRUPT exists, its commands are run first WITH INTERRUPTS IGNORED.
@@ -132,348 +133,362 @@ CompatDeleteTarget(GNode *gn)
static void
CompatInterrupt(int signo)
{
- CompatDeleteTarget(curTarg);
+ CompatDeleteTarget(curTarg);
+
+ if (curTarg != NULL && !Targ_Precious(curTarg)) {
+ /*
+ * Run .INTERRUPT only if hit with interrupt signal
+ */
+ if (signo == SIGINT) {
+ GNode *gn = Targ_FindNode(".INTERRUPT");
+ if (gn != NULL) {
+ Compat_Make(gn, gn);
+ }
+ }
+ }
+
+ if (signo == SIGQUIT)
+ _exit(signo);
- if (curTarg != NULL && !Targ_Precious(curTarg)) {
/*
- * Run .INTERRUPT only if hit with interrupt signal
+ * If there is a child running, pass the signal on.
+ * We will exist after it has exited.
*/
- if (signo == SIGINT) {
- GNode *gn = Targ_FindNode(".INTERRUPT");
- if (gn != NULL) {
- Compat_Make(gn, gn);
- }
- }
- }
-
- if (signo == SIGQUIT)
- _exit(signo);
-
- /*
- * If there is a child running, pass the signal on.
- * We will exist after it has exited.
- */
- compatSigno = signo;
- if (compatChild > 0) {
- KILLPG(compatChild, signo);
- } else {
- bmake_signal(signo, SIG_DFL);
- kill(myPid, signo);
- }
+ compatSigno = signo;
+ if (compatChild > 0) {
+ KILLPG(compatChild, signo);
+ } else {
+ bmake_signal(signo, SIG_DFL);
+ kill(myPid, signo);
+ }
+}
+
+static void
+DebugFailedTarget(const char *cmd, GNode *gn)
+{
+ const char *p = cmd;
+ debug_printf("\n*** Failed target: %s\n*** Failed command: ",
+ gn->name);
+
+ /* Replace runs of whitespace with a single space, to reduce
+ * the amount of whitespace for multi-line command lines. */
+ while (*p != '\0') {
+ if (ch_isspace(*p)) {
+ debug_printf(" ");
+ cpp_skip_whitespace(&p);
+ } else {
+ debug_printf("%c", *p);
+ p++;
+ }
+ }
+ debug_printf("\n");
}
-/* Execute the next command for a target. If the command returns an error,
+static Boolean
+UseShell(const char *cmd MAKE_ATTR_UNUSED)
+{
+#if !defined(MAKE_NATIVE)
+ /*
+ * In a non-native build, the host environment might be weird enough
+ * that it's necessary to go through a shell to get the correct
+ * behaviour. Or perhaps the shell has been replaced with something
+ * that does extra logging, and that should not be bypassed.
+ */
+ return TRUE;
+#else
+ /*
+ * Search for meta characters in the command. If there are no meta
+ * characters, there's no need to execute a shell to execute the
+ * command.
+ *
+ * Additionally variable assignments and empty commands
+ * go to the shell. Therefore treat '=' and ':' like shell
+ * meta characters as documented in make(1).
+ */
+
+ return needshell(cmd);
+#endif
+}
+
+/*
+ * Execute the next command for a target. If the command returns an error,
* the node's made field is set to ERROR and creation stops.
*
* Input:
* cmdp Command to execute
- * gnp Node from which the command came
+ * gn Node from which the command came
+ * ln List node that contains the command
*
* Results:
* 0 if the command succeeded, 1 if an error occurred.
*/
int
-Compat_RunCommand(const char *cmdp, GNode *gn)
+Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
{
- char *cmdStart; /* Start of expanded command */
- char *bp;
- Boolean silent; /* Don't print command */
- Boolean doIt; /* Execute even if -n */
- volatile Boolean errCheck; /* Check errors */
- WAIT_T reason; /* Reason for child's death */
- int status; /* Description of child's death */
- pid_t cpid; /* Child actually found */
- pid_t retstat; /* Result of wait */
- StringListNode *cmdNode; /* Node where current command is located */
- const char **volatile av; /* Argument vector for thing to exec */
- char **volatile mav; /* Copy of the argument vector for freeing */
- Boolean useShell; /* TRUE if command should be executed
+ char *cmdStart; /* Start of expanded command */
+ char *bp;
+ Boolean silent; /* Don't print command */
+ Boolean doIt; /* Execute even if -n */
+ volatile Boolean errCheck; /* Check errors */
+ WAIT_T reason; /* Reason for child's death */
+ WAIT_T status; /* Description of child's death */
+ pid_t cpid; /* Child actually found */
+ pid_t retstat; /* Result of wait */
+ const char **volatile av; /* Argument vector for thing to exec */
+ char **volatile mav; /* Copy of the argument vector for freeing */
+ Boolean useShell; /* TRUE if command should be executed
* using a shell */
- const char *volatile cmd = cmdp;
-
- silent = (gn->type & OP_SILENT) != 0;
- errCheck = !(gn->type & OP_IGNORE);
- doIt = FALSE;
-
- /* Luckily the commands don't end up in a string pool, otherwise
- * this comparison could match too early, in a dependency using "..."
- * for delayed commands, run in parallel mode, using the same shell
- * command line more than once; see JobPrintCommand.
- * TODO: write a unit-test to protect against this potential bug. */
- cmdNode = Lst_FindDatum(gn->commands, cmd);
- (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart);
- /* TODO: handle errors */
-
- if (cmdStart[0] == '\0') {
- free(cmdStart);
- return 0;
- }
- cmd = cmdStart;
- LstNode_Set(cmdNode, cmdStart);
-
- if (gn->type & OP_SAVE_CMDS) {
- GNode *endNode = Targ_GetEndNode();
- if (gn != endNode) {
- Lst_Append(endNode->commands, cmdStart);
- return 0;
- }
- }
- if (strcmp(cmdStart, "...") == 0) {
- gn->type |= OP_SAVE_CMDS;
- return 0;
- }
-
- for (;;) {
- if (*cmd == '@')
- silent = !DEBUG(LOUD);
- else if (*cmd == '-')
- errCheck = FALSE;
- else if (*cmd == '+') {
- doIt = TRUE;
- if (!shellName) /* we came here from jobs */
- Shell_Init();
- } else
- break;
- cmd++;
- }
+ const char *volatile cmd = cmdp;
- while (ch_isspace(*cmd))
- cmd++;
+ silent = (gn->type & OP_SILENT) != 0;
+ errCheck = !(gn->type & OP_IGNORE);
+ doIt = FALSE;
- /*
- * If we did not end up with a command, just skip it.
- */
- if (cmd[0] == '\0')
- return 0;
+ (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart);
+ /* TODO: handle errors */
-#if !defined(MAKE_NATIVE)
- /*
- * In a non-native build, the host environment might be weird enough
- * that it's necessary to go through a shell to get the correct
- * behaviour. Or perhaps the shell has been replaced with something
- * that does extra logging, and that should not be bypassed.
- */
- useShell = TRUE;
-#else
- /*
- * Search for meta characters in the command. If there are no meta
- * characters, there's no need to execute a shell to execute the
- * command.
- *
- * Additionally variable assignments and empty commands
- * go to the shell. Therefore treat '=' and ':' like shell
- * meta characters as documented in make(1).
- */
-
- useShell = needshell(cmd);
-#endif
+ if (cmdStart[0] == '\0') {
+ free(cmdStart);
+ return 0;
+ }
+ cmd = cmdStart;
+ LstNode_Set(ln, cmdStart);
+
+ if (gn->type & OP_SAVE_CMDS) {
+ GNode *endNode = Targ_GetEndNode();
+ if (gn != endNode) {
+ /*
+ * Append the expanded command, to prevent the
+ * local variables from being interpreted in the
+ * context of the .END node.
+ *
+ * A probably unintended side effect of this is that
+ * the expanded command will be expanded again in the
+ * .END node. Therefore, a literal '$' in these
+ * commands must be written as '$$$$' instead of the
+ * usual '$$'.
+ */
+ Lst_Append(&endNode->commands, cmdStart);
+ return 0;
+ }
+ }
+ if (strcmp(cmdStart, "...") == 0) {
+ gn->type |= OP_SAVE_CMDS;
+ return 0;
+ }
+
+ for (;;) {
+ if (*cmd == '@')
+ silent = !DEBUG(LOUD);
+ else if (*cmd == '-')
+ errCheck = FALSE;
+ else if (*cmd == '+') {
+ doIt = TRUE;
+ if (shellName == NULL) /* we came here from jobs */
+ Shell_Init();
+ } else
+ break;
+ cmd++;
+ }
+
+ while (ch_isspace(*cmd))
+ cmd++;
- /*
- * Print the command before echoing if we're not supposed to be quiet for
- * this one. We also print the command if -n given.
- */
- if (!silent || !GNode_ShouldExecute(gn)) {
- printf("%s\n", cmd);
- fflush(stdout);
- }
-
- /*
- * If we're not supposed to execute any commands, this is as far as
- * we go...
- */
- if (!doIt && !GNode_ShouldExecute(gn))
- return 0;
-
- DEBUG1(JOB, "Execute: '%s'\n", cmd);
-
- if (useShell) {
/*
- * We need to pass the command off to the shell, typically
- * because the command contains a "meta" character.
+ * If we did not end up with a command, just skip it.
*/
- static const char *shargv[5];
-
- /* The following work for any of the builtin shell specs. */
- int shargc = 0;
- shargv[shargc++] = shellPath;
- if (errCheck && shellErrFlag)
- shargv[shargc++] = shellErrFlag;
- shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c";
- shargv[shargc++] = cmd;
- shargv[shargc] = NULL;
- av = shargv;
- bp = NULL;
- mav = NULL;
- } else {
+ if (cmd[0] == '\0')
+ return 0;
+
+ useShell = UseShell(cmd);
/*
- * No meta-characters, so no need to exec a shell. Break the command
- * into words to form an argument vector we can execute.
+ * Print the command before echoing if we're not supposed to be quiet
+ * for this one. We also print the command if -n given.
*/
- Words words = Str_Words(cmd, FALSE);
- mav = words.words;
- bp = words.freeIt;
- av = (void *)mav;
- }
+ if (!silent || !GNode_ShouldExecute(gn)) {
+ printf("%s\n", cmd);
+ fflush(stdout);
+ }
-#ifdef USE_META
- if (useMeta) {
- meta_compat_start();
- }
-#endif
+ /*
+ * If we're not supposed to execute any commands, this is as far as
+ * we go...
+ */
+ if (!doIt && !GNode_ShouldExecute(gn))
+ return 0;
+
+ DEBUG1(JOB, "Execute: '%s'\n", cmd);
+
+ if (useShell) {
+ /*
+ * We need to pass the command off to the shell, typically
+ * because the command contains a "meta" character.
+ */
+ static const char *shargv[5];
+
+ /* The following work for any of the builtin shell specs. */
+ int shargc = 0;
+ shargv[shargc++] = shellPath;
+ if (errCheck && shellErrFlag != NULL)
+ shargv[shargc++] = shellErrFlag;
+ shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c";
+ shargv[shargc++] = cmd;
+ shargv[shargc] = NULL;
+ av = shargv;
+ bp = NULL;
+ mav = NULL;
+ } else {
+ /*
+ * No meta-characters, so no need to exec a shell. Break the
+ * command into words to form an argument vector we can
+ * execute.
+ */
+ Words words = Str_Words(cmd, FALSE);
+ mav = words.words;
+ bp = words.freeIt;
+ av = (void *)mav;
+ }
- /*
- * Fork and execute the single command. If the fork fails, we abort.
- */
- compatChild = cpid = vFork();
- if (cpid < 0) {
- Fatal("Could not fork");
- }
- if (cpid == 0) {
- Var_ExportVars();
#ifdef USE_META
if (useMeta) {
- meta_compat_child();
+ meta_compat_start();
}
#endif
- (void)execvp(av[0], (char *const *)UNCONST(av));
- execDie("exec", av[0]);
- }
- free(mav);
- free(bp);
+ Var_ReexportVars();
- /* XXX: Memory management looks suspicious here. */
- /* XXX: Setting a list item to NULL is unexpected. */
- LstNode_SetNull(cmdNode);
+ /*
+ * Fork and execute the single command. If the fork fails, we abort.
+ */
+ compatChild = cpid = vFork();
+ if (cpid < 0) {
+ Fatal("Could not fork");
+ }
+ if (cpid == 0) {
+#ifdef USE_META
+ if (useMeta) {
+ meta_compat_child();
+ }
+#endif
+ (void)execvp(av[0], (char *const *)UNCONST(av));
+ execDie("exec", av[0]);
+ }
+
+ free(mav);
+ free(bp);
+
+ /* XXX: Memory management looks suspicious here. */
+ /* XXX: Setting a list item to NULL is unexpected. */
+ LstNode_SetNull(ln);
#ifdef USE_META
- if (useMeta) {
- meta_compat_parent(cpid);
- }
+ if (useMeta) {
+ meta_compat_parent(cpid);
+ }
#endif
- /*
- * The child is off and running. Now all we can do is wait...
- */
- while ((retstat = wait(&reason)) != cpid) {
- if (retstat > 0)
- JobReapChild(retstat, reason, FALSE); /* not ours? */
- if (retstat == -1 && errno != EINTR) {
- break;
+ /*
+ * The child is off and running. Now all we can do is wait...
+ */
+ while ((retstat = wait(&reason)) != cpid) {
+ if (retstat > 0)
+ JobReapChild(retstat, reason, FALSE); /* not ours? */
+ if (retstat == -1 && errno != EINTR) {
+ break;
+ }
}
- }
- if (retstat < 0)
- Fatal("error in wait: %d: %s", retstat, strerror(errno));
+ if (retstat < 0)
+ Fatal("error in wait: %d: %s", retstat, strerror(errno));
- if (WIFSTOPPED(reason)) {
- status = WSTOPSIG(reason); /* stopped */
- } else if (WIFEXITED(reason)) {
- status = WEXITSTATUS(reason); /* exited */
+ if (WIFSTOPPED(reason)) {
+ status = WSTOPSIG(reason); /* stopped */
+ } else if (WIFEXITED(reason)) {
+ status = WEXITSTATUS(reason); /* exited */
#if defined(USE_META) && defined(USE_FILEMON_ONCE)
- if (useMeta) {
- meta_cmd_finish(NULL);
- }
+ if (useMeta) {
+ meta_cmd_finish(NULL);
+ }
#endif
- if (status != 0) {
- if (DEBUG(ERROR)) {
- const char *p = cmd;
- debug_printf("\n*** Failed target: %s\n*** Failed command: ",
- gn->name);
-
- /* Replace runs of whitespace with a single space, to reduce
- * the amount of whitespace for multi-line command lines. */
- while (*p != '\0') {
- if (ch_isspace(*p)) {
- debug_printf(" ");
- cpp_skip_whitespace(&p);
- } else {
- debug_printf("%c", *p);
- p++;
- }
+ if (status != 0) {
+ if (DEBUG(ERROR))
+ DebugFailedTarget(cmd, gn);
+ printf("*** Error code %d", status);
}
- debug_printf("\n");
- }
- printf("*** Error code %d", status);
+ } else {
+ status = WTERMSIG(reason); /* signaled */
+ printf("*** Signal %d", status);
}
- } else {
- status = WTERMSIG(reason); /* signaled */
- printf("*** Signal %d", status);
- }
- if (!WIFEXITED(reason) || status != 0) {
- if (errCheck) {
+ if (!WIFEXITED(reason) || status != 0) {
+ if (errCheck) {
#ifdef USE_META
- if (useMeta) {
- meta_job_error(NULL, gn, 0, status);
- }
+ if (useMeta) {
+ meta_job_error(NULL, gn, FALSE, status);
+ }
#endif
- gn->made = ERROR;
- if (opts.keepgoing) {
- /* Abort the current target, but let others continue. */
- printf(" (continuing)\n");
- } else {
- printf("\n");
- }
- if (deleteOnError)
- CompatDeleteTarget(gn);
- } else {
- /*
- * Continue executing commands for this target.
- * If we return 0, this will happen...
- */
- printf(" (ignored)\n");
- status = 0;
- }
- }
-
- free(cmdStart);
- compatChild = 0;
- if (compatSigno) {
- bmake_signal(compatSigno, SIG_DFL);
- kill(myPid, compatSigno);
- }
-
- return status;
+ gn->made = ERROR;
+ if (opts.keepgoing) {
+ /*
+ * Abort the current target,
+ * but let others continue.
+ */
+ printf(" (continuing)\n");
+ } else {
+ printf("\n");
+ }
+ if (deleteOnError)
+ CompatDeleteTarget(gn);
+ } else {
+ /*
+ * Continue executing commands for this target.
+ * If we return 0, this will happen...
+ */
+ printf(" (ignored)\n");
+ status = 0;
+ }
+ }
+
+ free(cmdStart);
+ compatChild = 0;
+ if (compatSigno != 0) {
+ bmake_signal(compatSigno, SIG_DFL);
+ kill(myPid, compatSigno);
+ }
+
+ return status;
}
static void
RunCommands(GNode *gn)
{
- StringListNode *ln;
- for (ln = gn->commands->first; ln != NULL; ln = ln->next) {
- const char *cmd = ln->datum;
- if (Compat_RunCommand(cmd, gn) != 0)
- break;
- }
+ StringListNode *ln;
+
+ for (ln = gn->commands.first; ln != NULL; ln = ln->next) {
+ const char *cmd = ln->datum;
+ if (Compat_RunCommand(cmd, gn, ln) != 0)
+ break;
+ }
}
static void
MakeNodes(GNodeList *gnodes, GNode *pgn)
{
- GNodeListNode *ln;
- for (ln = gnodes->first; ln != NULL; ln = ln->next) {
- GNode *cohort = ln->datum;
- Compat_Make(cohort, pgn);
- }
+ GNodeListNode *ln;
+
+ for (ln = gnodes->first; ln != NULL; ln = ln->next) {
+ GNode *cohort = ln->datum;
+ Compat_Make(cohort, pgn);
+ }
}
-/* Make a target.
- *
- * If an error is detected and not being ignored, the process exits.
- *
- * Input:
- * gn The node to make
- * pgn Parent to abort if necessary
- */
-void
-Compat_Make(GNode *gn, GNode *pgn)
+static Boolean
+MakeUnmade(GNode *gn, GNode *pgn)
{
- if (shellName == NULL) /* we came here from jobs */
- Shell_Init();
- if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
+ assert(gn->made == UNMADE);
+
/*
* First mark ourselves to be made, then apply whatever transformations
* the suffix module thinks are necessary. Once that's done, we can
@@ -484,17 +499,20 @@ Compat_Make(GNode *gn, GNode *pgn)
*/
gn->flags |= REMAKE;
gn->made = BEINGMADE;
+
if (!(gn->type & OP_MADE))
- Suff_FindDeps(gn);
- MakeNodes(gn->children, gn);
+ Suff_FindDeps(gn);
+
+ MakeNodes(&gn->children, gn);
+
if (!(gn->flags & REMAKE)) {
- gn->made = ABORTED;
- pgn->flags &= ~(unsigned)REMAKE;
- goto cohorts;
+ gn->made = ABORTED;
+ pgn->flags &= ~(unsigned)REMAKE;
+ return FALSE;
}
- if (Lst_FindDatum(gn->implicitParents, pgn) != NULL)
- Var_Set(IMPSRC, GNode_VarTarget(gn), pgn);
+ if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL)
+ Var_Set(IMPSRC, GNode_VarTarget(gn), pgn);
/*
* All the children were made ok. Now youngestChild->mtime contains the
@@ -504,115 +522,185 @@ Compat_Make(GNode *gn, GNode *pgn)
*/
DEBUG1(MAKE, "Examining %s...", gn->name);
if (!GNode_IsOODate(gn)) {
- gn->made = UPTODATE;
- DEBUG0(MAKE, "up-to-date.\n");
- goto cohorts;
- } else
- DEBUG0(MAKE, "out-of-date.\n");
+ gn->made = UPTODATE;
+ DEBUG0(MAKE, "up-to-date.\n");
+ return FALSE;
+ }
/*
* If the user is just seeing if something is out-of-date, exit now
* to tell him/her "yes".
*/
+ DEBUG0(MAKE, "out-of-date.\n");
if (opts.queryFlag)
- exit(1);
+ exit(1);
/*
- * We need to be re-made. We also have to make sure we've got a $?
- * variable. To be nice, we also define the $> variable using
- * Make_DoAllVar().
+ * We need to be re-made.
+ * Ensure that $? (.OODATE) and $> (.ALLSRC) are both set.
*/
Make_DoAllVar(gn);
/*
* Alter our type to tell if errors should be ignored or things
- * should not be printed so CompatRunCommand knows what to do.
+ * should not be printed so Compat_RunCommand knows what to do.
*/
- if (Targ_Ignore(gn))
- gn->type |= OP_IGNORE;
- if (Targ_Silent(gn))
- gn->type |= OP_SILENT;
+ if (opts.ignoreErrors)
+ gn->type |= OP_IGNORE;
+ if (opts.beSilent)
+ gn->type |= OP_SILENT;
if (Job_CheckCommands(gn, Fatal)) {
- /*
- * Our commands are ok, but we still have to worry about the -t
- * flag...
- */
- if (!opts.touchFlag || (gn->type & OP_MAKE)) {
- curTarg = gn;
+ /*
+ * Our commands are ok, but we still have to worry about
+ * the -t flag.
+ */
+ if (!opts.touchFlag || (gn->type & OP_MAKE)) {
+ curTarg = gn;
#ifdef USE_META
- if (useMeta && GNode_ShouldExecute(gn)) {
- meta_job_start(NULL, gn);
- }
+ if (useMeta && GNode_ShouldExecute(gn)) {
+ meta_job_start(NULL, gn);
+ }
#endif
- RunCommands(gn);
- curTarg = NULL;
- } else {
- Job_Touch(gn, (gn->type & OP_SILENT) != 0);
- }
+ RunCommands(gn);
+ curTarg = NULL;
+ } else {
+ Job_Touch(gn, (gn->type & OP_SILENT) != 0);
+ }
} else {
- gn->made = ERROR;
+ gn->made = ERROR;
}
#ifdef USE_META
if (useMeta && GNode_ShouldExecute(gn)) {
- if (meta_job_finish(NULL) != 0)
- gn->made = ERROR;
+ if (meta_job_finish(NULL) != 0)
+ gn->made = ERROR;
}
#endif
if (gn->made != ERROR) {
- /*
- * If the node was made successfully, mark it so, update
- * its modification time and timestamp all its parents.
- * This is to keep its state from affecting that of its parent.
- */
- gn->made = MADE;
- if (Make_Recheck(gn) == 0)
- pgn->flags |= FORCE;
- if (!(gn->type & OP_EXEC)) {
- pgn->flags |= CHILDMADE;
- GNode_UpdateYoungestChild(pgn, gn);
- }
+ /*
+ * If the node was made successfully, mark it so, update
+ * its modification time and timestamp all its parents.
+ * This is to keep its state from affecting that of its parent.
+ */
+ gn->made = MADE;
+ if (Make_Recheck(gn) == 0)
+ pgn->flags |= FORCE;
+ if (!(gn->type & OP_EXEC)) {
+ pgn->flags |= CHILDMADE;
+ GNode_UpdateYoungestChild(pgn, gn);
+ }
} else if (opts.keepgoing) {
- pgn->flags &= ~(unsigned)REMAKE;
+ pgn->flags &= ~(unsigned)REMAKE;
} else {
- PrintOnError(gn, "\nStop.");
- exit(1);
- }
- } else if (gn->made == ERROR) {
- /* Already had an error when making this. Tell the parent to abort. */
- pgn->flags &= ~(unsigned)REMAKE;
- } else {
- if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) {
- const char *target = GNode_VarTarget(gn);
- Var_Set(IMPSRC, target != NULL ? target : "", pgn);
- }
- switch(gn->made) {
- case BEINGMADE:
+ PrintOnError(gn, "\nStop.");
+ exit(1);
+ }
+ return TRUE;
+}
+
+static void
+MakeOther(GNode *gn, GNode *pgn)
+{
+
+ if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) {
+ const char *target = GNode_VarTarget(gn);
+ Var_Set(IMPSRC, target != NULL ? target : "", pgn);
+ }
+
+ switch (gn->made) {
+ case BEINGMADE:
Error("Graph cycles through %s", gn->name);
gn->made = ERROR;
pgn->flags &= ~(unsigned)REMAKE;
break;
- case MADE:
+ case MADE:
if (!(gn->type & OP_EXEC)) {
- pgn->flags |= CHILDMADE;
- GNode_UpdateYoungestChild(pgn, gn);
+ pgn->flags |= CHILDMADE;
+ GNode_UpdateYoungestChild(pgn, gn);
}
break;
- case UPTODATE:
+ case UPTODATE:
if (!(gn->type & OP_EXEC))
- GNode_UpdateYoungestChild(pgn, gn);
+ GNode_UpdateYoungestChild(pgn, gn);
break;
- default:
+ default:
break;
}
- }
+}
+
+/*
+ * Make a target.
+ *
+ * If an error is detected and not being ignored, the process exits.
+ *
+ * Input:
+ * gn The node to make
+ * pgn Parent to abort if necessary
+ *
+ * Output:
+ * gn->made
+ * UPTODATE gn was already up-to-date.
+ * MADE gn was recreated successfully.
+ * ERROR An error occurred while gn was being created,
+ * either due to missing commands or in -k mode.
+ * ABORTED gn was not remade because one of its
+ * dependencies could not be made due to errors.
+ */
+void
+Compat_Make(GNode *gn, GNode *pgn)
+{
+ if (shellName == NULL) /* we came here from jobs */
+ Shell_Init();
+
+ if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
+ if (!MakeUnmade(gn, pgn))
+ goto cohorts;
+
+ /* XXX: Replace with GNode_IsError(gn) */
+ } else if (gn->made == ERROR) {
+ /*
+ * Already had an error when making this.
+ * Tell the parent to abort.
+ */
+ pgn->flags &= ~(unsigned)REMAKE;
+ } else {
+ MakeOther(gn, pgn);
+ }
cohorts:
- MakeNodes(gn->cohorts, pgn);
+ MakeNodes(&gn->cohorts, pgn);
+}
+
+static void
+MakeBeginNode(void)
+{
+ GNode *gn = Targ_FindNode(".BEGIN");
+ if (gn == NULL)
+ return;
+
+ Compat_Make(gn, gn);
+ if (GNode_IsError(gn)) {
+ PrintOnError(gn, "\nStop.");
+ exit(1);
+ }
+}
+
+static void
+InitSignals(void)
+{
+ if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN)
+ bmake_signal(SIGINT, CompatInterrupt);
+ if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN)
+ bmake_signal(SIGTERM, CompatInterrupt);
+ if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN)
+ bmake_signal(SIGHUP, CompatInterrupt);
+ if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN)
+ bmake_signal(SIGQUIT, CompatInterrupt);
}
-/* Initialize this module and start making.
+/*
+ * Initialize this module and start making.
*
* Input:
* targs The target nodes to re-create
@@ -620,79 +708,51 @@ cohorts:
void
Compat_Run(GNodeList *targs)
{
- GNode *gn = NULL; /* Current root target */
- int errors; /* Number of targets not remade due to errors */
-
- if (!shellName)
- Shell_Init();
-
- if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN)
- bmake_signal(SIGINT, CompatInterrupt);
- if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN)
- bmake_signal(SIGTERM, CompatInterrupt);
- if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN)
- bmake_signal(SIGHUP, CompatInterrupt);
- if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN)
- bmake_signal(SIGQUIT, CompatInterrupt);
-
- /* Create the .END node now, to keep the (debug) output of the
- * counter.mk test the same as before 2020-09-23. This implementation
- * detail probably doesn't matter though. */
- (void)Targ_GetEndNode();
- /*
- * If the user has defined a .BEGIN target, execute the commands attached
- * to it.
- */
- if (!opts.queryFlag) {
- gn = Targ_FindNode(".BEGIN");
- if (gn != NULL) {
- Compat_Make(gn, gn);
- if (gn->made == ERROR) {
- PrintOnError(gn, "\nStop.");
- exit(1);
- }
- }
- }
-
- /*
- * Expand .USE nodes right now, because they can modify the structure
- * of the tree.
- */
- Make_ExpandUse(targs);
-
- /*
- * For each entry in the list of targets to create, call Compat_Make on
- * it to create the thing. Compat_Make will leave the 'made' field of gn
- * in one of several states:
- * UPTODATE gn was already up-to-date
- * MADE gn was recreated successfully
- * ERROR An error occurred while gn was being created
- * ABORTED gn was not remade because one of its inferiors
- * could not be made due to errors.
- */
- errors = 0;
- while (!Lst_IsEmpty(targs)) {
- gn = Lst_Dequeue(targs);
- Compat_Make(gn, gn);
+ GNode *errorNode = NULL;
+
+ if (shellName == NULL)
+ Shell_Init();
- if (gn->made == UPTODATE) {
- printf("`%s' is up to date.\n", gn->name);
- } else if (gn->made == ABORTED) {
- printf("`%s' not remade because of errors.\n", gn->name);
- errors++;
- }
- }
-
- /*
- * If the user has defined a .END target, run its commands.
- */
- if (errors == 0) {
- GNode *endNode = Targ_GetEndNode();
- Compat_Make(endNode, endNode);
- /* XXX: Did you mean endNode->made instead of gn->made? */
- if (gn->made == ERROR) {
- PrintOnError(gn, "\nStop.");
- exit(1);
- }
- }
+ InitSignals();
+
+ /* Create the .END node now, to keep the (debug) output of the
+ * counter.mk test the same as before 2020-09-23. This implementation
+ * detail probably doesn't matter though. */
+ (void)Targ_GetEndNode();
+
+ if (!opts.queryFlag)
+ MakeBeginNode();
+
+ /*
+ * Expand .USE nodes right now, because they can modify the structure
+ * of the tree.
+ */
+ Make_ExpandUse(targs);
+
+ while (!Lst_IsEmpty(targs)) {
+ GNode *gn = Lst_Dequeue(targs);
+ Compat_Make(gn, gn);
+
+ if (gn->made == UPTODATE) {
+ printf("`%s' is up to date.\n", gn->name);
+ } else if (gn->made == ABORTED) {
+ printf("`%s' not remade because of errors.\n",
+ gn->name);
+ }
+ if (GNode_IsError(gn) && errorNode == NULL)
+ errorNode = gn;
+ }
+
+ /* If the user has defined a .END target, run its commands. */
+ if (errorNode == NULL) {
+ GNode *endNode = Targ_GetEndNode();
+ Compat_Make(endNode, endNode);
+ if (GNode_IsError(endNode))
+ errorNode = endNode;
+ }
+
+ if (errorNode != NULL) {
+ PrintOnError(errorNode, "\nStop.");
+ exit(1);
+ }
}
diff --git a/cond.c b/cond.c
index a1b0d75dcb09..1a8aba637fe9 100644
--- a/cond.c
+++ b/cond.c
@@ -1,4 +1,4 @@
-/* $NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $ */
+/* $NetBSD: cond.c,v 1.235 2021/01/10 21:20:46 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -69,7 +69,8 @@
* SUCH DAMAGE.
*/
-/* Handling of conditionals in a makefile.
+/*
+ * Handling of conditionals in a makefile.
*
* Interface:
* Cond_EvalLine Evaluate the conditional directive, such as
@@ -94,7 +95,7 @@
#include "dir.h"
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $");
+MAKE_RCSID("$NetBSD: cond.c,v 1.235 2021/01/10 21:20:46 rillig Exp $");
/*
* The parsing of conditional expressions is based on this grammar:
@@ -135,20 +136,22 @@ MAKE_RCSID("$NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $");
* CondParser_Term) return either TOK_FALSE, TOK_TRUE, or TOK_ERROR on error.
*/
typedef enum Token {
- TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT,
- TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
+ TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT,
+ TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
} Token;
typedef struct CondParser {
- const struct If *if_info; /* Info for current statement */
- const char *p; /* The remaining condition to parse */
- Token curr; /* Single push-back token used in parsing */
-
- /* Whether an error message has already been printed for this condition.
- * The first available error message is usually the most specific one,
- * therefore it makes sense to suppress the standard "Malformed
- * conditional" message. */
- Boolean printedError;
+ const struct If *if_info; /* Info for current statement */
+ const char *p; /* The remaining condition to parse */
+ Token curr; /* Single push-back token used in parsing */
+
+ /*
+ * Whether an error message has already been printed for this
+ * condition. The first available error message is usually the most
+ * specific one, therefore it makes sense to suppress the standard
+ * "Malformed conditional" message.
+ */
+ Boolean printedError;
} CondParser;
static Token CondParser_Expr(CondParser *par, Boolean);
@@ -168,35 +171,36 @@ static unsigned int cond_min_depth = 0; /* depth at makefile open */
*/
static Boolean lhsStrict;
-static int
+static Boolean
is_token(const char *str, const char *tok, size_t len)
{
- return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]);
+ return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]);
}
static Token
ToToken(Boolean cond)
{
- return cond ? TOK_TRUE : TOK_FALSE;
+ return cond ? TOK_TRUE : TOK_FALSE;
}
/* Push back the most recent token read. We only need one level of this. */
static void
CondParser_PushBack(CondParser *par, Token t)
{
- assert(par->curr == TOK_NONE);
- assert(t != TOK_NONE);
+ assert(par->curr == TOK_NONE);
+ assert(t != TOK_NONE);
- par->curr = t;
+ par->curr = t;
}
static void
CondParser_SkipWhitespace(CondParser *par)
{
- cpp_skip_whitespace(&par->p);
+ cpp_skip_whitespace(&par->p);
}
-/* Parse the argument of a built-in function.
+/*
+ * Parse the argument of a built-in function.
*
* Arguments:
* *pp initially points at the '(',
@@ -207,128 +211,139 @@ CondParser_SkipWhitespace(CondParser *par)
* func says whether the argument belongs to an actual function, or
* whether the parsed argument is passed to the default function.
*
- * Return the length of the argument, or 0 on error. */
+ * Return the length of the argument, or 0 on error.
+ */
static size_t
ParseFuncArg(const char **pp, Boolean doEval, const char *func,
- char **out_arg) {
- const char *p = *pp;
- Buffer argBuf;
- int paren_depth;
- size_t argLen;
-
- if (func != NULL)
- p++; /* Skip opening '(' - verified by caller */
-
- if (*p == '\0') {
- *out_arg = NULL; /* Missing closing parenthesis: */
- return 0; /* .if defined( */
- }
-
- cpp_skip_hspace(&p);
-
- Buf_InitSize(&argBuf, 16);
-
- paren_depth = 0;
- for (;;) {
- char ch = *p;
- if (ch == '\0' || ch == ' ' || ch == '\t')
- break;
- if ((ch == '&' || ch == '|') && paren_depth == 0)
- break;
- if (*p == '$') {
- /*
- * Parse the variable spec and install it as part of the argument
- * if it's valid. We tell Var_Parse to complain on an undefined
- * variable, so we don't need to do it. Nor do we return an error,
- * though perhaps we should...
- */
- void *nestedVal_freeIt;
- VarEvalFlags eflags = doEval ? VARE_WANTRES | VARE_UNDEFERR
- : VARE_NONE;
- const char *nestedVal;
- (void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal,
- &nestedVal_freeIt);
- /* TODO: handle errors */
- Buf_AddStr(&argBuf, nestedVal);
- free(nestedVal_freeIt);
- continue;
+ char **out_arg)
+{
+ const char *p = *pp;
+ Buffer argBuf;
+ int paren_depth;
+ size_t argLen;
+
+ if (func != NULL)
+ p++; /* Skip opening '(' - verified by caller */
+
+ if (*p == '\0') {
+ *out_arg = NULL; /* Missing closing parenthesis: */
+ return 0; /* .if defined( */
}
- if (ch == '(')
- paren_depth++;
- else if (ch == ')' && --paren_depth < 0)
- break;
- Buf_AddByte(&argBuf, *p);
- p++;
- }
-
- *out_arg = Buf_GetAll(&argBuf, &argLen);
- Buf_Destroy(&argBuf, FALSE);
-
- cpp_skip_hspace(&p);
-
- if (func != NULL && *p++ != ')') {
- Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
- func);
- /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
- return 0;
- }
-
- *pp = p;
- return argLen;
+
+ cpp_skip_hspace(&p);
+
+ Buf_InitSize(&argBuf, 16);
+
+ paren_depth = 0;
+ for (;;) {
+ char ch = *p;
+ if (ch == '\0' || ch == ' ' || ch == '\t')
+ break;
+ if ((ch == '&' || ch == '|') && paren_depth == 0)
+ break;
+ if (*p == '$') {
+ /*
+ * Parse the variable expression and install it as
+ * part of the argument if it's valid. We tell
+ * Var_Parse to complain on an undefined variable,
+ * (XXX: but Var_Parse ignores that request)
+ * so we don't need to do it. Nor do we return an
+ * error, though perhaps we should.
+ */
+ VarEvalFlags eflags = doEval
+ ? VARE_WANTRES | VARE_UNDEFERR
+ : VARE_NONE;
+ FStr nestedVal;
+ (void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal);
+ /* TODO: handle errors */
+ Buf_AddStr(&argBuf, nestedVal.str);
+ FStr_Done(&nestedVal);
+ continue;
+ }
+ if (ch == '(')
+ paren_depth++;
+ else if (ch == ')' && --paren_depth < 0)
+ break;
+ Buf_AddByte(&argBuf, *p);
+ p++;
+ }
+
+ *out_arg = Buf_GetAll(&argBuf, &argLen);
+ Buf_Destroy(&argBuf, FALSE);
+
+ cpp_skip_hspace(&p);
+
+ if (func != NULL && *p++ != ')') {
+ Parse_Error(PARSE_WARNING,
+ "Missing closing parenthesis for %s()",
+ func);
+ /* The PARSE_FATAL follows in CondEvalExpression. */
+ return 0;
+ }
+
+ *pp = p;
+ return argLen;
}
/* Test whether the given variable is defined. */
+/*ARGSUSED*/
static Boolean
FuncDefined(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
- void *freeIt;
- Boolean result = Var_Value(arg, VAR_CMDLINE, &freeIt) != NULL;
- bmake_free(freeIt);
- return result;
+ FStr value = Var_Value(arg, VAR_CMDLINE);
+ Boolean result = value.str != NULL;
+ FStr_Done(&value);
+ return result;
}
/* See if the given target is being made. */
+/*ARGSUSED*/
static Boolean
FuncMake(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
- StringListNode *ln;
+ StringListNode *ln;
- for (ln = opts.create->first; ln != NULL; ln = ln->next)
- if (Str_Match(ln->datum, arg))
- return TRUE;
- return FALSE;
+ for (ln = opts.create.first; ln != NULL; ln = ln->next)
+ if (Str_Match(ln->datum, arg))
+ return TRUE;
+ return FALSE;
}
/* See if the given file exists. */
+/*ARGSUSED*/
static Boolean
FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
- Boolean result;
- char *path;
-
- path = Dir_FindFile(arg, dirSearchPath);
- DEBUG2(COND, "exists(%s) result is \"%s\"\n",
- arg, path != NULL ? path : "");
- result = path != NULL;
- free(path);
- return result;
+ Boolean result;
+ char *path;
+
+ path = Dir_FindFile(arg, &dirSearchPath);
+ DEBUG2(COND, "exists(%s) result is \"%s\"\n",
+ arg, path != NULL ? path : "");
+ result = path != NULL;
+ free(path);
+ return result;
}
/* See if the given node exists and is an actual target. */
+/*ARGSUSED*/
static Boolean
FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
- GNode *gn = Targ_FindNode(arg);
- return gn != NULL && GNode_IsTarget(gn);
+ GNode *gn = Targ_FindNode(arg);
+ return gn != NULL && GNode_IsTarget(gn);
}
-/* See if the given node exists and is an actual target with commands
- * associated with it. */
+/*
+ * See if the given node exists and is an actual target with commands
+ * associated with it.
+ */
+/*ARGSUSED*/
static Boolean
FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
- GNode *gn = Targ_FindNode(arg);
- return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands);
+ GNode *gn = Targ_FindNode(arg);
+ return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands);
}
/*
@@ -343,36 +358,36 @@ FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
static Boolean
TryParseNumber(const char *str, double *out_value)
{
- char *end;
- unsigned long ul_val;
- double dbl_val;
-
- errno = 0;
- if (str[0] == '\0') { /* XXX: why is an empty string a number? */
- *out_value = 0.0;
- return TRUE;
- }
+ char *end;
+ unsigned long ul_val;
+ double dbl_val;
+
+ errno = 0;
+ if (str[0] == '\0') { /* XXX: why is an empty string a number? */
+ *out_value = 0.0;
+ return TRUE;
+ }
- ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10);
- if (*end == '\0' && errno != ERANGE) {
- *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val;
- return TRUE;
- }
+ ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10);
+ if (*end == '\0' && errno != ERANGE) {
+ *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val;
+ return TRUE;
+ }
- if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E')
- return FALSE; /* skip the expensive strtod call */
- dbl_val = strtod(str, &end);
- if (*end != '\0')
- return FALSE;
+ if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E')
+ return FALSE; /* skip the expensive strtod call */
+ dbl_val = strtod(str, &end);
+ if (*end != '\0')
+ return FALSE;
- *out_value = dbl_val;
- return TRUE;
+ *out_value = dbl_val;
+ return TRUE;
}
static Boolean
is_separator(char ch)
{
- return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL;
+ return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL;
}
/*-
@@ -385,124 +400,126 @@ is_separator(char ch)
* Sets out_freeIt.
*/
/* coverity:[+alloc : arg-*4] */
-static const char *
+static void
CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
- Boolean *out_quoted, void **out_freeIt)
+ FStr *out_str, Boolean *out_quoted)
{
- Buffer buf;
- const char *str;
- Boolean atStart;
- const char *nested_p;
- Boolean quoted;
- const char *start;
- VarEvalFlags eflags;
- VarParseResult parseResult;
-
- Buf_Init(&buf);
- str = NULL;
- *out_freeIt = NULL;
- *out_quoted = quoted = par->p[0] == '"';
- start = par->p;
- if (quoted)
- par->p++;
- while (par->p[0] != '\0' && str == NULL) {
- switch (par->p[0]) {
- case '\\':
- par->p++;
- if (par->p[0] != '\0') {
- Buf_AddByte(&buf, par->p[0]);
+ Buffer buf;
+ FStr str;
+ Boolean atStart;
+ const char *nested_p;
+ Boolean quoted;
+ const char *start;
+ VarEvalFlags eflags;
+ VarParseResult parseResult;
+
+ Buf_Init(&buf);
+ str = FStr_InitRefer(NULL);
+ *out_quoted = quoted = par->p[0] == '"';
+ start = par->p;
+ if (quoted)
par->p++;
- }
- continue;
- case '"':
- if (quoted) {
- par->p++; /* skip the closing quote */
- goto got_str;
- }
- Buf_AddByte(&buf, par->p[0]); /* likely? */
- par->p++;
- continue;
- case ')': /* see is_separator */
- case '!':
- case '=':
- case '>':
- case '<':
- case ' ':
- case '\t':
- if (!quoted)
- goto got_str;
- Buf_AddByte(&buf, par->p[0]);
- par->p++;
- continue;
- case '$':
- /* if we are in quotes, an undefined variable is ok */
- eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR :
- doEval ? VARE_WANTRES :
- VARE_NONE;
-
- nested_p = par->p;
- atStart = nested_p == start;
- parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str,
- out_freeIt);
- /* TODO: handle errors */
- if (str == var_Error) {
- if (parseResult & VPR_ANY_MSG)
- par->printedError = TRUE;
- if (*out_freeIt != NULL) {
- /* XXX: Can there be any situation in which a returned
- * var_Error requires freeIt? */
- free(*out_freeIt);
- *out_freeIt = NULL;
+
+ while (par->p[0] != '\0' && str.str == NULL) {
+ switch (par->p[0]) {
+ case '\\':
+ par->p++;
+ if (par->p[0] != '\0') {
+ Buf_AddByte(&buf, par->p[0]);
+ par->p++;
+ }
+ continue;
+ case '"':
+ if (quoted) {
+ par->p++; /* skip the closing quote */
+ goto got_str;
+ }
+ Buf_AddByte(&buf, par->p[0]); /* likely? */
+ par->p++;
+ continue;
+ case ')': /* see is_separator */
+ case '!':
+ case '=':
+ case '>':
+ case '<':
+ case ' ':
+ case '\t':
+ if (!quoted)
+ goto got_str;
+ Buf_AddByte(&buf, par->p[0]);
+ par->p++;
+ continue;
+ case '$':
+ /* if we are in quotes, an undefined variable is ok */
+ eflags =
+ doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR :
+ doEval ? VARE_WANTRES :
+ VARE_NONE;
+
+ nested_p = par->p;
+ atStart = nested_p == start;
+ parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags,
+ &str);
+ /* TODO: handle errors */
+ if (str.str == var_Error) {
+ if (parseResult == VPR_ERR)
+ par->printedError = TRUE;
+ /*
+ * XXX: Can there be any situation in which
+ * a returned var_Error requires freeIt?
+ */
+ FStr_Done(&str);
+ /*
+ * Even if !doEval, we still report syntax
+ * errors, which is what getting var_Error
+ * back with !doEval means.
+ */
+ str = FStr_InitRefer(NULL);
+ goto cleanup;
+ }
+ par->p = nested_p;
+
+ /*
+ * If the '$' started the string literal (which means
+ * no quotes), and the variable expression is followed
+ * by a space, looks like a comparison operator or is
+ * the end of the expression, we are done.
+ */
+ if (atStart && is_separator(par->p[0]))
+ goto cleanup;
+
+ Buf_AddStr(&buf, str.str);
+ FStr_Done(&str);
+ str = FStr_InitRefer(NULL); /* not finished yet */
+ continue;
+ default:
+ if (strictLHS && !quoted && *start != '$' &&
+ !ch_isdigit(*start)) {
+ /*
+ * The left-hand side must be quoted,
+ * a variable reference or a number.
+ */
+ str = FStr_InitRefer(NULL);
+ goto cleanup;
+ }
+ Buf_AddByte(&buf, par->p[0]);
+ par->p++;
+ continue;
}
- /*
- * Even if !doEval, we still report syntax errors, which
- * is what getting var_Error back with !doEval means.
- */
- str = NULL;
- goto cleanup;
- }
- par->p = nested_p;
-
- /*
- * If the '$' started the string literal (which means no quotes),
- * and the variable expression is followed by a space, looks like
- * a comparison operator or is the end of the expression, we are
- * done.
- */
- if (atStart && is_separator(par->p[0]))
- goto cleanup;
-
- Buf_AddStr(&buf, str);
- if (*out_freeIt) {
- free(*out_freeIt);
- *out_freeIt = NULL;
- }
- str = NULL; /* not finished yet */
- continue;
- default:
- if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) {
- /* lhs must be quoted, a variable reference or number */
- str = NULL;
- goto cleanup;
- }
- Buf_AddByte(&buf, par->p[0]);
- par->p++;
- continue;
}
- }
got_str:
- *out_freeIt = Buf_GetAll(&buf, NULL);
- str = *out_freeIt;
+ str = FStr_InitOwn(Buf_GetAll(&buf, NULL));
cleanup:
- Buf_Destroy(&buf, FALSE);
- return str;
+ Buf_Destroy(&buf, FALSE);
+ *out_str = str;
}
struct If {
- const char *form; /* Form of if */
- size_t formlen; /* Length of form */
- Boolean doNot; /* TRUE if default function should be negated */
- Boolean (*defProc)(size_t, const char *); /* Default function to apply */
+ const char *form; /* Form of if */
+ size_t formlen; /* Length of form */
+ Boolean doNot; /* TRUE if default function should be negated */
+ /* The default function to apply on unquoted bare words. */
+ Boolean (*defProc)(size_t, const char *);
};
/* The different forms of .if directives. */
@@ -514,82 +531,87 @@ static const struct If ifs[] = {
{ "", 0, FALSE, FuncDefined },
{ NULL, 0, FALSE, NULL }
};
-enum { PLAIN_IF_INDEX = 4 };
+enum {
+ PLAIN_IF_INDEX = 4
+};
static Boolean
If_Eval(const struct If *if_info, const char *arg, size_t arglen)
{
- Boolean res = if_info->defProc(arglen, arg);
- return if_info->doNot ? !res : res;
+ Boolean res = if_info->defProc(arglen, arg);
+ return if_info->doNot ? !res : res;
}
-/* Evaluate a "comparison without operator", such as in ".if ${VAR}" or
- * ".if 0". */
+/*
+ * Evaluate a "comparison without operator", such as in ".if ${VAR}" or
+ * ".if 0".
+ */
static Boolean
EvalNotEmpty(CondParser *par, const char *value, Boolean quoted)
{
- double num;
+ double num;
- /* For .ifxxx "...", check for non-empty string. */
- if (quoted)
- return value[0] != '\0';
+ /* For .ifxxx "...", check for non-empty string. */
+ if (quoted)
+ return value[0] != '\0';
- /* For .ifxxx <number>, compare against zero */
- if (TryParseNumber(value, &num))
- return num != 0.0;
+ /* For .ifxxx <number>, compare against zero */
+ if (TryParseNumber(value, &num))
+ return num != 0.0;
- /* For .if ${...}, check for non-empty string. This is different from
- * the evaluation function from that .if variant, which would test
- * whether a variable of the given name were defined. */
- /* XXX: Whitespace should count as empty, just as in ParseEmptyArg. */
- if (par->if_info->form[0] == '\0')
- return value[0] != '\0';
+ /* For .if ${...}, check for non-empty string. This is different from
+ * the evaluation function from that .if variant, which would test
+ * whether a variable of the given name were defined. */
+ /* XXX: Whitespace should count as empty, just as in ParseEmptyArg. */
+ if (par->if_info->form[0] == '\0')
+ return value[0] != '\0';
- /* For the other variants of .ifxxx ${...}, use its default function. */
- return If_Eval(par->if_info, value, strlen(value));
+ /* For the other variants of .ifxxx ${...}, use its default function. */
+ return If_Eval(par->if_info, value, strlen(value));
}
/* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */
static Token
EvalCompareNum(double lhs, const char *op, double rhs)
{
- DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, op);
-
- switch (op[0]) {
- case '!':
- if (op[1] != '=') {
- Parse_Error(PARSE_WARNING, "Unknown operator");
- /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
- return TOK_ERROR;
- }
- return ToToken(lhs != rhs);
- case '=':
- if (op[1] != '=') {
- Parse_Error(PARSE_WARNING, "Unknown operator");
- /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
- return TOK_ERROR;
+ DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, op);
+
+ switch (op[0]) {
+ case '!':
+ if (op[1] != '=') {
+ Parse_Error(PARSE_WARNING, "Unknown operator");
+ /* The PARSE_FATAL follows in CondEvalExpression. */
+ return TOK_ERROR;
+ }
+ return ToToken(lhs != rhs);
+ case '=':
+ if (op[1] != '=') {
+ Parse_Error(PARSE_WARNING, "Unknown operator");
+ /* The PARSE_FATAL follows in CondEvalExpression. */
+ return TOK_ERROR;
+ }
+ return ToToken(lhs == rhs);
+ case '<':
+ return ToToken(op[1] == '=' ? lhs <= rhs : lhs < rhs);
+ case '>':
+ return ToToken(op[1] == '=' ? lhs >= rhs : lhs > rhs);
}
- return ToToken(lhs == rhs);
- case '<':
- return ToToken(op[1] == '=' ? lhs <= rhs : lhs < rhs);
- case '>':
- return ToToken(op[1] == '=' ? lhs >= rhs : lhs > rhs);
- }
- return TOK_ERROR;
+ return TOK_ERROR;
}
static Token
EvalCompareStr(const char *lhs, const char *op, const char *rhs)
{
- if (!((op[0] == '!' || op[0] == '=') && op[1] == '=')) {
- Parse_Error(PARSE_WARNING,
- "String comparison operator must be either == or !=");
- /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
- return TOK_ERROR;
- }
+ if (!((op[0] == '!' || op[0] == '=') && op[1] == '=')) {
+ Parse_Error(PARSE_WARNING,
+ "String comparison operator "
+ "must be either == or !=");
+ /* The PARSE_FATAL follows in CondEvalExpression. */
+ return TOK_ERROR;
+ }
- DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op);
- return ToToken((*op == '=') == (strcmp(lhs, rhs) == 0));
+ DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op);
+ return ToToken((*op == '=') == (strcmp(lhs, rhs) == 0));
}
/* Evaluate a comparison, such as "${VAR} == 12345". */
@@ -597,16 +619,17 @@ static Token
EvalCompare(const char *lhs, Boolean lhsQuoted, const char *op,
const char *rhs, Boolean rhsQuoted)
{
- double left, right;
+ double left, right;
- if (!rhsQuoted && !lhsQuoted)
- if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right))
- return EvalCompareNum(left, op, right);
+ if (!rhsQuoted && !lhsQuoted)
+ if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right))
+ return EvalCompareNum(left, op, right);
- return EvalCompareStr(lhs, op, rhs);
+ return EvalCompareStr(lhs, op, rhs);
}
-/* Parse a comparison condition such as:
+/*
+ * Parse a comparison condition such as:
*
* 0
* ${VAR:Mpattern}
@@ -616,266 +639,277 @@ EvalCompare(const char *lhs, Boolean lhsQuoted, const char *op,
static Token
CondParser_Comparison(CondParser *par, Boolean doEval)
{
- Token t = TOK_ERROR;
- const char *lhs, *op, *rhs;
- void *lhs_freeIt, *rhs_freeIt;
- Boolean lhsQuoted, rhsQuoted;
-
- /*
- * Parse the variable spec and skip over it, saving its
- * value in lhs.
- */
- lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhs_freeIt);
- if (lhs == NULL)
- goto done_lhs;
-
- CondParser_SkipWhitespace(par);
-
- op = par->p;
- switch (par->p[0]) {
- case '!':
- case '=':
- case '<':
- case '>':
- if (par->p[1] == '=')
- par->p += 2;
- else
- par->p++;
- break;
- default:
- /* Unknown operator, compare against an empty string or 0. */
- t = ToToken(doEval && EvalNotEmpty(par, lhs, lhsQuoted));
- goto done_lhs;
- }
-
- CondParser_SkipWhitespace(par);
-
- if (par->p[0] == '\0') {
- Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator");
- /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
- goto done_lhs;
- }
-
- rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhs_freeIt);
- if (rhs == NULL)
- goto done_rhs;
-
- if (!doEval) {
- t = TOK_FALSE;
- goto done_rhs;
- }
-
- t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted);
+ Token t = TOK_ERROR;
+ FStr lhs, rhs;
+ const char *op;
+ Boolean lhsQuoted, rhsQuoted;
+
+ /*
+ * Parse the variable spec and skip over it, saving its
+ * value in lhs.
+ */
+ CondParser_String(par, doEval, lhsStrict, &lhs, &lhsQuoted);
+ if (lhs.str == NULL)
+ goto done_lhs;
+
+ CondParser_SkipWhitespace(par);
+
+ op = par->p;
+ switch (par->p[0]) {
+ case '!':
+ case '=':
+ case '<':
+ case '>':
+ if (par->p[1] == '=')
+ par->p += 2;
+ else
+ par->p++;
+ break;
+ default:
+ /* Unknown operator, compare against an empty string or 0. */
+ t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted));
+ goto done_lhs;
+ }
+
+ CondParser_SkipWhitespace(par);
+
+ if (par->p[0] == '\0') {
+ Parse_Error(PARSE_WARNING,
+ "Missing right-hand-side of operator");
+ /* The PARSE_FATAL follows in CondEvalExpression. */
+ goto done_lhs;
+ }
+
+ CondParser_String(par, doEval, FALSE, &rhs, &rhsQuoted);
+ if (rhs.str == NULL)
+ goto done_rhs;
+
+ if (!doEval) {
+ t = TOK_FALSE;
+ goto done_rhs;
+ }
+
+ t = EvalCompare(lhs.str, lhsQuoted, op, rhs.str, rhsQuoted);
done_rhs:
- free(rhs_freeIt);
+ FStr_Done(&rhs);
done_lhs:
- free(lhs_freeIt);
- return t;
+ FStr_Done(&lhs);
+ return t;
}
-/* The argument to empty() is a variable name, optionally followed by
- * variable modifiers. */
+/*
+ * The argument to empty() is a variable name, optionally followed by
+ * variable modifiers.
+ */
+/*ARGSUSED*/
static size_t
ParseEmptyArg(const char **pp, Boolean doEval,
const char *func MAKE_ATTR_UNUSED, char **out_arg)
{
- void *val_freeIt;
- const char *val;
- size_t magic_res;
-
- /* We do all the work here and return the result as the length */
- *out_arg = NULL;
-
- (*pp)--; /* Make (*pp)[1] point to the '('. */
- (void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE,
- &val, &val_freeIt);
- /* TODO: handle errors */
- /* If successful, *pp points beyond the closing ')' now. */
-
- if (val == var_Error) {
- free(val_freeIt);
- return (size_t)-1;
- }
-
- /* A variable is empty when it just contains spaces... 4/15/92, christos */
- cpp_skip_whitespace(&val);
-
- /*
- * For consistency with the other functions we can't generate the
- * true/false here.
- */
- magic_res = *val != '\0' ? 2 : 1;
- free(val_freeIt);
- return magic_res;
+ FStr val;
+ size_t magic_res;
+
+ /* We do all the work here and return the result as the length */
+ *out_arg = NULL;
+
+ (*pp)--; /* Make (*pp)[1] point to the '('. */
+ (void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE,
+ &val);
+ /* TODO: handle errors */
+ /* If successful, *pp points beyond the closing ')' now. */
+
+ if (val.str == var_Error) {
+ FStr_Done(&val);
+ return (size_t)-1;
+ }
+
+ /*
+ * A variable is empty when it just contains spaces...
+ * 4/15/92, christos
+ */
+ cpp_skip_whitespace(&val.str);
+
+ /*
+ * For consistency with the other functions we can't generate the
+ * true/false here.
+ */
+ magic_res = val.str[0] != '\0' ? 2 : 1;
+ FStr_Done(&val);
+ return magic_res;
}
+/*ARGSUSED*/
static Boolean
FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED)
{
- /* Magic values ahead, see ParseEmptyArg. */
- return arglen == 1;
+ /* Magic values ahead, see ParseEmptyArg. */
+ return arglen == 1;
}
static Boolean
CondParser_Func(CondParser *par, Boolean doEval, Token *out_token)
{
- static const struct fn_def {
- const char *fn_name;
- size_t fn_name_len;
- size_t (*fn_parse)(const char **, Boolean, const char *, char **);
- Boolean (*fn_eval)(size_t, const char *);
- } fns[] = {
- { "defined", 7, ParseFuncArg, FuncDefined },
- { "make", 4, ParseFuncArg, FuncMake },
- { "exists", 6, ParseFuncArg, FuncExists },
- { "empty", 5, ParseEmptyArg, FuncEmpty },
- { "target", 6, ParseFuncArg, FuncTarget },
- { "commands", 8, ParseFuncArg, FuncCommands }
- };
- const struct fn_def *fn;
- char *arg = NULL;
- size_t arglen;
- const char *cp = par->p;
- const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0];
-
- for (fn = fns; fn != fns_end; fn++) {
- if (!is_token(cp, fn->fn_name, fn->fn_name_len))
- continue;
-
- cp += fn->fn_name_len;
- cpp_skip_whitespace(&cp);
- if (*cp != '(')
- break;
-
- arglen = fn->fn_parse(&cp, doEval, fn->fn_name, &arg);
- if (arglen == 0 || arglen == (size_t)-1) {
- par->p = cp;
- *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR;
- return TRUE;
- }
+ static const struct fn_def {
+ const char *fn_name;
+ size_t fn_name_len;
+ size_t (*fn_parse)(const char **, Boolean, const char *,
+ char **);
+ Boolean (*fn_eval)(size_t, const char *);
+ } fns[] = {
+ { "defined", 7, ParseFuncArg, FuncDefined },
+ { "make", 4, ParseFuncArg, FuncMake },
+ { "exists", 6, ParseFuncArg, FuncExists },
+ { "empty", 5, ParseEmptyArg, FuncEmpty },
+ { "target", 6, ParseFuncArg, FuncTarget },
+ { "commands", 8, ParseFuncArg, FuncCommands }
+ };
+ const struct fn_def *fn;
+ char *arg = NULL;
+ size_t arglen;
+ const char *cp = par->p;
+ const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0];
+
+ for (fn = fns; fn != fns_end; fn++) {
+ if (!is_token(cp, fn->fn_name, fn->fn_name_len))
+ continue;
+
+ cp += fn->fn_name_len;
+ cpp_skip_whitespace(&cp);
+ if (*cp != '(')
+ break;
+
+ arglen = fn->fn_parse(&cp, doEval, fn->fn_name, &arg);
+ if (arglen == 0 || arglen == (size_t)-1) {
+ par->p = cp;
+ *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR;
+ return TRUE;
+ }
- /* Evaluate the argument using the required function. */
- *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg));
- free(arg);
- par->p = cp;
- return TRUE;
- }
+ /* Evaluate the argument using the required function. */
+ *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg));
+ free(arg);
+ par->p = cp;
+ return TRUE;
+ }
- return FALSE;
+ return FALSE;
}
-/* Parse a function call, a number, a variable expression or a string
- * literal. */
+/*
+ * Parse a function call, a number, a variable expression or a string
+ * literal.
+ */
static Token
CondParser_LeafToken(CondParser *par, Boolean doEval)
{
- Token t;
- char *arg = NULL;
- size_t arglen;
- const char *cp = par->p;
- const char *cp1;
+ Token t;
+ char *arg = NULL;
+ size_t arglen;
+ const char *cp;
+ const char *cp1;
- if (CondParser_Func(par, doEval, &t))
- return t;
+ if (CondParser_Func(par, doEval, &t))
+ return t;
- /* Push anything numeric through the compare expression */
- cp = par->p;
- if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+')
- return CondParser_Comparison(par, doEval);
-
- /*
- * Most likely we have a naked token to apply the default function to.
- * However ".if a == b" gets here when the "a" is unquoted and doesn't
- * start with a '$'. This surprises people.
- * If what follows the function argument is a '=' or '!' then the syntax
- * would be invalid if we did "defined(a)" - so instead treat as an
- * expression.
- */
- arglen = ParseFuncArg(&cp, doEval, NULL, &arg);
- cp1 = cp;
- cpp_skip_whitespace(&cp1);
- if (*cp1 == '=' || *cp1 == '!')
- return CondParser_Comparison(par, doEval);
- par->p = cp;
-
- /*
- * Evaluate the argument using the default function.
- * This path always treats .if as .ifdef. To get here, the character
- * after .if must have been taken literally, so the argument cannot
- * be empty - even if it contained a variable expansion.
- */
- t = ToToken(!doEval || If_Eval(par->if_info, arg, arglen));
- free(arg);
- return t;
+ /* Push anything numeric through the compare expression */
+ cp = par->p;
+ if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+')
+ return CondParser_Comparison(par, doEval);
+
+ /*
+ * Most likely we have a naked token to apply the default function to.
+ * However ".if a == b" gets here when the "a" is unquoted and doesn't
+ * start with a '$'. This surprises people.
+ * If what follows the function argument is a '=' or '!' then the
+ * syntax would be invalid if we did "defined(a)" - so instead treat
+ * as an expression.
+ */
+ arglen = ParseFuncArg(&cp, doEval, NULL, &arg);
+ cp1 = cp;
+ cpp_skip_whitespace(&cp1);
+ if (*cp1 == '=' || *cp1 == '!')
+ return CondParser_Comparison(par, doEval);
+ par->p = cp;
+
+ /*
+ * Evaluate the argument using the default function.
+ * This path always treats .if as .ifdef. To get here, the character
+ * after .if must have been taken literally, so the argument cannot
+ * be empty - even if it contained a variable expansion.
+ */
+ t = ToToken(!doEval || If_Eval(par->if_info, arg, arglen));
+ free(arg);
+ return t;
}
/* Return the next token or comparison result from the parser. */
static Token
CondParser_Token(CondParser *par, Boolean doEval)
{
- Token t;
+ Token t;
- t = par->curr;
- if (t != TOK_NONE) {
- par->curr = TOK_NONE;
- return t;
- }
+ t = par->curr;
+ if (t != TOK_NONE) {
+ par->curr = TOK_NONE;
+ return t;
+ }
- cpp_skip_hspace(&par->p);
+ cpp_skip_hspace(&par->p);
- switch (par->p[0]) {
+ switch (par->p[0]) {
- case '(':
- par->p++;
- return TOK_LPAREN;
+ case '(':
+ par->p++;
+ return TOK_LPAREN;
- case ')':
- par->p++;
- return TOK_RPAREN;
+ case ')':
+ par->p++;
+ return TOK_RPAREN;
- case '|':
- par->p++;
- if (par->p[0] == '|')
- par->p++;
- else if (opts.lint) {
- Parse_Error(PARSE_FATAL, "Unknown operator '|'");
- par->printedError = TRUE;
- return TOK_ERROR;
- }
- return TOK_OR;
-
- case '&':
- par->p++;
- if (par->p[0] == '&')
- par->p++;
- else if (opts.lint) {
- Parse_Error(PARSE_FATAL, "Unknown operator '&'");
- par->printedError = TRUE;
- return TOK_ERROR;
- }
- return TOK_AND;
+ case '|':
+ par->p++;
+ if (par->p[0] == '|')
+ par->p++;
+ else if (opts.strict) {
+ Parse_Error(PARSE_FATAL, "Unknown operator '|'");
+ par->printedError = TRUE;
+ return TOK_ERROR;
+ }
+ return TOK_OR;
- case '!':
- par->p++;
- return TOK_NOT;
+ case '&':
+ par->p++;
+ if (par->p[0] == '&')
+ par->p++;
+ else if (opts.strict) {
+ Parse_Error(PARSE_FATAL, "Unknown operator '&'");
+ par->printedError = TRUE;
+ return TOK_ERROR;
+ }
+ return TOK_AND;
- case '#': /* XXX: see unit-tests/cond-token-plain.mk */
- case '\n': /* XXX: why should this end the condition? */
- /* Probably obsolete now, from 1993-03-21. */
- case '\0':
- return TOK_EOF;
+ case '!':
+ par->p++;
+ return TOK_NOT;
- case '"':
- case '$':
- return CondParser_Comparison(par, doEval);
+ case '#': /* XXX: see unit-tests/cond-token-plain.mk */
+ case '\n': /* XXX: why should this end the condition? */
+ /* Probably obsolete now, from 1993-03-21. */
+ case '\0':
+ return TOK_EOF;
- default:
- return CondParser_LeafToken(par, doEval);
- }
+ case '"':
+ case '$':
+ return CondParser_Comparison(par, doEval);
+
+ default:
+ return CondParser_LeafToken(par, doEval);
+ }
}
-/* Parse a single term in the expression. This consists of a terminal symbol
+/*
+ * Parse a single term in the expression. This consists of a terminal symbol
* or TOK_NOT and a term (not including the binary operators):
*
* T -> defined(variable) | make(target) | exists(file) | symbol
@@ -887,38 +921,39 @@ CondParser_Token(CondParser *par, Boolean doEval)
static Token
CondParser_Term(CondParser *par, Boolean doEval)
{
- Token t;
+ Token t;
- t = CondParser_Token(par, doEval);
+ t = CondParser_Token(par, doEval);
- if (t == TOK_EOF) {
- /*
- * If we reached the end of the expression, the expression
- * is malformed...
- */
- t = TOK_ERROR;
- } else if (t == TOK_LPAREN) {
- /*
- * T -> ( E )
- */
- t = CondParser_Expr(par, doEval);
- if (t != TOK_ERROR) {
- if (CondParser_Token(par, doEval) != TOK_RPAREN) {
+ if (t == TOK_EOF) {
+ /*
+ * If we reached the end of the expression, the expression
+ * is malformed...
+ */
t = TOK_ERROR;
- }
- }
- } else if (t == TOK_NOT) {
- t = CondParser_Term(par, doEval);
- if (t == TOK_TRUE) {
- t = TOK_FALSE;
- } else if (t == TOK_FALSE) {
- t = TOK_TRUE;
+ } else if (t == TOK_LPAREN) {
+ /*
+ * T -> ( E )
+ */
+ t = CondParser_Expr(par, doEval);
+ if (t != TOK_ERROR) {
+ if (CondParser_Token(par, doEval) != TOK_RPAREN) {
+ t = TOK_ERROR;
+ }
+ }
+ } else if (t == TOK_NOT) {
+ t = CondParser_Term(par, doEval);
+ if (t == TOK_TRUE) {
+ t = TOK_FALSE;
+ } else if (t == TOK_FALSE) {
+ t = TOK_TRUE;
+ }
}
- }
- return t;
+ return t;
}
-/* Parse a conjunctive factor (nice name, wot?)
+/*
+ * Parse a conjunctive factor (nice name, wot?)
*
* F -> T && F | T
*
@@ -928,37 +963,38 @@ CondParser_Term(CondParser *par, Boolean doEval)
static Token
CondParser_Factor(CondParser *par, Boolean doEval)
{
- Token l, o;
-
- l = CondParser_Term(par, doEval);
- if (l != TOK_ERROR) {
- o = CondParser_Token(par, doEval);
-
- if (o == TOK_AND) {
- /*
- * F -> T && F
- *
- * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we
- * have to parse the r.h.s. anyway (to throw it away).
- * If T is TOK_TRUE, the result is the r.h.s., be it a TOK_ERROR
- * or not.
- */
- if (l == TOK_TRUE) {
- l = CondParser_Factor(par, doEval);
- } else {
- (void)CondParser_Factor(par, FALSE);
- }
- } else {
- /*
- * F -> T
- */
- CondParser_PushBack(par, o);
+ Token l, o;
+
+ l = CondParser_Term(par, doEval);
+ if (l != TOK_ERROR) {
+ o = CondParser_Token(par, doEval);
+
+ if (o == TOK_AND) {
+ /*
+ * F -> T && F
+ *
+ * If T is TOK_FALSE, the whole thing will be
+ * TOK_FALSE, but we have to parse the r.h.s. anyway
+ * (to throw it away). If T is TOK_TRUE, the result
+ * is the r.h.s., be it a TOK_ERROR or not.
+ */
+ if (l == TOK_TRUE) {
+ l = CondParser_Factor(par, doEval);
+ } else {
+ (void)CondParser_Factor(par, FALSE);
+ }
+ } else {
+ /*
+ * F -> T
+ */
+ CondParser_PushBack(par, o);
+ }
}
- }
- return l;
+ return l;
}
-/* Main expression production.
+/*
+ * Main expression production.
*
* E -> F || E | F
*
@@ -968,55 +1004,58 @@ CondParser_Factor(CondParser *par, Boolean doEval)
static Token
CondParser_Expr(CondParser *par, Boolean doEval)
{
- Token l, o;
-
- l = CondParser_Factor(par, doEval);
- if (l != TOK_ERROR) {
- o = CondParser_Token(par, doEval);
-
- if (o == TOK_OR) {
- /*
- * E -> F || E
- *
- * A similar thing occurs for ||, except that here we make sure
- * the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s.
- * Once again, if l is TOK_FALSE, the result is the r.h.s. and once
- * again if l is TOK_TRUE, we parse the r.h.s. to throw it away.
- */
- if (l == TOK_FALSE) {
- l = CondParser_Expr(par, doEval);
- } else {
- (void)CondParser_Expr(par, FALSE);
- }
- } else {
- /*
- * E -> F
- */
- CondParser_PushBack(par, o);
+ Token l, o;
+
+ l = CondParser_Factor(par, doEval);
+ if (l != TOK_ERROR) {
+ o = CondParser_Token(par, doEval);
+
+ if (o == TOK_OR) {
+ /*
+ * E -> F || E
+ *
+ * A similar thing occurs for ||, except that here
+ * we make sure the l.h.s. is TOK_FALSE before we
+ * bother to evaluate the r.h.s. Once again, if l
+ * is TOK_FALSE, the result is the r.h.s. and once
+ * again if l is TOK_TRUE, we parse the r.h.s. to
+ * throw it away.
+ */
+ if (l == TOK_FALSE) {
+ l = CondParser_Expr(par, doEval);
+ } else {
+ (void)CondParser_Expr(par, FALSE);
+ }
+ } else {
+ /*
+ * E -> F
+ */
+ CondParser_PushBack(par, o);
+ }
}
- }
- return l;
+ return l;
}
static CondEvalResult
CondParser_Eval(CondParser *par, Boolean *value)
{
- Token res;
+ Token res;
- DEBUG1(COND, "CondParser_Eval: %s\n", par->p);
+ DEBUG1(COND, "CondParser_Eval: %s\n", par->p);
- res = CondParser_Expr(par, TRUE);
- if (res != TOK_FALSE && res != TOK_TRUE)
- return COND_INVALID;
+ res = CondParser_Expr(par, TRUE);
+ if (res != TOK_FALSE && res != TOK_TRUE)
+ return COND_INVALID;
- if (CondParser_Token(par, TRUE /* XXX: Why TRUE? */) != TOK_EOF)
- return COND_INVALID;
+ if (CondParser_Token(par, FALSE) != TOK_EOF)
+ return COND_INVALID;
- *value = res == TOK_TRUE;
- return COND_PARSE;
+ *value = res == TOK_TRUE;
+ return COND_PARSE;
}
-/* Evaluate the condition, including any side effects from the variable
+/*
+ * 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.
*
@@ -1028,37 +1067,47 @@ CondParser_Eval(CondParser *par, Boolean *value)
*/
static CondEvalResult
CondEvalExpression(const struct If *info, const char *cond, Boolean *value,
- Boolean eprint, Boolean strictLHS)
+ Boolean eprint, Boolean strictLHS)
{
- CondParser par;
- CondEvalResult rval;
+ CondParser par;
+ CondEvalResult rval;
- lhsStrict = strictLHS;
+ lhsStrict = strictLHS;
- cpp_skip_hspace(&cond);
+ cpp_skip_hspace(&cond);
- par.if_info = info != NULL ? info : ifs + PLAIN_IF_INDEX;
- par.p = cond;
- par.curr = TOK_NONE;
- par.printedError = FALSE;
+ par.if_info = info != NULL ? info : ifs + PLAIN_IF_INDEX;
+ par.p = cond;
+ par.curr = TOK_NONE;
+ par.printedError = FALSE;
- rval = CondParser_Eval(&par, value);
+ rval = CondParser_Eval(&par, value);
- if (rval == COND_INVALID && eprint && !par.printedError)
- Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond);
+ if (rval == COND_INVALID && eprint && !par.printedError)
+ Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond);
- return rval;
+ return rval;
}
-/* Evaluate a condition in a :? modifier, such as
- * ${"${VAR}" == value:?yes:no}. */
+/*
+ * Evaluate a condition in a :? modifier, such as
+ * ${"${VAR}" == value:?yes:no}.
+ */
CondEvalResult
Cond_EvalCondition(const char *cond, Boolean *out_value)
{
return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE);
}
-/* Evaluate the conditional directive in the line, which is one of:
+static Boolean
+IsEndif(const char *p)
+{
+ return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' &&
+ p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]);
+}
+
+/*
+ * Evaluate the conditional directive in the line, which is one of:
*
* .if <cond>
* .ifmake <cond>
@@ -1088,194 +1137,212 @@ Cond_EvalCondition(const char *cond, Boolean *out_value)
* or because the condition could not be evaluated
*/
CondEvalResult
-Cond_EvalLine(const char *const line)
+Cond_EvalLine(const char *line)
{
- typedef enum IfState {
+ typedef enum IfState {
- /* None of the previous <cond> evaluated to TRUE. */
- IFS_INITIAL = 0,
+ /* None of the previous <cond> evaluated to TRUE. */
+ IFS_INITIAL = 0,
- /* The previous <cond> evaluated to TRUE.
- * The lines following this condition are interpreted. */
- IFS_ACTIVE = 1 << 0,
+ /* The previous <cond> evaluated to TRUE.
+ * The lines following this condition are interpreted. */
+ IFS_ACTIVE = 1 << 0,
- /* The previous directive was an '.else'. */
- IFS_SEEN_ELSE = 1 << 1,
+ /* The previous directive was an '.else'. */
+ IFS_SEEN_ELSE = 1 << 1,
- /* One of the previous <cond> evaluated to TRUE. */
- IFS_WAS_ACTIVE = 1 << 2
+ /* One of the previous <cond> evaluated to TRUE. */
+ IFS_WAS_ACTIVE = 1 << 2
- } IfState;
+ } IfState;
- static enum IfState *cond_states = NULL;
- static unsigned int cond_states_cap = 128;
+ static enum IfState *cond_states = NULL;
+ static unsigned int cond_states_cap = 128;
- const struct If *ifp;
- Boolean isElif;
- Boolean value;
- IfState state;
- const char *p = line;
+ const struct If *ifp;
+ Boolean isElif;
+ Boolean value;
+ IfState state;
+ const char *p = line;
- if (cond_states == NULL) {
- cond_states = bmake_malloc(cond_states_cap * sizeof *cond_states);
- cond_states[0] = IFS_ACTIVE;
- }
+ if (cond_states == NULL) {
+ cond_states = bmake_malloc(
+ cond_states_cap * sizeof *cond_states);
+ cond_states[0] = IFS_ACTIVE;
+ }
- p++; /* skip the leading '.' */
- cpp_skip_hspace(&p);
+ p++; /* skip the leading '.' */
+ cpp_skip_hspace(&p);
- /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
- if (p[0] == 'e') {
- if (p[1] != 'l') {
- if (!is_token(p + 1, "ndif", 4)) {
- /* Unknown directive. It might still be a transformation
- * rule like '.elisp.scm', therefore no error message here. */
- return COND_INVALID;
- }
+ if (IsEndif(p)) { /* It is an '.endif'. */
+ if (p[5] != '\0') {
+ Parse_Error(PARSE_FATAL,
+ "The .endif directive does not take arguments.");
+ }
- /* It is an '.endif'. */
- /* TODO: check for extraneous <cond> */
+ if (cond_depth == cond_min_depth) {
+ Parse_Error(PARSE_FATAL, "if-less endif");
+ return COND_PARSE;
+ }
- if (cond_depth == cond_min_depth) {
- Parse_Error(PARSE_FATAL, "if-less endif");
- return COND_PARSE;
- }
+ /* Return state for previous conditional */
+ cond_depth--;
+ return cond_states[cond_depth] & IFS_ACTIVE
+ ? COND_PARSE : COND_SKIP;
+ }
- /* Return state for previous conditional */
- cond_depth--;
- return cond_states[cond_depth] & IFS_ACTIVE
- ? COND_PARSE : COND_SKIP;
+ /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
+ if (p[0] == 'e') {
+ if (p[1] != 'l') {
+ /*
+ * Unknown directive. It might still be a
+ * transformation rule like '.elisp.scm',
+ * therefore no error message here.
+ */
+ return COND_INVALID;
+ }
+
+ /* Quite likely this is 'else' or 'elif' */
+ p += 2;
+ if (is_token(p, "se", 2)) { /* It is an 'else'. */
+
+ if (p[2] != '\0')
+ Parse_Error(PARSE_FATAL,
+ "The .else directive "
+ "does not take arguments.");
+
+ if (cond_depth == cond_min_depth) {
+ Parse_Error(PARSE_FATAL, "if-less else");
+ return COND_PARSE;
+ }
+
+ state = cond_states[cond_depth];
+ if (state == IFS_INITIAL) {
+ state = IFS_ACTIVE | IFS_SEEN_ELSE;
+ } else {
+ if (state & IFS_SEEN_ELSE)
+ Parse_Error(PARSE_WARNING,
+ "extra else");
+ state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
+ }
+ cond_states[cond_depth] = state;
+
+ return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP;
+ }
+ /* Assume for now it is an elif */
+ isElif = TRUE;
+ } else
+ isElif = FALSE;
+
+ if (p[0] != 'i' || p[1] != 'f') {
+ /*
+ * Unknown directive. It might still be a transformation rule
+ * like '.elisp.scm', therefore no error message here.
+ */
+ return COND_INVALID; /* Not an ifxxx or elifxxx line */
}
- /* Quite likely this is 'else' or 'elif' */
+ /*
+ * Figure out what sort of conditional it is -- what its default
+ * function is, etc. -- by looking in the table of valid "ifs"
+ */
p += 2;
- if (is_token(p, "se", 2)) { /* It is an 'else'. */
-
- if (opts.lint && p[2] != '\0')
- Parse_Error(PARSE_FATAL,
- "The .else directive does not take arguments.");
-
- if (cond_depth == cond_min_depth) {
- Parse_Error(PARSE_FATAL, "if-less else");
- return COND_PARSE;
- }
-
- state = cond_states[cond_depth];
- if (state == IFS_INITIAL) {
- state = IFS_ACTIVE | IFS_SEEN_ELSE;
- } else {
- if (state & IFS_SEEN_ELSE)
- Parse_Error(PARSE_WARNING, "extra else");
- state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
- }
- cond_states[cond_depth] = state;
-
- return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP;
- }
- /* Assume for now it is an elif */
- isElif = TRUE;
- } else
- isElif = FALSE;
-
- if (p[0] != 'i' || p[1] != 'f') {
- /* Unknown directive. It might still be a transformation rule like
- * '.elisp.scm', therefore no error message here. */
- return COND_INVALID; /* Not an ifxxx or elifxxx line */
- }
-
- /*
- * Figure out what sort of conditional it is -- what its default
- * function is, etc. -- by looking in the table of valid "ifs"
- */
- p += 2;
- for (ifp = ifs;; ifp++) {
- if (ifp->form == NULL) {
- /* TODO: Add error message about unknown directive,
- * since there is no other known directive that starts with 'el'
- * or 'if'.
- * Example: .elifx 123 */
- return COND_INVALID;
- }
- if (is_token(p, ifp->form, ifp->formlen)) {
- p += ifp->formlen;
- break;
+ for (ifp = ifs;; ifp++) {
+ if (ifp->form == NULL) {
+ /*
+ * TODO: Add error message about unknown directive,
+ * since there is no other known directive that starts
+ * with 'el' or 'if'.
+ *
+ * Example: .elifx 123
+ */
+ return COND_INVALID;
+ }
+ if (is_token(p, ifp->form, ifp->formlen)) {
+ p += ifp->formlen;
+ break;
+ }
}
- }
- /* Now we know what sort of 'if' it is... */
+ /* Now we know what sort of 'if' it is... */
- if (isElif) {
- if (cond_depth == cond_min_depth) {
- Parse_Error(PARSE_FATAL, "if-less elif");
- return COND_PARSE;
- }
- state = cond_states[cond_depth];
- if (state & IFS_SEEN_ELSE) {
- Parse_Error(PARSE_WARNING, "extra elif");
- cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
- return COND_SKIP;
- }
- if (state != IFS_INITIAL) {
- cond_states[cond_depth] = IFS_WAS_ACTIVE;
- return COND_SKIP;
+ if (isElif) {
+ if (cond_depth == cond_min_depth) {
+ Parse_Error(PARSE_FATAL, "if-less elif");
+ return COND_PARSE;
+ }
+ state = cond_states[cond_depth];
+ if (state & IFS_SEEN_ELSE) {
+ Parse_Error(PARSE_WARNING, "extra elif");
+ cond_states[cond_depth] =
+ IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
+ return COND_SKIP;
+ }
+ if (state != IFS_INITIAL) {
+ cond_states[cond_depth] = IFS_WAS_ACTIVE;
+ return COND_SKIP;
+ }
+ } else {
+ /* Normal .if */
+ if (cond_depth + 1 >= cond_states_cap) {
+ /*
+ * This is rare, but not impossible.
+ * In meta mode, dirdeps.mk (only runs at level 0)
+ * can need more than the default.
+ */
+ cond_states_cap += 32;
+ cond_states = bmake_realloc(cond_states,
+ cond_states_cap *
+ sizeof *cond_states);
+ }
+ state = cond_states[cond_depth];
+ cond_depth++;
+ if (!(state & IFS_ACTIVE)) {
+ /*
+ * If we aren't parsing the data,
+ * treat as always false.
+ */
+ cond_states[cond_depth] = IFS_WAS_ACTIVE;
+ return COND_SKIP;
+ }
}
- } else {
- /* Normal .if */
- if (cond_depth + 1 >= cond_states_cap) {
- /*
- * This is rare, but not impossible.
- * In meta mode, dirdeps.mk (only runs at level 0)
- * can need more than the default.
- */
- cond_states_cap += 32;
- cond_states = bmake_realloc(cond_states,
- cond_states_cap * sizeof *cond_states);
+
+ /* And evaluate the conditional expression */
+ if (CondEvalExpression(ifp, p, &value, TRUE, TRUE) == COND_INVALID) {
+ /* Syntax error in conditional, error message already output. */
+ /* Skip everything to matching .endif */
+ /* XXX: An extra '.else' is not detected in this case. */
+ cond_states[cond_depth] = IFS_WAS_ACTIVE;
+ return COND_SKIP;
}
- state = cond_states[cond_depth];
- cond_depth++;
- if (!(state & IFS_ACTIVE)) {
- /* If we aren't parsing the data, treat as always false */
- cond_states[cond_depth] = IFS_WAS_ACTIVE;
- return COND_SKIP;
+
+ if (!value) {
+ cond_states[cond_depth] = IFS_INITIAL;
+ return COND_SKIP;
}
- }
-
- /* And evaluate the conditional expression */
- if (CondEvalExpression(ifp, p, &value, TRUE, TRUE) == COND_INVALID) {
- /* Syntax error in conditional, error message already output. */
- /* Skip everything to matching .endif */
- /* XXX: An extra '.else' is not detected in this case. */
- cond_states[cond_depth] = IFS_WAS_ACTIVE;
- return COND_SKIP;
- }
-
- if (!value) {
- cond_states[cond_depth] = IFS_INITIAL;
- return COND_SKIP;
- }
- cond_states[cond_depth] = IFS_ACTIVE;
- return COND_PARSE;
+ cond_states[cond_depth] = IFS_ACTIVE;
+ return COND_PARSE;
}
void
Cond_restore_depth(unsigned int saved_depth)
{
- unsigned int open_conds = cond_depth - cond_min_depth;
+ unsigned int open_conds = cond_depth - cond_min_depth;
- if (open_conds != 0 || saved_depth > cond_depth) {
- Parse_Error(PARSE_FATAL, "%u open conditional%s", open_conds,
- open_conds == 1 ? "" : "s");
- cond_depth = cond_min_depth;
- }
+ if (open_conds != 0 || saved_depth > cond_depth) {
+ Parse_Error(PARSE_FATAL, "%u open conditional%s",
+ open_conds, open_conds == 1 ? "" : "s");
+ cond_depth = cond_min_depth;
+ }
- cond_min_depth = saved_depth;
+ cond_min_depth = saved_depth;
}
unsigned int
Cond_save_depth(void)
{
- unsigned int depth = cond_min_depth;
+ unsigned int depth = cond_min_depth;
- cond_min_depth = cond_depth;
- return depth;
+ cond_min_depth = cond_depth;
+ return depth;
}
diff --git a/dir.c b/dir.c
index 359e61adb669..230b66b3baf3 100644
--- a/dir.c
+++ b/dir.c
@@ -1,4 +1,4 @@
-/* $NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $ */
+/* $NetBSD: dir.c,v 1.255 2021/01/10 21:20:46 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -69,7 +69,8 @@
* SUCH DAMAGE.
*/
-/* Directory searching using wildcards and/or normal names.
+/*
+ * Directory searching using wildcards and/or normal names.
* Used both for source wildcarding in the makefile and for finding
* implicit sources.
*
@@ -106,7 +107,8 @@
*
* Dir_AddDir Add a directory to a search path.
*
- * Dir_MakeFlags Given a search path and a command flag, create
+ * SearchPath_ToFlags
+ * Given a search path and a command flag, create
* a string with each of the directories in the path
* preceded by the command flag and all of them
* separated by a space.
@@ -116,7 +118,8 @@
* as the element is no longer referenced by any other
* search path.
*
- * Dir_ClearPath Resets a search path to the empty list.
+ * SearchPath_Clear
+ * Resets a search path to the empty list.
*
* For debugging:
* Dir_PrintDirectories
@@ -134,13 +137,10 @@
#include "job.h"
/* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $");
-
-#define DIR_DEBUG0(text) DEBUG0(DIR, text)
-#define DIR_DEBUG1(fmt, arg1) DEBUG1(DIR, fmt, arg1)
-#define DIR_DEBUG2(fmt, arg1, arg2) DEBUG2(DIR, fmt, arg1, arg2)
+MAKE_RCSID("$NetBSD: dir.c,v 1.255 2021/01/10 21:20:46 rillig Exp $");
-/* A search path is a list of CachedDir structures. A CachedDir has in it the
+/*
+ * A search path is a list of CachedDir structures. A CachedDir has in it the
* name of the directory and the names of all the files in the directory.
* This is used to cut down on the number of system calls necessary to find
* implicit dependents and their like. Since these searches are made before
@@ -207,179 +207,277 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $");
* the mtime in a cache for when Dir_UpdateMTime was actually called.
*/
+
+/* A cache for the filenames in a directory. */
+struct CachedDir {
+ /*
+ * Name of directory, either absolute or relative to the current
+ * directory. The name is not normalized in any way, that is, "."
+ * and "./." are different.
+ *
+ * Not sure what happens when .CURDIR is assigned a new value; see
+ * Parse_DoVar.
+ */
+ char *name;
+
+ /*
+ * The number of SearchPaths that refer to this directory.
+ * Plus the number of global variables that refer to this directory.
+ * References from openDirs do not count though.
+ */
+ int refCount;
+
+ /* The number of times a file in this directory has been found. */
+ int hits;
+
+ /* The names of the directory entries. */
+ HashSet files;
+};
+
typedef List CachedDirList;
typedef ListNode CachedDirListNode;
typedef ListNode SearchPathNode;
-SearchPath *dirSearchPath; /* main search path */
-
/* A list of cached directories, with fast lookup by directory name. */
typedef struct OpenDirs {
- CachedDirList *list;
- HashTable /* of CachedDirListNode */ table;
+ CachedDirList list;
+ HashTable /* of CachedDirListNode */ table;
} OpenDirs;
+typedef enum CachedStatsFlags {
+ CST_NONE = 0,
+ CST_LSTAT = 1 << 0, /* call lstat(2) instead of stat(2) */
+ CST_UPDATE = 1 << 1 /* ignore existing cached entry */
+} CachedStatsFlags;
+
+
+SearchPath dirSearchPath = LST_INIT; /* main search path */
+
+static OpenDirs openDirs; /* all cached directories */
+
+/*
+ * Variables for gathering statistics on the efficiency of the caching
+ * mechanism.
+ */
+static int hits; /* Found in directory cache */
+static int misses; /* Sad, but not evil misses */
+static int nearmisses; /* Found under search path */
+static int bigmisses; /* Sought by itself */
+
+/* The cached contents of ".", the relative current directory. */
+static CachedDir *dot = NULL;
+/* The cached contents of the absolute current directory. */
+static CachedDir *cur = NULL;
+/* A fake path entry indicating we need to look for '.' last. */
+static CachedDir *dotLast = NULL;
+
+/*
+ * Results of doing a last-resort stat in Dir_FindFile -- if we have to go to
+ * the system to find the file, we might as well have its mtime on record.
+ *
+ * XXX: If this is done way early, there's a chance other rules will have
+ * already updated the file, in which case we'll update it again. Generally,
+ * there won't be two rules to update a single file, so this should be ok,
+ * but...
+ */
+static HashTable mtimes;
+
+static HashTable lmtimes; /* same as mtimes but for lstat */
+
+
+static void OpenDirs_Remove(OpenDirs *, const char *);
+
+
+static CachedDir *
+CachedDir_New(const char *name)
+{
+ CachedDir *dir = bmake_malloc(sizeof *dir);
+
+ dir->name = bmake_strdup(name);
+ dir->refCount = 0;
+ dir->hits = 0;
+ HashSet_Init(&dir->files);
+
+#ifdef DEBUG_REFCNT
+ DEBUG2(DIR, "CachedDir %p new for \"%s\"\n", dir, dir->name);
+#endif
+
+ return dir;
+}
+
+static CachedDir *
+CachedDir_Ref(CachedDir *dir)
+{
+ dir->refCount++;
+
+#ifdef DEBUG_REFCNT
+ DEBUG3(DIR, "CachedDir %p ++ %d for \"%s\"\n",
+ dir, dir->refCount, dir->name);
+#endif
+
+ return dir;
+}
+
+static void
+CachedDir_Unref(CachedDir *dir)
+{
+ dir->refCount--;
+
+#ifdef DEBUG_REFCNT
+ DEBUG3(DIR, "CachedDir %p -- %d for \"%s\"\n",
+ dir, dir->refCount, dir->name);
+#endif
+
+ if (dir->refCount > 0)
+ return;
+
+#ifdef DEBUG_REFCNT
+ DEBUG2(DIR, "CachedDir %p free for \"%s\"\n", dir, dir->name);
+#endif
+
+ OpenDirs_Remove(&openDirs, dir->name);
+
+ free(dir->name);
+ HashSet_Done(&dir->files);
+ free(dir);
+}
+
+/* Update the value of the CachedDir variable, updating the reference counts. */
+static void
+CachedDir_Assign(CachedDir **var, CachedDir *dir)
+{
+ CachedDir *prev;
+
+ prev = *var;
+ *var = dir;
+ if (dir != NULL)
+ CachedDir_Ref(dir);
+ if (prev != NULL)
+ CachedDir_Unref(prev);
+}
+
static void
OpenDirs_Init(OpenDirs *odirs)
{
- odirs->list = Lst_New();
- HashTable_Init(&odirs->table);
+ Lst_Init(&odirs->list);
+ HashTable_Init(&odirs->table);
}
#ifdef CLEANUP
static void
OpenDirs_Done(OpenDirs *odirs)
{
- CachedDirListNode *ln = odirs->list->first;
- while (ln != NULL) {
- CachedDirListNode *next = ln->next;
- CachedDir *dir = ln->datum;
- Dir_Destroy(dir); /* removes the dir from odirs->list */
- ln = next;
- }
- Lst_Free(odirs->list);
- HashTable_Done(&odirs->table);
+ CachedDirListNode *ln = odirs->list.first;
+ DEBUG1(DIR, "OpenDirs_Done: %u entries to remove\n",
+ odirs->table.numEntries);
+ while (ln != NULL) {
+ CachedDirListNode *next = ln->next;
+ CachedDir *dir = ln->datum;
+ DEBUG2(DIR, "OpenDirs_Done: refCount %d for \"%s\"\n",
+ dir->refCount, dir->name);
+ CachedDir_Unref(dir); /* removes the dir from odirs->list */
+ ln = next;
+ }
+ Lst_Done(&odirs->list);
+ HashTable_Done(&odirs->table);
}
#endif
static CachedDir *
OpenDirs_Find(OpenDirs *odirs, const char *name)
{
- CachedDirListNode *ln = HashTable_FindValue(&odirs->table, name);
- return ln != NULL ? ln->datum : NULL;
+ CachedDirListNode *ln = HashTable_FindValue(&odirs->table, name);
+ return ln != NULL ? ln->datum : NULL;
}
static void
OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir)
{
- if (HashTable_FindEntry(&odirs->table, cdir->name) != NULL)
- return;
- Lst_Append(odirs->list, cdir);
- HashTable_Set(&odirs->table, cdir->name, odirs->list->last);
+ if (HashTable_FindEntry(&odirs->table, cdir->name) != NULL)
+ return;
+ Lst_Append(&odirs->list, cdir);
+ HashTable_Set(&odirs->table, cdir->name, odirs->list.last);
}
static void
OpenDirs_Remove(OpenDirs *odirs, const char *name)
{
- HashEntry *he = HashTable_FindEntry(&odirs->table, name);
- CachedDirListNode *ln;
- if (he == NULL)
- return;
- ln = HashEntry_Get(he);
- HashTable_DeleteEntry(&odirs->table, he);
- Lst_Remove(odirs->list, ln);
+ HashEntry *he = HashTable_FindEntry(&odirs->table, name);
+ CachedDirListNode *ln;
+ if (he == NULL)
+ return;
+ ln = HashEntry_Get(he);
+ HashTable_DeleteEntry(&odirs->table, he);
+ Lst_Remove(&odirs->list, ln);
}
-static OpenDirs openDirs; /* all cached directories */
-
/*
- * Variables for gathering statistics on the efficiency of the caching
- * mechanism.
+ * Returns 0 and the result of stat(2) or lstat(2) in *out_cst,
+ * or -1 on error.
*/
-static int hits; /* Found in directory cache */
-static int misses; /* Sad, but not evil misses */
-static int nearmisses; /* Found under search path */
-static int bigmisses; /* Sought by itself */
-
-static CachedDir *dot; /* contents of current directory */
-static CachedDir *cur; /* contents of current directory, if not dot */
-static CachedDir *dotLast; /* a fake path entry indicating we need to
- * look for . last */
-
-/* Results of doing a last-resort stat in Dir_FindFile -- if we have to go to
- * the system to find the file, we might as well have its mtime on record.
- *
- * XXX: If this is done way early, there's a chance other rules will have
- * already updated the file, in which case we'll update it again. Generally,
- * there won't be two rules to update a single file, so this should be ok,
- * but... */
-static HashTable mtimes;
-
-static HashTable lmtimes; /* same as mtimes but for lstat */
-
-typedef enum CachedStatsFlags {
- CST_NONE = 0,
- CST_LSTAT = 1 << 0, /* call lstat(2) instead of stat(2) */
- CST_UPDATE = 1 << 1 /* ignore existing cached entry */
-} CachedStatsFlags;
-
-/* Returns 0 and the result of stat(2) or lstat(2) in *out_cst,
- * or -1 on error. */
static int
cached_stats(const char *pathname, struct cached_stat *out_cst,
CachedStatsFlags flags)
{
- HashTable *tbl = flags & CST_LSTAT ? &lmtimes : &mtimes;
- struct stat sys_st;
- struct cached_stat *cst;
- int rc;
-
- if (pathname == NULL || pathname[0] == '\0')
- return -1; /* This can happen in meta mode. */
-
- cst = HashTable_FindValue(tbl, pathname);
- if (cst != NULL && !(flags & CST_UPDATE)) {
- *out_cst = *cst;
- DIR_DEBUG2("Using cached time %s for %s\n",
- Targ_FmtTime(cst->cst_mtime), pathname);
- return 0;
- }
+ HashTable *tbl = flags & CST_LSTAT ? &lmtimes : &mtimes;
+ struct stat sys_st;
+ struct cached_stat *cst;
+ int rc;
+
+ if (pathname == NULL || pathname[0] == '\0')
+ return -1; /* This can happen in meta mode. */
+
+ cst = HashTable_FindValue(tbl, pathname);
+ if (cst != NULL && !(flags & CST_UPDATE)) {
+ *out_cst = *cst;
+ DEBUG2(DIR, "Using cached time %s for %s\n",
+ Targ_FmtTime(cst->cst_mtime), pathname);
+ return 0;
+ }
- rc = (flags & CST_LSTAT ? lstat : stat)(pathname, &sys_st);
- if (rc == -1)
- return -1; /* don't cache negative lookups */
+ rc = (flags & CST_LSTAT ? lstat : stat)(pathname, &sys_st);
+ if (rc == -1)
+ return -1; /* don't cache negative lookups */
- if (sys_st.st_mtime == 0)
- sys_st.st_mtime = 1; /* avoid confusion with missing file */
+ if (sys_st.st_mtime == 0)
+ sys_st.st_mtime = 1; /* avoid confusion with missing file */
- if (cst == NULL) {
- cst = bmake_malloc(sizeof *cst);
- HashTable_Set(tbl, pathname, cst);
- }
+ if (cst == NULL) {
+ cst = bmake_malloc(sizeof *cst);
+ HashTable_Set(tbl, pathname, cst);
+ }
- cst->cst_mtime = sys_st.st_mtime;
- cst->cst_mode = sys_st.st_mode;
+ cst->cst_mtime = sys_st.st_mtime;
+ cst->cst_mode = sys_st.st_mode;
- *out_cst = *cst;
- DIR_DEBUG2(" Caching %s for %s\n",
- Targ_FmtTime(sys_st.st_mtime), pathname);
+ *out_cst = *cst;
+ DEBUG2(DIR, " Caching %s for %s\n",
+ Targ_FmtTime(sys_st.st_mtime), pathname);
- return 0;
+ return 0;
}
int
cached_stat(const char *pathname, struct cached_stat *cst)
{
- return cached_stats(pathname, cst, CST_NONE);
+ return cached_stats(pathname, cst, CST_NONE);
}
int
cached_lstat(const char *pathname, struct cached_stat *cst)
{
- return cached_stats(pathname, cst, CST_LSTAT);
+ return cached_stats(pathname, cst, CST_LSTAT);
}
/* Initialize the directories module. */
void
Dir_Init(void)
{
- dirSearchPath = Lst_New();
- OpenDirs_Init(&openDirs);
- HashTable_Init(&mtimes);
- HashTable_Init(&lmtimes);
-}
-
-void
-Dir_InitDir(const char *cdname)
-{
- Dir_InitCur(cdname);
-
- dotLast = bmake_malloc(sizeof *dotLast);
- dotLast->refCount = 1;
- dotLast->hits = 0;
- dotLast->name = bmake_strdup(".DOTLAST");
- HashTable_Init(&dotLast->files);
+ OpenDirs_Init(&openDirs);
+ HashTable_Init(&mtimes);
+ HashTable_Init(&lmtimes);
+ CachedDir_Assign(&dotLast, CachedDir_New(".DOTLAST"));
}
/*
@@ -388,58 +486,40 @@ Dir_InitDir(const char *cdname)
void
Dir_InitCur(const char *cdname)
{
- CachedDir *dir;
-
- if (cdname == NULL)
- return;
-
- /*
- * Our build directory is not the same as our source directory.
- * Keep this one around too.
- */
- dir = Dir_AddDir(NULL, cdname);
- if (dir == NULL)
- return;
-
- /* XXX: Reference counting is wrong here.
- * If this function is called repeatedly with the same directory name,
- * its reference count increases each time even though the number of
- * actual references stays the same. */
-
- dir->refCount++;
- if (cur != NULL && cur != dir) {
+ CachedDir *dir;
+
+ if (cdname == NULL)
+ return;
+
/*
- * We've been here before, clean up.
+ * Our build directory is not the same as our source directory.
+ * Keep this one around too.
*/
- cur->refCount--;
- Dir_Destroy(cur);
- }
- cur = dir;
+ dir = Dir_AddDir(NULL, cdname);
+ if (dir == NULL)
+ return;
+
+ CachedDir_Assign(&cur, dir);
}
-/* (Re)initialize "dot" (current/object directory) path hash.
- * Some directories may be opened. */
+/*
+ * (Re)initialize "dot" (current/object directory) path hash.
+ * Some directories may be cached.
+ */
void
Dir_InitDot(void)
{
- if (dot != NULL) {
- /* Remove old entry from openDirs, but do not destroy. */
- OpenDirs_Remove(&openDirs, dot->name);
- }
-
- dot = Dir_AddDir(NULL, ".");
-
- if (dot == NULL) {
- Error("Cannot open `.' (%s)", strerror(errno));
- exit(1);
- }
-
- /*
- * We always need to have dot around, so we increment its reference count
- * to make sure it's not destroyed.
- */
- dot->refCount++;
- Dir_SetPATH(); /* initialize */
+ CachedDir *dir;
+
+ dir = Dir_AddDir(NULL, ".");
+ if (dir == NULL) {
+ Error("Cannot open `.' (%s)", strerror(errno));
+ exit(2); /* Not 1 so -q can distinguish error */
+ }
+
+ CachedDir_Assign(&dot, dir);
+
+ Dir_SetPATH(); /* initialize */
}
/* Clean up the directories module. */
@@ -447,18 +527,13 @@ void
Dir_End(void)
{
#ifdef CLEANUP
- if (cur) {
- cur->refCount--;
- Dir_Destroy(cur);
- }
- dot->refCount--;
- dotLast->refCount--;
- Dir_Destroy(dotLast);
- Dir_Destroy(dot);
- Dir_ClearPath(dirSearchPath);
- Lst_Free(dirSearchPath);
- OpenDirs_Done(&openDirs);
- HashTable_Done(&mtimes);
+ CachedDir_Assign(&cur, NULL);
+ CachedDir_Assign(&dot, NULL);
+ CachedDir_Assign(&dotLast, NULL);
+ SearchPath_Clear(&dirSearchPath);
+ OpenDirs_Done(&openDirs);
+ HashTable_Done(&mtimes);
+ HashTable_Done(&lmtimes);
#endif
}
@@ -470,44 +545,45 @@ Dir_End(void)
void
Dir_SetPATH(void)
{
- CachedDirListNode *ln;
- Boolean hasLastDot = FALSE; /* true if we should search dot last */
+ CachedDirListNode *ln;
+ Boolean seenDotLast = FALSE; /* true if we should search '.' last */
+
+ Var_Delete(".PATH", VAR_GLOBAL);
+
+ if ((ln = dirSearchPath.first) != NULL) {
+ CachedDir *dir = ln->datum;
+ if (dir == dotLast) {
+ seenDotLast = TRUE;
+ Var_Append(".PATH", dotLast->name, VAR_GLOBAL);
+ }
+ }
+
+ if (!seenDotLast) {
+ if (dot != NULL)
+ Var_Append(".PATH", dot->name, VAR_GLOBAL);
+ if (cur != NULL)
+ Var_Append(".PATH", cur->name, VAR_GLOBAL);
+ }
- Var_Delete(".PATH", VAR_GLOBAL);
+ for (ln = dirSearchPath.first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ if (dir == dotLast)
+ continue;
+ if (dir == dot && seenDotLast)
+ continue;
+ Var_Append(".PATH", dir->name, VAR_GLOBAL);
+ }
- if ((ln = dirSearchPath->first) != NULL) {
- CachedDir *dir = ln->datum;
- if (dir == dotLast) {
- hasLastDot = TRUE;
- Var_Append(".PATH", dotLast->name, VAR_GLOBAL);
+ if (seenDotLast) {
+ if (dot != NULL)
+ Var_Append(".PATH", dot->name, VAR_GLOBAL);
+ if (cur != NULL)
+ Var_Append(".PATH", cur->name, VAR_GLOBAL);
}
- }
-
- if (!hasLastDot) {
- if (dot)
- Var_Append(".PATH", dot->name, VAR_GLOBAL);
- if (cur)
- Var_Append(".PATH", cur->name, VAR_GLOBAL);
- }
-
- for (ln = dirSearchPath->first; ln != NULL; ln = ln->next) {
- CachedDir *dir = ln->datum;
- if (dir == dotLast)
- continue;
- if (dir == dot && hasLastDot)
- continue;
- Var_Append(".PATH", dir->name, VAR_GLOBAL);
- }
-
- if (hasLastDot) {
- if (dot)
- Var_Append(".PATH", dot->name, VAR_GLOBAL);
- if (cur)
- Var_Append(".PATH", cur->name, VAR_GLOBAL);
- }
}
-/* See if the given name has any wildcard characters in it and all braces and
+/*
+ * See if the given name has any wildcard characters in it and all braces and
* brackets are properly balanced.
*
* XXX: This code is not 100% correct ([^]] fails etc.). I really don't think
@@ -519,38 +595,39 @@ Dir_SetPATH(void)
Boolean
Dir_HasWildcards(const char *name)
{
- const char *p;
- Boolean wild = FALSE;
- int braces = 0, brackets = 0;
-
- for (p = name; *p != '\0'; p++) {
- switch (*p) {
- case '{':
- braces++;
- wild = TRUE;
- break;
- case '}':
- braces--;
- break;
- case '[':
- brackets++;
- wild = TRUE;
- break;
- case ']':
- brackets--;
- break;
- case '?':
- case '*':
- wild = TRUE;
- break;
- default:
- break;
+ const char *p;
+ Boolean wild = FALSE;
+ int braces = 0, brackets = 0;
+
+ for (p = name; *p != '\0'; p++) {
+ switch (*p) {
+ case '{':
+ braces++;
+ wild = TRUE;
+ break;
+ case '}':
+ braces--;
+ break;
+ case '[':
+ brackets++;
+ wild = TRUE;
+ break;
+ case ']':
+ brackets--;
+ break;
+ case '?':
+ case '*':
+ wild = TRUE;
+ break;
+ default:
+ break;
+ }
}
- }
- return wild && brackets == 0 && braces == 0;
+ return wild && brackets == 0 && braces == 0;
}
-/* See if any files match the pattern and add their names to the 'expansions'
+/*
+ * See if any files match the pattern and add their names to the 'expansions'
* list if they do.
*
* This is incomplete -- wildcards are only expanded in the final path
@@ -566,105 +643,114 @@ Dir_HasWildcards(const char *name)
static void
DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions)
{
- const char *dirName = dir->name;
- Boolean isDot = dirName[0] == '.' && dirName[1] == '\0';
- HashIter hi;
-
- /* XXX: Iterating over all hash entries is inefficient. If the pattern
- * is a plain string without any wildcards, a direct lookup is faster. */
-
- HashIter_Init(&hi, &dir->files);
- while (HashIter_Next(&hi) != NULL) {
- const char *base = hi.entry->key;
-
- if (!Str_Match(base, pattern))
- continue;
+ const char *dirName = dir->name;
+ Boolean isDot = dirName[0] == '.' && dirName[1] == '\0';
+ HashIter hi;
/*
- * Follow the UNIX convention that dot files are only found if the
- * pattern begins with a dot. The pattern '.*' does not match '.' or
- * '..' since these are not included in the directory cache.
- *
- * This means that the pattern '[a-z.]*' does not find '.file', which
- * is consistent with bash, NetBSD sh and csh.
+ * XXX: Iterating over all hash entries is inefficient. If the
+ * pattern is a plain string without any wildcards, a direct lookup
+ * is faster.
*/
- if (base[0] == '.' && pattern[0] != '.')
- continue;
- {
- char *fullName = isDot
- ? bmake_strdup(base)
- : str_concat3(dirName, "/", base);
- Lst_Append(expansions, fullName);
+ HashIter_InitSet(&hi, &dir->files);
+ while (HashIter_Next(&hi) != NULL) {
+ const char *base = hi.entry->key;
+
+ if (!Str_Match(base, pattern))
+ continue;
+
+ /*
+ * Follow the UNIX convention that dot files are only found
+ * if the pattern begins with a dot. The pattern '.*' does
+ * not match '.' or '..' since these are not included in the
+ * directory cache.
+ *
+ * This means that the pattern '[a-z.]*' does not find
+ * '.file', which is consistent with bash, NetBSD sh and csh.
+ */
+ if (base[0] == '.' && pattern[0] != '.')
+ continue;
+
+ {
+ char *fullName = isDot
+ ? bmake_strdup(base)
+ : str_concat3(dirName, "/", base);
+ Lst_Append(expansions, fullName);
+ }
}
- }
}
-/* Find the next closing brace in the string, taking nested braces into
- * account. */
+/*
+ * Find the next closing brace in the string, taking nested braces into
+ * account.
+ */
static const char *
closing_brace(const char *p)
{
- int nest = 0;
- while (*p != '\0') {
- if (*p == '}' && nest == 0)
- break;
- if (*p == '{')
- nest++;
- if (*p == '}')
- nest--;
- p++;
- }
- return p;
+ int nest = 0;
+ while (*p != '\0') {
+ if (*p == '}' && nest == 0)
+ break;
+ if (*p == '{')
+ nest++;
+ if (*p == '}')
+ nest--;
+ p++;
+ }
+ return p;
}
-/* Find the next closing brace or comma in the string, taking nested braces
- * into account. */
+/*
+ * Find the next closing brace or comma in the string, taking nested braces
+ * into account.
+ */
static const char *
separator_comma(const char *p)
{
- int nest = 0;
- while (*p != '\0') {
- if ((*p == '}' || *p == ',') && nest == 0)
- break;
- if (*p == '{')
- nest++;
- if (*p == '}')
- nest--;
- p++;
- }
- return p;
+ int nest = 0;
+ while (*p != '\0') {
+ if ((*p == '}' || *p == ',') && nest == 0)
+ break;
+ if (*p == '{')
+ nest++;
+ if (*p == '}')
+ nest--;
+ p++;
+ }
+ return p;
}
static Boolean
contains_wildcard(const char *p)
{
- for (; *p != '\0'; p++) {
- switch (*p) {
- case '*':
- case '?':
- case '{':
- case '[':
- return TRUE;
+ for (; *p != '\0'; p++) {
+ switch (*p) {
+ case '*':
+ case '?':
+ case '{':
+ case '[':
+ return TRUE;
+ }
}
- }
- return FALSE;
+ return FALSE;
}
static char *
concat3(const char *a, size_t a_len, const char *b, size_t b_len,
const char *c, size_t c_len)
{
- size_t s_len = a_len + b_len + c_len;
- char *s = bmake_malloc(s_len + 1);
- memcpy(s, a, a_len);
- memcpy(s + a_len, b, b_len);
- memcpy(s + a_len + b_len, c, c_len);
- s[s_len] = '\0';
- return s;
+ size_t s_len = a_len + b_len + c_len;
+ char *s = bmake_malloc(s_len + 1);
+ memcpy(s, a, a_len);
+ memcpy(s + a_len, b, b_len);
+ memcpy(s + a_len + b_len, c, c_len);
+ s[s_len] = '\0';
+ return s;
}
-/* Expand curly braces like the C shell. Brace expansion by itself is purely
+/*
+ * Expand curly braces like the C shell. Brace expansion by itself is purely
* textual, the expansions are not looked up in the file system. But if an
* expanded word contains wildcard characters, it is expanded further,
* matching only the actually existing files.
@@ -683,42 +769,43 @@ static void
DirExpandCurly(const char *word, const char *brace, SearchPath *path,
StringList *expansions)
{
- const char *prefix, *middle, *piece, *middle_end, *suffix;
- size_t prefix_len, suffix_len;
-
- /* Split the word into prefix '{' middle '}' suffix. */
-
- middle = brace + 1;
- middle_end = closing_brace(middle);
- if (*middle_end == '\0') {
- Error("Unterminated {} clause \"%s\"", middle);
- return;
- }
-
- prefix = word;
- prefix_len = (size_t)(brace - prefix);
- suffix = middle_end + 1;
- suffix_len = strlen(suffix);
-
- /* Split the middle into pieces, separated by commas. */
-
- piece = middle;
- while (piece < middle_end + 1) {
- const char *piece_end = separator_comma(piece);
- size_t piece_len = (size_t)(piece_end - piece);
-
- char *file = concat3(prefix, prefix_len, piece, piece_len,
- suffix, suffix_len);
-
- if (contains_wildcard(file)) {
- Dir_Expand(file, path, expansions);
- free(file);
- } else {
- Lst_Append(expansions, file);
+ const char *prefix, *middle, *piece, *middle_end, *suffix;
+ size_t prefix_len, suffix_len;
+
+ /* Split the word into prefix '{' middle '}' suffix. */
+
+ middle = brace + 1;
+ middle_end = closing_brace(middle);
+ if (*middle_end == '\0') {
+ Error("Unterminated {} clause \"%s\"", middle);
+ return;
}
- piece = piece_end + 1; /* skip over the comma or closing brace */
- }
+ prefix = word;
+ prefix_len = (size_t)(brace - prefix);
+ suffix = middle_end + 1;
+ suffix_len = strlen(suffix);
+
+ /* Split the middle into pieces, separated by commas. */
+
+ piece = middle;
+ while (piece < middle_end + 1) {
+ const char *piece_end = separator_comma(piece);
+ size_t piece_len = (size_t)(piece_end - piece);
+
+ char *file = concat3(prefix, prefix_len, piece, piece_len,
+ suffix, suffix_len);
+
+ if (contains_wildcard(file)) {
+ Dir_Expand(file, path, expansions);
+ free(file);
+ } else {
+ Lst_Append(expansions, file);
+ }
+
+ /* skip over the comma or closing brace */
+ piece = piece_end + 1;
+ }
}
@@ -726,27 +813,28 @@ DirExpandCurly(const char *word, const char *brace, SearchPath *path,
static void
DirExpandPath(const char *word, SearchPath *path, StringList *expansions)
{
- SearchPathNode *ln;
- for (ln = path->first; ln != NULL; ln = ln->next) {
- CachedDir *dir = ln->datum;
- DirMatchFiles(word, dir, expansions);
- }
+ SearchPathNode *ln;
+ for (ln = path->first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ DirMatchFiles(word, dir, expansions);
+ }
}
static void
PrintExpansions(StringList *expansions)
{
- const char *sep = "";
- StringListNode *ln;
- for (ln = expansions->first; ln != NULL; ln = ln->next) {
- const char *word = ln->datum;
- debug_printf("%s%s", sep, word);
- sep = " ";
- }
- debug_printf("\n");
+ const char *sep = "";
+ StringListNode *ln;
+ for (ln = expansions->first; ln != NULL; ln = ln->next) {
+ const char *word = ln->datum;
+ debug_printf("%s%s", sep, word);
+ sep = " ";
+ }
+ debug_printf("\n");
}
-/* Expand the given word into a list of words by globbing it, looking in the
+/*
+ * Expand the given word into a list of words by globbing it, looking in the
* directories on the given search path.
*
* Input:
@@ -757,185 +845,214 @@ PrintExpansions(StringList *expansions)
void
Dir_Expand(const char *word, SearchPath *path, StringList *expansions)
{
- const char *cp;
+ const char *cp;
+
+ assert(path != NULL);
+ assert(expansions != NULL);
- assert(path != NULL);
- assert(expansions != NULL);
+ DEBUG1(DIR, "Expanding \"%s\"... ", word);
+
+ cp = strchr(word, '{');
+ if (cp != NULL) {
+ DirExpandCurly(word, cp, path, expansions);
+ goto done;
+ }
- DIR_DEBUG1("Expanding \"%s\"... ", word);
+ /* At this point, the word does not contain '{'. */
- cp = strchr(word, '{');
- if (cp) {
- DirExpandCurly(word, cp, path, expansions);
- } else {
cp = strchr(word, '/');
- if (cp) {
- /*
- * The thing has a directory component -- find the first wildcard
- * in the string.
- */
- for (cp = word; *cp; cp++) {
- if (*cp == '?' || *cp == '[' || *cp == '*') {
- break;
- }
- }
+ if (cp == NULL) {
+ /* The word has no directory component. */
+ /* First the files in dot. */
+ DirMatchFiles(word, dot, expansions);
- if (*cp != '\0') {
+ /* Then the files in every other directory on the path. */
+ DirExpandPath(word, path, expansions);
+ goto done;
+ }
+
+ /* At this point, the word has a directory component. */
+
+ /* Find the first wildcard in the word. */
+ for (cp = word; *cp != '\0'; cp++)
+ if (*cp == '?' || *cp == '[' || *cp == '*')
+ break;
+
+ if (*cp == '\0') {
/*
- * Back up to the start of the component
+ * No directory component and no wildcard at all -- this
+ * should never happen as in such a simple case there is no
+ * need to expand anything.
*/
- while (cp > word && *cp != '/') {
- cp--;
- }
- if (cp != word) {
- char *prefix = bmake_strsedup(word, cp + 1);
- /*
- * If the glob isn't in the first component, try and find
- * all the components up to the one with a wildcard.
- */
- char *dirpath = Dir_FindFile(prefix, path);
- free(prefix);
- /*
- * dirpath is null if can't find the leading component
- * XXX: Dir_FindFile won't find internal components.
- * i.e. if the path contains ../Etc/Object and we're
- * looking for Etc, it won't be found. Ah well.
- * Probably not important.
- */
- if (dirpath != NULL) {
- char *dp = &dirpath[strlen(dirpath) - 1];
- if (*dp == '/')
- *dp = '\0';
- path = Lst_New();
- (void)Dir_AddDir(path, dirpath);
- DirExpandPath(cp + 1, path, expansions);
- Lst_Free(path);
- }
- } else {
- /*
- * Start the search from the local directory
- */
- DirExpandPath(word, path, expansions);
- }
- } else {
+ DirExpandPath(word, path, expansions);
+ goto done;
+ }
+
+ /* Back up to the start of the component containing the wildcard. */
+ /* XXX: This handles '///' and '/' differently. */
+ while (cp > word && *cp != '/')
+ cp--;
+
+ if (cp == word) {
+ /* The first component contains the wildcard. */
+ /* Start the search from the local directory */
+ DirExpandPath(word, path, expansions);
+ goto done;
+ }
+
+ {
+ char *prefix = bmake_strsedup(word, cp + 1);
/*
- * Return the file -- this should never happen.
+ * The wildcard isn't in the first component.
+ * Find all the components up to the one with the wildcard.
*/
- DirExpandPath(word, path, expansions);
- }
- } else {
- /*
- * First the files in dot
- */
- DirMatchFiles(word, dot, expansions);
-
- /*
- * Then the files in every other directory on the path.
- */
- DirExpandPath(word, path, expansions);
+ /*
+ * XXX: Check the "the directory is added to the path" part.
+ * It is probably surprising that the directory before a
+ * wildcard gets added to the path.
+ */
+ /*
+ * XXX: Only the first match of the prefix in the path is
+ * taken, any others are ignored. The expectation may be
+ * that the pattern is expanded in the whole path.
+ */
+ char *dirpath = Dir_FindFile(prefix, path);
+ free(prefix);
+
+ /*
+ * dirpath is null if can't find the leading component
+ * XXX: Dir_FindFile won't find internal components.
+ * i.e. if the path contains ../Etc/Object and we're
+ * looking for Etc, it won't be found. Ah well.
+ * Probably not important.
+ * XXX: Check whether the above comment is still true.
+ */
+ if (dirpath != NULL) {
+ SearchPath *partPath;
+
+ char *end = &dirpath[strlen(dirpath) - 1];
+ /* XXX: What about multiple trailing slashes? */
+ if (*end == '/')
+ *end = '\0';
+
+ partPath = SearchPath_New();
+ (void)Dir_AddDir(partPath, dirpath);
+ DirExpandPath(cp + 1, partPath, expansions);
+ SearchPath_Free(partPath);
+ }
}
- }
- if (DEBUG(DIR))
- PrintExpansions(expansions);
+
+done:
+ if (DEBUG(DIR))
+ PrintExpansions(expansions);
}
-/* Find if the file with the given name exists in the given path.
- * Return the freshly allocated path to the file, or NULL. */
+/*
+ * Find if the file with the given name exists in the given path.
+ * Return the freshly allocated path to the file, or NULL.
+ */
static char *
DirLookup(CachedDir *dir, const char *base)
{
- char *file; /* the current filename to check */
+ char *file; /* the current filename to check */
- DIR_DEBUG1(" %s ...\n", dir->name);
+ DEBUG1(DIR, " %s ...\n", dir->name);
- if (HashTable_FindEntry(&dir->files, base) == NULL)
- return NULL;
+ if (!HashSet_Contains(&dir->files, base))
+ return NULL;
- file = str_concat3(dir->name, "/", base);
- DIR_DEBUG1(" returning %s\n", file);
- dir->hits++;
- hits++;
- return file;
+ file = str_concat3(dir->name, "/", base);
+ DEBUG1(DIR, " returning %s\n", file);
+ dir->hits++;
+ hits++;
+ return file;
}
-/* Find if the file with the given name exists in the given directory.
- * Return the freshly allocated path to the file, or NULL. */
+/*
+ * Find if the file with the given name exists in the given directory.
+ * Return the freshly allocated path to the file, or NULL.
+ */
static char *
DirLookupSubdir(CachedDir *dir, const char *name)
{
- struct cached_stat cst;
- char *file = dir == dot ? bmake_strdup(name)
- : str_concat3(dir->name, "/", name);
+ struct cached_stat cst;
+ char *file = dir == dot ? bmake_strdup(name)
+ : str_concat3(dir->name, "/", name);
- DIR_DEBUG1("checking %s ...\n", file);
+ DEBUG1(DIR, "checking %s ...\n", file);
- if (cached_stat(file, &cst) == 0) {
- nearmisses++;
- return file;
- }
- free(file);
- return NULL;
+ if (cached_stat(file, &cst) == 0) {
+ nearmisses++;
+ return file;
+ }
+ free(file);
+ return NULL;
}
-/* Find if the file with the given name exists in the given path.
+/*
+ * Find if the file with the given name exists in the given path.
* Return the freshly allocated path to the file, the empty string, or NULL.
* Returning the empty string means that the search should be terminated.
*/
static char *
DirLookupAbs(CachedDir *dir, const char *name, const char *cp)
{
- const char *dnp; /* pointer into dir->name */
- const char *np; /* pointer into name */
-
- DIR_DEBUG1(" %s ...\n", dir->name);
-
- /*
- * If the file has a leading path component and that component
- * exactly matches the entire name of the current search
- * directory, we can attempt another cache lookup. And if we don't
- * have a hit, we can safely assume the file does not exist at all.
- */
- for (dnp = dir->name, np = name; *dnp != '\0' && *dnp == *np; dnp++, np++)
- continue;
- if (*dnp != '\0' || np != cp - 1)
- return NULL;
+ const char *dnp; /* pointer into dir->name */
+ const char *np; /* pointer into name */
+
+ DEBUG1(DIR, " %s ...\n", dir->name);
- if (HashTable_FindEntry(&dir->files, cp) == NULL) {
- DIR_DEBUG0(" must be here but isn't -- returning\n");
- return bmake_strdup(""); /* to terminate the search */
- }
+ /*
+ * If the file has a leading path component and that component
+ * exactly matches the entire name of the current search
+ * directory, we can attempt another cache lookup. And if we don't
+ * have a hit, we can safely assume the file does not exist at all.
+ */
+ for (dnp = dir->name, np = name;
+ *dnp != '\0' && *dnp == *np; dnp++, np++)
+ continue;
+ if (*dnp != '\0' || np != cp - 1)
+ return NULL;
+
+ if (!HashSet_Contains(&dir->files, cp)) {
+ DEBUG0(DIR, " must be here but isn't -- returning\n");
+ return bmake_strdup(""); /* to terminate the search */
+ }
- dir->hits++;
- hits++;
- DIR_DEBUG1(" returning %s\n", name);
- return bmake_strdup(name);
+ dir->hits++;
+ hits++;
+ DEBUG1(DIR, " returning %s\n", name);
+ return bmake_strdup(name);
}
-/* Find the file given on "." or curdir.
- * Return the freshly allocated path to the file, or NULL. */
+/*
+ * Find the file given on "." or curdir.
+ * Return the freshly allocated path to the file, or NULL.
+ */
static char *
DirFindDot(const char *name, const char *base)
{
- if (HashTable_FindEntry(&dot->files, base) != NULL) {
- DIR_DEBUG0(" in '.'\n");
- hits++;
- dot->hits++;
- return bmake_strdup(name);
- }
+ if (HashSet_Contains(&dot->files, base)) {
+ DEBUG0(DIR, " in '.'\n");
+ hits++;
+ dot->hits++;
+ return bmake_strdup(name);
+ }
- if (cur != NULL && HashTable_FindEntry(&cur->files, base) != NULL) {
- DIR_DEBUG1(" in ${.CURDIR} = %s\n", cur->name);
- hits++;
- cur->hits++;
- return str_concat3(cur->name, "/", base);
- }
+ if (cur != NULL && HashSet_Contains(&cur->files, base)) {
+ DEBUG1(DIR, " in ${.CURDIR} = %s\n", cur->name);
+ hits++;
+ cur->hits++;
+ return str_concat3(cur->name, "/", base);
+ }
- return NULL;
+ return NULL;
}
-/* Find the file with the given name along the given search path.
+/*
+ * Find the file with the given name along the given search path.
*
* If the file is found in a directory that is not on the path
* already (either 'name' is absolute or it is a relative path
@@ -954,250 +1071,244 @@ DirFindDot(const char *name, const char *base)
char *
Dir_FindFile(const char *name, SearchPath *path)
{
- SearchPathNode *ln;
- char *file; /* the current filename to check */
- const char *base; /* Terminal name of file */
- Boolean hasLastDot = FALSE; /* true if we should search dot last */
- Boolean hasSlash; /* true if 'name' contains a / */
- struct cached_stat cst; /* Buffer for stat, if necessary */
- const char *trailing_dot = ".";
-
- /*
- * Find the final component of the name and note whether it has a
- * slash in it (the name, I mean)
- */
- base = strrchr(name, '/');
- if (base) {
- hasSlash = TRUE;
- base++;
- } else {
- hasSlash = FALSE;
- base = name;
- }
-
- DIR_DEBUG1("Searching for %s ...", name);
-
- if (path == NULL) {
- DIR_DEBUG0("couldn't open path, file not found\n");
- misses++;
- return NULL;
- }
+ char *file; /* the current filename to check */
+ Boolean seenDotLast = FALSE; /* true if we should search dot last */
+ struct cached_stat cst; /* Buffer for stat, if necessary */
+ const char *trailing_dot = ".";
+ const char *base = str_basename(name);
+
+ DEBUG1(DIR, "Searching for %s ...", name);
- if ((ln = path->first) != NULL) {
- CachedDir *dir = ln->datum;
- if (dir == dotLast) {
- hasLastDot = TRUE;
- DIR_DEBUG0("[dot last]...");
+ if (path == NULL) {
+ DEBUG0(DIR, "couldn't open path, file not found\n");
+ misses++;
+ return NULL;
+ }
+
+ if (path->first != NULL) {
+ CachedDir *dir = path->first->datum;
+ if (dir == dotLast) {
+ seenDotLast = TRUE;
+ DEBUG0(DIR, "[dot last]...");
+ }
}
- }
- DIR_DEBUG0("\n");
-
- /*
- * If there's no leading directory components or if the leading
- * directory component is exactly `./', consult the cached contents
- * of each of the directories on the search path.
- */
- if (!hasSlash || (base - name == 2 && *name == '.')) {
+ DEBUG0(DIR, "\n");
+
/*
- * We look through all the directories on the path seeking one which
- * contains the final component of the given name. If such a beast
- * is found, we concatenate the directory name and the final
- * component and return the resulting string. If we don't find any
- * such thing, we go on to phase two...
- *
- * No matter what, we always look for the file in the current
- * directory before anywhere else (unless we found the magic
- * DOTLAST path, in which case we search it last) and we *do not*
- * add the ./ to it if it exists.
- * This is so there are no conflicts between what the user
- * specifies (fish.c) and what pmake finds (./fish.c).
+ * If there's no leading directory components or if the leading
+ * directory component is exactly `./', consult the cached contents
+ * of each of the directories on the search path.
*/
- if (!hasLastDot && (file = DirFindDot(name, base)) != NULL)
- return file;
+ if (base == name || (base - name == 2 && *name == '.')) {
+ SearchPathNode *ln;
- for (; ln != NULL; ln = ln->next) {
- CachedDir *dir = ln->datum;
- if (dir == dotLast)
- continue;
- if ((file = DirLookup(dir, base)) != NULL)
- return file;
+ /*
+ * We look through all the directories on the path seeking one
+ * which contains the final component of the given name. If
+ * such a beast is found, we concatenate the directory name
+ * and the final component and return the resulting string.
+ * If we don't find any such thing, we go on to phase two.
+ *
+ * No matter what, we always look for the file in the current
+ * directory before anywhere else (unless we found the magic
+ * DOTLAST path, in which case we search it last) and we *do
+ * not* add the ./ to it if it exists.
+ * This is so there are no conflicts between what the user
+ * specifies (fish.c) and what pmake finds (./fish.c).
+ */
+ if (!seenDotLast && (file = DirFindDot(name, base)) != NULL)
+ return file;
+
+ for (ln = path->first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ if (dir == dotLast)
+ continue;
+ if ((file = DirLookup(dir, base)) != NULL)
+ return file;
+ }
+
+ if (seenDotLast && (file = DirFindDot(name, base)) != NULL)
+ return file;
}
- if (hasLastDot && (file = DirFindDot(name, base)) != NULL)
- return file;
- }
-
- /*
- * We didn't find the file on any directory in the search path.
- * If the name doesn't contain a slash, that means it doesn't exist.
- * If it *does* contain a slash, however, there is still hope: it
- * could be in a subdirectory of one of the members of the search
- * path. (eg. /usr/include and sys/types.h. The above search would
- * fail to turn up types.h in /usr/include, but it *is* in
- * /usr/include/sys/types.h).
- * [ This no longer applies: If we find such a beast, we assume there
- * will be more (what else can we assume?) and add all but the last
- * component of the resulting name onto the search path (at the
- * end).]
- * This phase is only performed if the file is *not* absolute.
- */
- if (!hasSlash) {
- DIR_DEBUG0(" failed.\n");
- misses++;
- return NULL;
- }
+ /*
+ * We didn't find the file on any directory in the search path.
+ * If the name doesn't contain a slash, that means it doesn't exist.
+ * If it *does* contain a slash, however, there is still hope: it
+ * could be in a subdirectory of one of the members of the search
+ * path. (eg. /usr/include and sys/types.h. The above search would
+ * fail to turn up types.h in /usr/include, but it *is* in
+ * /usr/include/sys/types.h).
+ * [ This no longer applies: If we find such a beast, we assume there
+ * will be more (what else can we assume?) and add all but the last
+ * component of the resulting name onto the search path (at the
+ * end).]
+ * This phase is only performed if the file is *not* absolute.
+ */
+ if (base == name) {
+ DEBUG0(DIR, " failed.\n");
+ misses++;
+ return NULL;
+ }
- if (*base == '\0') {
- /* we were given a trailing "/" */
- base = trailing_dot;
- }
+ if (*base == '\0') {
+ /* we were given a trailing "/" */
+ base = trailing_dot;
+ }
- if (name[0] != '/') {
- Boolean checkedDot = FALSE;
+ if (name[0] != '/') {
+ SearchPathNode *ln;
+ Boolean checkedDot = FALSE;
+
+ DEBUG0(DIR, " Trying subdirectories...\n");
+
+ if (!seenDotLast) {
+ if (dot != NULL) {
+ checkedDot = TRUE;
+ if ((file = DirLookupSubdir(dot, name)) != NULL)
+ return file;
+ }
+ if (cur != NULL &&
+ (file = DirLookupSubdir(cur, name)) != NULL)
+ return file;
+ }
- DIR_DEBUG0(" Trying subdirectories...\n");
+ for (ln = path->first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ if (dir == dotLast)
+ continue;
+ if (dir == dot) {
+ if (checkedDot)
+ continue;
+ checkedDot = TRUE;
+ }
+ if ((file = DirLookupSubdir(dir, name)) != NULL)
+ return file;
+ }
- if (!hasLastDot) {
- if (dot) {
- checkedDot = TRUE;
- if ((file = DirLookupSubdir(dot, name)) != NULL)
- return file;
- }
- if (cur && (file = DirLookupSubdir(cur, name)) != NULL)
- return file;
- }
+ if (seenDotLast) {
+ if (dot != NULL && !checkedDot) {
+ checkedDot = TRUE;
+ if ((file = DirLookupSubdir(dot, name)) != NULL)
+ return file;
+ }
+ if (cur != NULL &&
+ (file = DirLookupSubdir(cur, name)) != NULL)
+ return file;
+ }
- for (ln = path->first; ln != NULL; ln = ln->next) {
- CachedDir *dir = ln->datum;
- if (dir == dotLast)
- continue;
- if (dir == dot) {
- if (checkedDot)
- continue;
- checkedDot = TRUE;
- }
- if ((file = DirLookupSubdir(dir, name)) != NULL)
- return file;
- }
+ if (checkedDot) {
+ /*
+ * Already checked by the given name, since . was in
+ * the path, so no point in proceeding.
+ */
+ DEBUG0(DIR, " Checked . already, returning NULL\n");
+ return NULL;
+ }
- if (hasLastDot) {
- if (dot && !checkedDot) {
- checkedDot = TRUE;
- if ((file = DirLookupSubdir(dot, name)) != NULL)
- return file;
- }
- if (cur && (file = DirLookupSubdir(cur, name)) != NULL)
- return file;
- }
+ } else { /* name[0] == '/' */
+ SearchPathNode *ln;
- if (checkedDot) {
- /*
- * Already checked by the given name, since . was in the path,
- * so no point in proceeding...
- */
- DIR_DEBUG0(" Checked . already, returning NULL\n");
- return NULL;
- }
+ /*
+ * For absolute names, compare directory path prefix against
+ * the the directory path of each member on the search path
+ * for an exact match. If we have an exact match on any member
+ * of the search path, use the cached contents of that member
+ * to lookup the final file component. If that lookup fails we
+ * can safely assume that the file does not exist at all.
+ * This is signified by DirLookupAbs() returning an empty
+ * string.
+ */
+ DEBUG0(DIR, " Trying exact path matches...\n");
+
+ if (!seenDotLast && cur != NULL &&
+ ((file = DirLookupAbs(cur, name, base)) != NULL)) {
+ if (file[0] == '\0') {
+ free(file);
+ return NULL;
+ }
+ return file;
+ }
- } else { /* name[0] == '/' */
+ for (ln = path->first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ if (dir == dotLast)
+ continue;
+ if ((file = DirLookupAbs(dir, name, base)) != NULL) {
+ if (file[0] == '\0') {
+ free(file);
+ return NULL;
+ }
+ return file;
+ }
+ }
+
+ if (seenDotLast && cur != NULL &&
+ ((file = DirLookupAbs(cur, name, base)) != NULL)) {
+ if (file[0] == '\0') {
+ free(file);
+ return NULL;
+ }
+ return file;
+ }
+ }
/*
- * For absolute names, compare directory path prefix against the
- * the directory path of each member on the search path for an exact
- * match. If we have an exact match on any member of the search path,
- * use the cached contents of that member to lookup the final file
- * component. If that lookup fails we can safely assume that the
- * file does not exist at all. This is signified by DirLookupAbs()
- * returning an empty string.
+ * Didn't find it that way, either. Sigh. Phase 3. Add its directory
+ * onto the search path in any case, just in case, then look for the
+ * thing in the hash table. If we find it, grand. We return a new
+ * copy of the name. Otherwise we sadly return a NULL pointer. Sigh.
+ * Note that if the directory holding the file doesn't exist, this
+ * will do an extra search of the final directory on the path. Unless
+ * something weird happens, this search won't succeed and life will
+ * be groovy.
+ *
+ * Sigh. We cannot add the directory onto the search path because
+ * of this amusing case:
+ * $(INSTALLDIR)/$(FILE): $(FILE)
+ *
+ * $(FILE) exists in $(INSTALLDIR) but not in the current one.
+ * When searching for $(FILE), we will find it in $(INSTALLDIR)
+ * b/c we added it here. This is not good...
*/
- DIR_DEBUG0(" Trying exact path matches...\n");
-
- if (!hasLastDot && cur &&
- ((file = DirLookupAbs(cur, name, base)) != NULL)) {
- if (file[0] == '\0') {
- free(file);
- return NULL;
- }
- return file;
- }
+#if 0
+ {
+ CachedDir *dir;
+ char *prefix;
- for (ln = path->first; ln != NULL; ln = ln->next) {
- CachedDir *dir = ln->datum;
- if (dir == dotLast)
- continue;
- if ((file = DirLookupAbs(dir, name, base)) != NULL) {
- if (file[0] == '\0') {
- free(file);
- return NULL;
+ if (base == trailing_dot) {
+ base = strrchr(name, '/');
+ base++;
}
- return file;
- }
- }
+ prefix = bmake_strsedup(name, base - 1);
+ (void)Dir_AddDir(path, prefix);
+ free(prefix);
+
+ bigmisses++;
+ if (path->last == NULL)
+ return NULL;
- if (hasLastDot && cur &&
- ((file = DirLookupAbs(cur, name, base)) != NULL)) {
- if (file[0] == '\0') {
- free(file);
+ dir = path->last->datum;
+ if (HashSet_Contains(&dir->files, base))
+ return bmake_strdup(name);
return NULL;
- }
- return file;
}
- }
-
- /*
- * Didn't find it that way, either. Sigh. Phase 3. Add its directory
- * onto the search path in any case, just in case, then look for the
- * thing in the hash table. If we find it, grand. We return a new
- * copy of the name. Otherwise we sadly return a NULL pointer. Sigh.
- * Note that if the directory holding the file doesn't exist, this will
- * do an extra search of the final directory on the path. Unless something
- * weird happens, this search won't succeed and life will be groovy.
- *
- * Sigh. We cannot add the directory onto the search path because
- * of this amusing case:
- * $(INSTALLDIR)/$(FILE): $(FILE)
- *
- * $(FILE) exists in $(INSTALLDIR) but not in the current one.
- * When searching for $(FILE), we will find it in $(INSTALLDIR)
- * b/c we added it here. This is not good...
- */
-#if 0
- if (base == trailing_dot) {
- base = strrchr(name, '/');
- base++;
- }
- base[-1] = '\0';
- (void)Dir_AddDir(path, name);
- base[-1] = '/';
-
- bigmisses++;
- ln = Lst_Last(path);
- if (ln == NULL) {
- return NULL;
- } else {
- dir = LstNode_Datum(ln);
- }
-
- if (Hash_FindEntry(&dir->files, base) != NULL) {
- return bmake_strdup(name);
- } else {
- return NULL;
- }
#else
- DIR_DEBUG1(" Looking for \"%s\" ...\n", name);
+ DEBUG1(DIR, " Looking for \"%s\" ...\n", name);
- bigmisses++;
- if (cached_stat(name, &cst) == 0) {
- return bmake_strdup(name);
- }
+ bigmisses++;
+ if (cached_stat(name, &cst) == 0) {
+ return bmake_strdup(name);
+ }
- DIR_DEBUG0(" failed. Returning NULL\n");
- return NULL;
+ DEBUG0(DIR, " failed. Returning NULL\n");
+ return NULL;
#endif
}
-/* Search for a path starting at a given directory and then working our way
+/*
+ * Search for a path starting at a given directory and then working our way
* up towards the root.
*
* Input:
@@ -1210,346 +1321,376 @@ Dir_FindFile(const char *name, SearchPath *path)
char *
Dir_FindHereOrAbove(const char *here, const char *search_path)
{
- struct cached_stat cst;
- char *dirbase, *dirbase_end;
- char *try, *try_end;
-
- /* copy out our starting point */
- dirbase = bmake_strdup(here);
- dirbase_end = dirbase + strlen(dirbase);
-
- /* loop until we determine a result */
- for (;;) {
-
- /* try and stat(2) it ... */
- try = str_concat3(dirbase, "/", search_path);
- if (cached_stat(try, &cst) != -1) {
- /*
- * success! if we found a file, chop off
- * the filename so we return a directory.
- */
- if ((cst.cst_mode & S_IFMT) != S_IFDIR) {
- try_end = try + strlen(try);
- while (try_end > try && *try_end != '/')
- try_end--;
- if (try_end > try)
- *try_end = '\0'; /* chop! */
- }
-
- free(dirbase);
- return try;
+ struct cached_stat cst;
+ char *dirbase, *dirbase_end;
+ char *try, *try_end;
+
+ /* copy out our starting point */
+ dirbase = bmake_strdup(here);
+ dirbase_end = dirbase + strlen(dirbase);
+
+ /* loop until we determine a result */
+ for (;;) {
+
+ /* try and stat(2) it ... */
+ try = str_concat3(dirbase, "/", search_path);
+ if (cached_stat(try, &cst) != -1) {
+ /*
+ * success! if we found a file, chop off
+ * the filename so we return a directory.
+ */
+ if ((cst.cst_mode & S_IFMT) != S_IFDIR) {
+ try_end = try + strlen(try);
+ while (try_end > try && *try_end != '/')
+ try_end--;
+ if (try_end > try)
+ *try_end = '\0'; /* chop! */
+ }
+
+ free(dirbase);
+ return try;
+ }
+ free(try);
+
+ /*
+ * nope, we didn't find it. if we used up dirbase we've
+ * reached the root and failed.
+ */
+ if (dirbase_end == dirbase)
+ break; /* failed! */
+
+ /*
+ * truncate dirbase from the end to move up a dir
+ */
+ while (dirbase_end > dirbase && *dirbase_end != '/')
+ dirbase_end--;
+ *dirbase_end = '\0'; /* chop! */
}
- free(try);
+
+ free(dirbase);
+ return NULL;
+}
+
+/*
+ * This is an implied source, and it may have moved,
+ * see if we can find it via the current .PATH
+ */
+static char *
+ResolveMovedDepends(GNode *gn)
+{
+ char *fullName;
+
+ const char *base = str_basename(gn->name);
+ if (base == gn->name)
+ return NULL;
+
+ fullName = Dir_FindFile(base, Suff_FindPath(gn));
+ if (fullName == NULL)
+ return NULL;
/*
- * nope, we didn't find it. if we used up dirbase we've
- * reached the root and failed.
+ * Put the found file in gn->path so that we give that to the compiler.
*/
- if (dirbase_end == dirbase)
- break; /* failed! */
-
/*
- * truncate dirbase from the end to move up a dir
+ * XXX: Better just reset gn->path to NULL; updating it is already done
+ * by Dir_UpdateMTime.
*/
- while (dirbase_end > dirbase && *dirbase_end != '/')
- dirbase_end--;
- *dirbase_end = '\0'; /* chop! */
- }
+ gn->path = bmake_strdup(fullName);
+ if (!Job_RunTarget(".STALE", gn->fname))
+ fprintf(stdout, /* XXX: Why stdout? */
+ "%s: %s, %d: ignoring stale %s for %s, found %s\n",
+ progname, gn->fname, gn->lineno,
+ makeDependfile, gn->name, fullName);
+
+ return fullName;
+}
+
+static char *
+ResolveFullName(GNode *gn)
+{
+ char *fullName;
+
+ fullName = gn->path;
+ if (fullName == NULL && !(gn->type & OP_NOPATH)) {
+
+ fullName = Dir_FindFile(gn->name, Suff_FindPath(gn));
+
+ if (fullName == NULL && gn->flags & FROM_DEPEND &&
+ !Lst_IsEmpty(&gn->implicitParents))
+ fullName = ResolveMovedDepends(gn);
+
+ DEBUG2(DIR, "Found '%s' as '%s'\n",
+ gn->name, fullName != NULL ? fullName : "(not found)");
+ }
+
+ if (fullName == NULL)
+ fullName = bmake_strdup(gn->name);
+
+ /* XXX: Is every piece of memory freed as it should? */
- free(dirbase);
- return NULL;
+ return fullName;
}
-/* Search gn along dirSearchPath and store its modification time in gn->mtime.
+/*
+ * Search gn along dirSearchPath and store its modification time in gn->mtime.
* If no file is found, store 0 instead.
*
- * The found file is stored in gn->path, unless the node already had a path. */
+ * The found file is stored in gn->path, unless the node already had a path.
+ */
void
Dir_UpdateMTime(GNode *gn, Boolean recheck)
{
- char *fullName;
- struct cached_stat cst;
-
- if (gn->type & OP_ARCHV) {
- Arch_UpdateMTime(gn);
- return;
- }
-
- if (gn->type & OP_PHONY) {
- gn->mtime = 0;
- return;
- }
-
- if (gn->path == NULL) {
- if (gn->type & OP_NOPATH)
- fullName = NULL;
- else {
- fullName = Dir_FindFile(gn->name, Suff_FindPath(gn));
- if (fullName == NULL && gn->flags & FROM_DEPEND &&
- !Lst_IsEmpty(gn->implicitParents)) {
- char *cp;
-
- cp = strrchr(gn->name, '/');
- if (cp) {
- /*
- * This is an implied source, and it may have moved,
- * see if we can find it via the current .PATH
- */
- cp++;
-
- fullName = Dir_FindFile(cp, Suff_FindPath(gn));
- if (fullName) {
- /*
- * Put the found file in gn->path
- * so that we give that to the compiler.
- */
- gn->path = bmake_strdup(fullName);
- if (!Job_RunTarget(".STALE", gn->fname))
- fprintf(stdout,
- "%s: %s, %d: ignoring stale %s for %s, "
- "found %s\n", progname, gn->fname,
- gn->lineno,
- makeDependfile, gn->name, fullName);
- }
+ char *fullName;
+ struct cached_stat cst;
+
+ if (gn->type & OP_ARCHV) {
+ Arch_UpdateMTime(gn);
+ return;
+ }
+
+ if (gn->type & OP_PHONY) {
+ gn->mtime = 0;
+ return;
+ }
+
+ fullName = ResolveFullName(gn);
+
+ if (cached_stats(fullName, &cst, recheck ? CST_UPDATE : CST_NONE) < 0) {
+ if (gn->type & OP_MEMBER) {
+ if (fullName != gn->path)
+ free(fullName);
+ Arch_UpdateMemberMTime(gn);
+ return;
}
- }
- DIR_DEBUG2("Found '%s' as '%s'\n",
- gn->name, fullName ? fullName : "(not found)");
+
+ cst.cst_mtime = 0;
}
- } else {
- fullName = gn->path;
- }
- if (fullName == NULL)
- fullName = bmake_strdup(gn->name);
+ if (fullName != NULL && gn->path == NULL)
+ gn->path = fullName;
+ /* XXX: else free(fullName)? */
+
+ gn->mtime = cst.cst_mtime;
+}
+
+/*
+ * Read the directory and add it to the cache in openDirs.
+ * If a path is given, add the directory to that path as well.
+ */
+static CachedDir *
+CacheNewDir(const char *name, SearchPath *path)
+{
+ CachedDir *dir = NULL;
+ DIR *d;
+ struct dirent *dp;
- if (cached_stats(fullName, &cst, recheck ? CST_UPDATE : CST_NONE) < 0) {
- if (gn->type & OP_MEMBER) {
- if (fullName != gn->path)
- free(fullName);
- Arch_UpdateMemberMTime(gn);
- return;
+ if ((d = opendir(name)) == NULL) {
+ DEBUG1(DIR, "Caching %s ... not found\n", name);
+ return dir;
}
- cst.cst_mtime = 0;
- }
+ DEBUG1(DIR, "Caching %s ...\n", name);
- if (fullName != NULL && gn->path == NULL)
- gn->path = fullName;
+ dir = CachedDir_New(name);
+
+ while ((dp = readdir(d)) != NULL) {
+
+#if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */
+ /*
+ * The sun directory library doesn't check for a 0 inode
+ * (0-inode slots just take up space), so we have to do
+ * it ourselves.
+ */
+ if (dp->d_fileno == 0)
+ continue;
+#endif /* sun && d_ino */
+
+ (void)HashSet_Add(&dir->files, dp->d_name);
+ }
+ (void)closedir(d);
+
+ OpenDirs_Add(&openDirs, dir);
+ if (path != NULL)
+ Lst_Append(path, CachedDir_Ref(dir));
- gn->mtime = cst.cst_mtime;
+ DEBUG1(DIR, "Caching %s done\n", name);
+ return dir;
}
-/* Read the list of filenames in the directory and store the result
- * in openDirectories.
+/*
+ * Read the list of filenames in the directory and store the result
+ * in openDirs.
*
* If a path is given, append the directory to that path.
*
* Input:
* path The path to which the directory should be
- * added, or NULL to only add the directory to
- * openDirectories
+ * added, or NULL to only add the directory to openDirs
* name The name of the directory to add.
* The name is not normalized in any way.
+ * Output:
+ * result If no path is given and the directory exists, the
+ * returned CachedDir has a reference count of 0. It
+ * must either be assigned to a variable using
+ * CachedDir_Assign or be appended to a SearchPath using
+ * Lst_Append and CachedDir_Ref.
*/
CachedDir *
Dir_AddDir(SearchPath *path, const char *name)
{
- CachedDir *dir = NULL; /* the added directory */
- DIR *d;
- struct dirent *dp;
- if (path != NULL && strcmp(name, ".DOTLAST") == 0) {
- SearchPathNode *ln;
+ if (path != NULL && strcmp(name, ".DOTLAST") == 0) {
+ SearchPathNode *ln;
- /* XXX: Linear search gets slow with thousands of entries. */
- for (ln = path->first; ln != NULL; ln = ln->next) {
- CachedDir *pathDir = ln->datum;
- if (strcmp(pathDir->name, name) == 0)
- return pathDir;
- }
-
- dotLast->refCount++;
- Lst_Prepend(path, dotLast);
- }
+ /* XXX: Linear search gets slow with thousands of entries. */
+ for (ln = path->first; ln != NULL; ln = ln->next) {
+ CachedDir *pathDir = ln->datum;
+ if (strcmp(pathDir->name, name) == 0)
+ return pathDir;
+ }
- if (path != NULL)
- dir = OpenDirs_Find(&openDirs, name);
- if (dir != NULL) {
- if (Lst_FindDatum(path, dir) == NULL) {
- dir->refCount++;
- Lst_Append(path, dir);
+ Lst_Prepend(path, CachedDir_Ref(dotLast));
}
- return dir;
- }
-
- DIR_DEBUG1("Caching %s ...", name);
-
- if ((d = opendir(name)) != NULL) {
- dir = bmake_malloc(sizeof *dir);
- dir->name = bmake_strdup(name);
- dir->hits = 0;
- dir->refCount = 1;
- HashTable_Init(&dir->files);
- while ((dp = readdir(d)) != NULL) {
-#if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */
- /*
- * The sun directory library doesn't check for a 0 inode
- * (0-inode slots just take up space), so we have to do
- * it ourselves.
- */
- if (dp->d_fileno == 0) {
- continue;
- }
-#endif /* sun && d_ino */
- (void)HashTable_CreateEntry(&dir->files, dp->d_name, NULL);
+ if (path != NULL) {
+ /* XXX: Why is OpenDirs only checked if path != NULL? */
+ CachedDir *dir = OpenDirs_Find(&openDirs, name);
+ if (dir != NULL) {
+ if (Lst_FindDatum(path, dir) == NULL)
+ Lst_Append(path, CachedDir_Ref(dir));
+ return dir;
+ }
}
- (void)closedir(d);
- OpenDirs_Add(&openDirs, dir);
- if (path != NULL)
- Lst_Append(path, dir);
- }
- DIR_DEBUG0("done\n");
- return dir;
+
+ return CacheNewDir(name, path);
}
-/* Return a copy of dirSearchPath, incrementing the reference counts for
- * the contained directories. */
+/*
+ * Return a copy of dirSearchPath, incrementing the reference counts for
+ * the contained directories.
+ */
SearchPath *
Dir_CopyDirSearchPath(void)
{
- SearchPath *path = Lst_New();
- SearchPathNode *ln;
- for (ln = dirSearchPath->first; ln != NULL; ln = ln->next) {
- CachedDir *dir = ln->datum;
- dir->refCount++;
- Lst_Append(path, dir);
- }
- return path;
+ SearchPath *path = SearchPath_New();
+ SearchPathNode *ln;
+ for (ln = dirSearchPath.first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ Lst_Append(path, CachedDir_Ref(dir));
+ }
+ return path;
}
-/*-
- *-----------------------------------------------------------------------
- * Dir_MakeFlags --
- * Make a string by taking all the directories in the given search
- * path and preceding them by the given flag. Used by the suffix
- * module to create variables for compilers based on suffix search
- * paths.
+/*
+ * Make a string by taking all the directories in the given search path and
+ * preceding them by the given flag. Used by the suffix module to create
+ * variables for compilers based on suffix search paths.
*
* Input:
* flag flag which should precede each directory
* path list of directories
*
* Results:
- * The string mentioned above. Note that there is no space between
- * the given flag and each directory. The empty string is returned if
- * Things don't go well.
- *
- * Side Effects:
- * None
- *-----------------------------------------------------------------------
+ * The string mentioned above. Note that there is no space between the
+ * given flag and each directory. The empty string is returned if things
+ * don't go well.
*/
char *
-Dir_MakeFlags(const char *flag, SearchPath *path)
+SearchPath_ToFlags(const char *flag, SearchPath *path)
{
- Buffer buf;
- SearchPathNode *ln;
+ Buffer buf;
+ SearchPathNode *ln;
- Buf_Init(&buf);
+ Buf_Init(&buf);
- if (path != NULL) {
- for (ln = path->first; ln != NULL; ln = ln->next) {
- CachedDir *dir = ln->datum;
- Buf_AddStr(&buf, " ");
- Buf_AddStr(&buf, flag);
- Buf_AddStr(&buf, dir->name);
+ if (path != NULL) {
+ for (ln = path->first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ Buf_AddStr(&buf, " ");
+ Buf_AddStr(&buf, flag);
+ Buf_AddStr(&buf, dir->name);
+ }
}
- }
- return Buf_Destroy(&buf, FALSE);
+ return Buf_Destroy(&buf, FALSE);
}
-/* Nuke a directory descriptor, if possible. Callback procedure for the
- * suffixes module when destroying a search path.
- *
- * Input:
- * dirp The directory descriptor to nuke
- */
+/* Free the search path and all directories mentioned in it. */
void
-Dir_Destroy(void *dirp)
+SearchPath_Free(SearchPath *path)
{
- CachedDir *dir = dirp;
- dir->refCount--;
-
- if (dir->refCount == 0) {
- OpenDirs_Remove(&openDirs, dir->name);
+ SearchPathNode *ln;
- HashTable_Done(&dir->files);
- free(dir->name);
- free(dir);
- }
+ for (ln = path->first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ CachedDir_Unref(dir);
+ }
+ Lst_Free(path);
}
-/* Clear out all elements from the given search path.
- * The path is set to the empty list but is not destroyed. */
+/*
+ * Clear out all elements from the given search path.
+ * The path is set to the empty list but is not destroyed.
+ */
void
-Dir_ClearPath(SearchPath *path)
+SearchPath_Clear(SearchPath *path)
{
- while (!Lst_IsEmpty(path)) {
- CachedDir *dir = Lst_Dequeue(path);
- Dir_Destroy(dir);
- }
+ while (!Lst_IsEmpty(path)) {
+ CachedDir *dir = Lst_Dequeue(path);
+ CachedDir_Unref(dir);
+ }
}
-/* Concatenate two paths, adding the second to the end of the first,
- * skipping duplicates. */
+/*
+ * Concatenate two paths, adding the second to the end of the first,
+ * skipping duplicates.
+ */
void
-Dir_Concat(SearchPath *dst, SearchPath *src)
+SearchPath_AddAll(SearchPath *dst, SearchPath *src)
{
- SearchPathNode *ln;
+ SearchPathNode *ln;
- for (ln = src->first; ln != NULL; ln = ln->next) {
- CachedDir *dir = ln->datum;
- if (Lst_FindDatum(dst, dir) == NULL) {
- dir->refCount++;
- Lst_Append(dst, dir);
+ for (ln = src->first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ if (Lst_FindDatum(dst, dir) == NULL)
+ Lst_Append(dst, CachedDir_Ref(dir));
}
- }
}
static int
percentage(int num, int den)
{
- return den != 0 ? num * 100 / den : 0;
+ return den != 0 ? num * 100 / den : 0;
}
/********** DEBUG INFO **********/
void
Dir_PrintDirectories(void)
{
- CachedDirListNode *ln;
-
- debug_printf("#*** Directory Cache:\n");
- debug_printf("# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n",
- hits, misses, nearmisses, bigmisses,
- percentage(hits, hits + bigmisses + nearmisses));
- debug_printf("# %-20s referenced\thits\n", "directory");
-
- for (ln = openDirs.list->first; ln != NULL; ln = ln->next) {
- CachedDir *dir = ln->datum;
- debug_printf("# %-20s %10d\t%4d\n", dir->name, dir->refCount,
- dir->hits);
- }
+ CachedDirListNode *ln;
+
+ debug_printf("#*** Directory Cache:\n");
+ debug_printf(
+ "# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n",
+ hits, misses, nearmisses, bigmisses,
+ percentage(hits, hits + bigmisses + nearmisses));
+ debug_printf("# refs hits directory\n");
+
+ for (ln = openDirs.list.first; ln != NULL; ln = ln->next) {
+ CachedDir *dir = ln->datum;
+ debug_printf("# %4d %4d %s\n",
+ dir->refCount, dir->hits, dir->name);
+ }
}
void
-Dir_PrintPath(SearchPath *path)
+SearchPath_Print(SearchPath *path)
{
- SearchPathNode *node;
- for (node = path->first; node != NULL; node = node->next) {
- const CachedDir *dir = node->datum;
- debug_printf("%s ", dir->name);
- }
+ SearchPathNode *ln;
+
+ for (ln = path->first; ln != NULL; ln = ln->next) {
+ const CachedDir *dir = ln->datum;
+ debug_printf("%s ", dir->name);
+ }
}
diff --git a/dir.h b/dir.h
index d0badcd1dec7..9d9002b166be 100644
--- a/dir.h
+++ b/dir.h
@@ -1,4 +1,4 @@
-/* $NetBSD: dir.h,v 1.34 2020/11/14 19:24:24 rillig Exp $ */
+/* $NetBSD: dir.h,v 1.40 2020/12/01 19:28:32 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -72,27 +72,12 @@
* from: @(#)dir.h 8.1 (Berkeley) 6/6/93
*/
-#ifndef MAKE_DIR_H
-#define MAKE_DIR_H
+#ifndef MAKE_DIR_H
+#define MAKE_DIR_H
-/* A cache for the filenames in a directory. */
-typedef struct CachedDir {
- char *name; /* Name of directory, either absolute or
- * relative to the current directory.
- * The name is not normalized in any way,
- * that is, "." and "./." are different.
- *
- * Not sure what happens when .CURDIR is
- * assigned a new value; see Parse_DoVar. */
- int refCount; /* Number of SearchPaths with this directory */
- int hits; /* The number of times a file in this
- * directory has been found */
- HashTable files; /* Hash set of files in directory;
- * all values are NULL. */
-} CachedDir;
+typedef struct CachedDir CachedDir;
void Dir_Init(void);
-void Dir_InitDir(const char *);
void Dir_InitCur(const char *);
void Dir_InitDot(void);
void Dir_End(void);
@@ -103,18 +88,17 @@ char *Dir_FindFile(const char *, SearchPath *);
char *Dir_FindHereOrAbove(const char *, const char *);
void Dir_UpdateMTime(GNode *, Boolean);
CachedDir *Dir_AddDir(SearchPath *, const char *);
-char *Dir_MakeFlags(const char *, SearchPath *);
-void Dir_ClearPath(SearchPath *);
-void Dir_Concat(SearchPath *, SearchPath *);
+char *SearchPath_ToFlags(const char *, SearchPath *);
+void SearchPath_Clear(SearchPath *);
+void SearchPath_AddAll(SearchPath *, SearchPath *);
void Dir_PrintDirectories(void);
-void Dir_PrintPath(SearchPath *);
-void Dir_Destroy(void *);
+void SearchPath_Print(SearchPath *);
SearchPath *Dir_CopyDirSearchPath(void);
/* Stripped-down variant of struct stat. */
struct cached_stat {
- time_t cst_mtime;
- mode_t cst_mode;
+ time_t cst_mtime;
+ mode_t cst_mode;
};
int cached_lstat(const char *, struct cached_stat *);
diff --git a/enum.c b/enum.c
index cce986905411..f08b4edd978d 100755
--- a/enum.c
+++ b/enum.c
@@ -1,4 +1,4 @@
-/* $NetBSD: enum.c,v 1.12 2020/10/05 19:27:47 rillig Exp $ */
+/* $NetBSD: enum.c,v 1.14 2021/01/09 16:06:09 rillig Exp $ */
/*
Copyright (c) 2020 Roland Illig <rillig@NetBSD.org>
@@ -29,13 +29,15 @@
#include "make.h"
-MAKE_RCSID("$NetBSD: enum.c,v 1.12 2020/10/05 19:27:47 rillig Exp $");
+MAKE_RCSID("$NetBSD: enum.c,v 1.14 2021/01/09 16:06:09 rillig Exp $");
-/* Convert a bitset into a string representation, showing the names of the
+/*
+ * Convert a bitset into a string representation, showing the names of the
* individual bits.
*
* Optionally, shortcuts for groups of bits can be added. To have an effect,
- * they need to be listed before their individual bits. */
+ * they need to be listed before their individual bits.
+ */
const char *
Enum_FlagsToString(char *buf, size_t buf_size,
int value, const EnumToStringSpec *spec)
@@ -86,4 +88,5 @@ Enum_ValueToString(int value, const EnumToStringSpec *spec)
return spec->es_name;
}
abort(/* unknown enum value */);
+ /*NOTREACHED*/
}
diff --git a/enum.h b/enum.h
index ba7c7a826e97..6707214e8b0d 100755
--- a/enum.h
+++ b/enum.h
@@ -1,4 +1,4 @@
-/* $NetBSD: enum.h,v 1.12 2020/09/25 15:54:50 rillig Exp $ */
+/* $NetBSD: enum.h,v 1.14 2020/12/30 10:03:16 rillig Exp $ */
/*
Copyright (c) 2020 Roland Illig <rillig@NetBSD.org>
@@ -45,8 +45,10 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *);
/* For Enum_FlagsToString, the separator between flags. */
#define ENUM__SEP "|"
-/* Generate the string that joins all possible flags, to see how large the
- * buffer must be. */
+/*
+ * Generate the string that joins all possible flags, to see how large the
+ * buffer must be.
+ */
#define ENUM__JOIN_STR_1(v1) \
#v1
#define ENUM__JOIN_STR_2(v1, v2) \
@@ -107,8 +109,10 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *);
static const EnumToStringSpec typnam ## _ ## ToStringSpecs[] = specs; \
enum { typnam ## _ ## ToStringSize = sizeof joined }
-/* Declare the necessary data structures for calling Enum_FlagsToString
- * for an enum with 2 flags. */
+/*
+ * Declare the necessary data structures for calling Enum_FlagsToString
+ * for an enum with 2 flags.
+ */
#define ENUM_FLAGS_RTTI_2(typnam, v1, v2) \
ENUM__FLAGS_RTTI(typnam, \
ENUM__SPECS_2( \
@@ -118,8 +122,10 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *);
ENUM__JOIN_STR_1(v1), \
ENUM__JOIN_STR_1(v2)))
-/* Declare the necessary data structures for calling Enum_FlagsToString
- * for an enum with 3 flags. */
+/*
+ * Declare the necessary data structures for calling Enum_FlagsToString
+ * for an enum with 3 flags.
+ */
#define ENUM_FLAGS_RTTI_3(typnam, v1, v2, v3) \
ENUM__FLAGS_RTTI(typnam, \
ENUM__SPECS_2( \
@@ -129,8 +135,23 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *);
ENUM__JOIN_STR_2(v1, v2), \
ENUM__JOIN_STR_1(v3)))
-/* Declare the necessary data structures for calling Enum_FlagsToString
- * for an enum with 6 flags. */
+/*
+ * Declare the necessary data structures for calling Enum_FlagsToString
+ * for an enum with 4 flags.
+ */
+#define ENUM_FLAGS_RTTI_4(typnam, v1, v2, v3, v4) \
+ ENUM__FLAGS_RTTI(typnam, \
+ ENUM__SPECS_2( \
+ ENUM__SPEC_2(v1, v2), \
+ ENUM__SPEC_2(v3, v4)), \
+ ENUM__JOIN_2( \
+ ENUM__JOIN_STR_2(v1, v2), \
+ ENUM__JOIN_STR_2(v3, v4)))
+
+/*
+ * Declare the necessary data structures for calling Enum_FlagsToString
+ * for an enum with 6 flags.
+ */
#define ENUM_FLAGS_RTTI_6(typnam, v1, v2, v3, v4, v5, v6) \
ENUM__FLAGS_RTTI(typnam, \
ENUM__SPECS_2( \
@@ -140,8 +161,10 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *);
ENUM__JOIN_STR_4(v1, v2, v3, v4), \
ENUM__JOIN_STR_2(v5, v6)))
-/* Declare the necessary data structures for calling Enum_FlagsToString
- * for an enum with 8 flags. */
+/*
+ * Declare the necessary data structures for calling Enum_FlagsToString
+ * for an enum with 8 flags.
+ */
#define ENUM_FLAGS_RTTI_8(typnam, v1, v2, v3, v4, v5, v6, v7, v8) \
ENUM__FLAGS_RTTI(typnam, \
ENUM__SPECS_2( \
@@ -151,16 +174,20 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *);
ENUM__JOIN_STR_4(v1, v2, v3, v4), \
ENUM__JOIN_STR_4(v5, v6, v7, v8)))
-/* Declare the necessary data structures for calling Enum_ValueToString
- * for an enum with 8 constants. */
+/*
+ * Declare the necessary data structures for calling Enum_ValueToString
+ * for an enum with 8 constants.
+ */
#define ENUM_VALUE_RTTI_8(typnam, v1, v2, v3, v4, v5, v6, v7, v8) \
ENUM__VALUE_RTTI(typnam, \
ENUM__SPECS_2( \
ENUM__SPEC_4(v1, v2, v3, v4), \
ENUM__SPEC_4(v5, v6, v7, v8)))
-/* Declare the necessary data structures for calling Enum_FlagsToString
- * for an enum with 10 flags. */
+/*
+ * Declare the necessary data structures for calling Enum_FlagsToString
+ * for an enum with 10 flags.
+ */
#define ENUM_FLAGS_RTTI_10(typnam, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) \
ENUM__FLAGS_RTTI(typnam, \
ENUM__SPECS_2( \
@@ -170,8 +197,10 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *);
ENUM__JOIN_STR_8(v1, v2, v3, v4, v5, v6, v7, v8), \
ENUM__JOIN_STR_2(v9, v10)))
-/* Declare the necessary data structures for calling Enum_FlagsToString
- * for an enum with 31 flags. */
+/*
+ * Declare the necessary data structures for calling Enum_FlagsToString
+ * for an enum with 31 flags.
+ */
#define ENUM_FLAGS_RTTI_31(typnam, \
v01, v02, v03, v04, v05, v06, v07, v08, \
v09, v10, v11, v12, v13, v14, v15, v16, \
@@ -193,8 +222,10 @@ const char *Enum_ValueToString(int, const EnumToStringSpec *);
ENUM__JOIN_STR_2(v29, v30), \
ENUM__JOIN_STR_1(v31)))
-/* Declare the necessary data structures for calling Enum_FlagsToString
- * for an enum with 32 flags. */
+/*
+ * Declare the necessary data structures for calling Enum_FlagsToString
+ * for an enum with 32 flags.
+ */
#define ENUM_FLAGS_RTTI_32(typnam, \
v01, v02, v03, v04, v05, v06, v07, v08, \
v09, v10, v11, v12, v13, v14, v15, v16, \
diff --git a/filemon/filemon.h b/filemon/filemon.h
index fcf37a3ab54b..4bd11bbde608 100644
--- a/filemon/filemon.h
+++ b/filemon/filemon.h
@@ -1,4 +1,4 @@
-/* $NetBSD: filemon.h,v 1.3 2020/10/18 11:49:47 rillig Exp $ */
+/* $NetBSD: filemon.h,v 1.4 2020/11/29 09:27:40 rillig Exp $ */
/*-
* Copyright (c) 2019 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
*/
#ifndef MAKE_FILEMON_H
-#define MAKE_FILEMON_H
+#define MAKE_FILEMON_H
#include <sys/types.h>
@@ -50,4 +50,4 @@ int filemon_setpid_child(const struct filemon *, pid_t);
int filemon_readfd(const struct filemon *);
int filemon_process(struct filemon *);
-#endif /* MAKE_FILEMON_H */
+#endif /* MAKE_FILEMON_H */
diff --git a/filemon/filemon_dev.c b/filemon/filemon_dev.c
index afedb3c57e58..e4c583417b51 100644
--- a/filemon/filemon_dev.c
+++ b/filemon/filemon_dev.c
@@ -1,4 +1,4 @@
-/* $NetBSD: filemon_dev.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */
+/* $NetBSD: filemon_dev.c,v 1.6 2020/11/29 09:27:40 rillig Exp $ */
/*-
* Copyright (c) 2020 The NetBSD Foundation, Inc.
@@ -43,7 +43,7 @@
#endif
#ifndef _PATH_FILEMON
-#define _PATH_FILEMON "/dev/filemon"
+#define _PATH_FILEMON "/dev/filemon"
#endif
struct filemon {
@@ -127,7 +127,7 @@ filemon_close(struct filemon *F)
free(F);
/* Set errno and return -1 if anything went wrong. */
- if (error) {
+ if (error != 0) {
errno = error;
return -1;
}
diff --git a/filemon/filemon_ktrace.c b/filemon/filemon_ktrace.c
index 857e72e78028..699d66bad9f3 100644
--- a/filemon/filemon_ktrace.c
+++ b/filemon/filemon_ktrace.c
@@ -1,4 +1,4 @@
-/* $NetBSD: filemon_ktrace.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */
+/* $NetBSD: filemon_ktrace.c,v 1.12 2021/01/10 23:59:53 rillig Exp $ */
/*-
* Copyright (c) 2019 The NetBSD Foundation, Inc.
@@ -29,7 +29,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
-#define _KERNTYPES /* register_t */
+#define _KERNTYPES /* register_t */
#include "filemon.h"
@@ -90,9 +90,9 @@ static filemon_syscall_t *const filemon_syscalls[] = {
};
struct filemon {
- int ktrfd; /* kernel writes ktrace events here */
- FILE *in; /* we read ktrace events from here */
- FILE *out; /* we write filemon events to here */
+ int ktrfd; /* kernel writes ktrace events here */
+ FILE *in; /* we read ktrace events from here */
+ FILE *out; /* we write filemon events to here */
rb_tree_t active;
pid_t child;
@@ -130,6 +130,7 @@ struct filemon_state {
char *path[/*npath*/];
};
+/*ARGSUSED*/
static int
compare_filemon_states(void *cookie, const void *na, const void *nb)
{
@@ -147,6 +148,7 @@ compare_filemon_states(void *cookie, const void *na, const void *nb)
return 0;
}
+/*ARGSUSED*/
static int
compare_filemon_key(void *cookie, const void *n, const void *k)
{
@@ -225,7 +227,6 @@ filemon_open(void)
/* Success! */
return F;
-fail2: __unused
(void)fclose(F->in);
fail1: (void)close(ktrpipe[0]);
(void)close(ktrpipe[1]);
@@ -262,7 +263,7 @@ filemon_closefd(struct filemon *F)
F->out = NULL;
/* Set errno and return -1 if anything went wrong. */
- if (error) {
+ if (error != 0) {
errno = error;
return -1;
}
@@ -373,7 +374,7 @@ filemon_close(struct filemon *F)
free(F);
/* Set errno and return -1 if anything went wrong. */
- if (error) {
+ if (error != 0) {
errno = error;
return -1;
}
@@ -518,12 +519,12 @@ top: /* If the child has exited, nothing to do. */
return 0;
/* If we're waiting for input, read some. */
- if (F->resid) {
+ if (F->resid > 0) {
nread = fread(F->p, 1, F->resid, F->in);
if (nread == 0) {
- if (feof(F->in))
+ if (feof(F->in) != 0)
return 0;
- assert(ferror(F->in));
+ assert(ferror(F->in) != 0);
/*
* If interrupted or would block, there may be
* more events. Otherwise fail.
@@ -538,7 +539,7 @@ top: /* If the child has exited, nothing to do. */
assert(nread <= F->resid);
F->p += nread;
F->resid -= nread;
- if (F->resid) /* may be more events */
+ if (F->resid > 0) /* may be more events */
return 1;
}
@@ -582,7 +583,7 @@ top: /* If the child has exited, nothing to do. */
}
static struct filemon_state *
-syscall_enter(struct filemon *F,
+syscall_enter(
const struct filemon_key *key, const struct ktr_syscall *call,
unsigned npath,
void (*show)(struct filemon *, const struct filemon_state *,
@@ -618,7 +619,7 @@ show_paths(struct filemon *F, const struct filemon_state *S,
* Ignore it if it failed or yielded EJUSTRETURN (-2), or if
* we're not producing output.
*/
- if (ret->ktr_error && ret->ktr_error != -2)
+ if (ret->ktr_error != 0 && ret->ktr_error != -2)
return;
if (F->out == NULL)
return;
@@ -644,7 +645,7 @@ show_retval(struct filemon *F, const struct filemon_state *S,
* Ignore it if it failed or yielded EJUSTRETURN (-2), or if
* we're not producing output.
*/
- if (ret->ktr_error && ret->ktr_error != -2)
+ if (ret->ktr_error != 0 && ret->ktr_error != -2)
return;
if (F->out == NULL)
return;
@@ -664,7 +665,7 @@ static void
show_execve(struct filemon *F, const struct filemon_state *S,
const struct ktr_sysret *ret)
{
- return show_paths(F, S, ret, "E");
+ show_paths(F, S, ret, "E");
}
static void
@@ -752,18 +753,20 @@ show_rename(struct filemon *F, const struct filemon_state *S,
show_paths(F, S, ret, "M");
}
+/*ARGSUSED*/
static struct filemon_state *
filemon_sys_chdir(struct filemon *F, const struct filemon_key *key,
const struct ktr_syscall *call)
{
- return syscall_enter(F, key, call, 1, &show_chdir);
+ return syscall_enter(key, call, 1, &show_chdir);
}
+/*ARGSUSED*/
static struct filemon_state *
filemon_sys_execve(struct filemon *F, const struct filemon_key *key,
const struct ktr_syscall *call)
{
- return syscall_enter(F, key, call, 1, &show_execve);
+ return syscall_enter(key, call, 1, &show_execve);
}
static struct filemon_state *
@@ -773,7 +776,7 @@ filemon_sys_exit(struct filemon *F, const struct filemon_key *key,
const register_t *args = (const void *)&call[1];
int status = (int)args[0];
- if (F->out) {
+ if (F->out != NULL) {
fprintf(F->out, "X %jd %d\n", (intmax_t)key->pid, status);
if (key->pid == F->child) {
fprintf(F->out, "# Bye bye\n");
@@ -783,20 +786,23 @@ filemon_sys_exit(struct filemon *F, const struct filemon_key *key,
return NULL;
}
+/*ARGSUSED*/
static struct filemon_state *
filemon_sys_fork(struct filemon *F, const struct filemon_key *key,
const struct ktr_syscall *call)
{
- return syscall_enter(F, key, call, 0, &show_fork);
+ return syscall_enter(key, call, 0, &show_fork);
}
+/*ARGSUSED*/
static struct filemon_state *
filemon_sys_link(struct filemon *F, const struct filemon_key *key,
const struct ktr_syscall *call)
{
- return syscall_enter(F, key, call, 2, &show_link);
+ return syscall_enter(key, call, 2, &show_link);
}
+/*ARGSUSED*/
static struct filemon_state *
filemon_sys_open(struct filemon *F, const struct filemon_key *key,
const struct ktr_syscall *call)
@@ -809,15 +815,16 @@ filemon_sys_open(struct filemon *F, const struct filemon_key *key,
flags = (int)args[1];
if ((flags & O_RDWR) == O_RDWR)
- return syscall_enter(F, key, call, 1, &show_open_readwrite);
+ return syscall_enter(key, call, 1, &show_open_readwrite);
else if ((flags & O_WRONLY) == O_WRONLY)
- return syscall_enter(F, key, call, 1, &show_open_write);
+ return syscall_enter(key, call, 1, &show_open_write);
else if ((flags & O_RDONLY) == O_RDONLY)
- return syscall_enter(F, key, call, 1, &show_open_read);
+ return syscall_enter(key, call, 1, &show_open_read);
else
return NULL; /* XXX Do we care if no read or write? */
}
+/*ARGSUSED*/
static struct filemon_state *
filemon_sys_openat(struct filemon *F, const struct filemon_key *key,
const struct ktr_syscall *call)
@@ -832,47 +839,47 @@ filemon_sys_openat(struct filemon *F, const struct filemon_key *key,
if (fd == AT_CWD) {
if ((flags & O_RDWR) == O_RDWR)
- return syscall_enter(F, key, call, 1,
+ return syscall_enter(key, call, 1,
&show_open_readwrite);
else if ((flags & O_WRONLY) == O_WRONLY)
- return syscall_enter(F, key, call, 1,
- &show_open_write);
+ return syscall_enter(key, call, 1, &show_open_write);
else if ((flags & O_RDONLY) == O_RDONLY)
- return syscall_enter(F, key, call, 1, &show_open_read);
+ return syscall_enter(key, call, 1, &show_open_read);
else
return NULL;
} else {
if ((flags & O_RDWR) == O_RDWR)
- return syscall_enter(F, key, call, 1,
+ return syscall_enter(key, call, 1,
&show_openat_readwrite);
else if ((flags & O_WRONLY) == O_WRONLY)
- return syscall_enter(F, key, call, 1,
- &show_openat_write);
+ return syscall_enter(key, call, 1, &show_openat_write);
else if ((flags & O_RDONLY) == O_RDONLY)
- return syscall_enter(F, key, call, 1,
- &show_openat_read);
+ return syscall_enter(key, call, 1, &show_openat_read);
else
return NULL;
}
}
+/*ARGSUSED*/
static struct filemon_state *
filemon_sys_symlink(struct filemon *F, const struct filemon_key *key,
const struct ktr_syscall *call)
{
- return syscall_enter(F, key, call, 2, &show_symlink);
+ return syscall_enter(key, call, 2, &show_symlink);
}
+/*ARGSUSED*/
static struct filemon_state *
filemon_sys_unlink(struct filemon *F, const struct filemon_key *key,
const struct ktr_syscall *call)
{
- return syscall_enter(F, key, call, 1, &show_unlink);
+ return syscall_enter(key, call, 1, &show_unlink);
}
+/*ARGSUSED*/
static struct filemon_state *
filemon_sys_rename(struct filemon *F, const struct filemon_key *key,
const struct ktr_syscall *call)
{
- return syscall_enter(F, key, call, 2, &show_rename);
+ return syscall_enter(key, call, 2, &show_rename);
}
diff --git a/for.c b/for.c
index 8730ab5c157d..af55179dee8d 100644
--- a/for.c
+++ b/for.c
@@ -1,4 +1,4 @@
-/* $NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $ */
+/* $NetBSD: for.c,v 1.134 2021/01/10 21:20:46 rillig Exp $ */
/*
* Copyright (c) 1992, The Regents of the University of California.
@@ -32,24 +32,22 @@
/*-
* Handling of .for/.endfor loops in a makefile.
*
- * For loops are of the form:
+ * For loops have the form:
*
- * .for <varname...> in <value...>
- * ...
- * .endfor
- *
- * When a .for line is parsed, all following lines are accumulated into a
- * buffer, up to but excluding the corresponding .endfor line. To find the
- * corresponding .endfor, the number of nested .for and .endfor directives
- * are counted.
+ * .for <varname...> in <value...>
+ * # the body
+ * .endfor
*
- * During parsing, any nested .for loops are just passed through; they get
- * handled recursively in For_Eval when the enclosing .for loop is evaluated
- * in For_Run.
+ * When a .for line is parsed, the following lines are copied to the body of
+ * the .for loop, until the corresponding .endfor line is reached. In this
+ * phase, the body is not yet evaluated. This also applies to any nested
+ * .for loops.
*
- * When the .for loop has been parsed completely, the variable expressions
- * for the iteration variables are replaced with expressions of the form
- * ${:Uvalue}, and then this modified body is "included" as a special file.
+ * After reaching the .endfor, the values from the .for line are grouped
+ * according to the number of variables. For each such group, the unexpanded
+ * body is scanned for variable expressions, and those that match the variable
+ * names are replaced with expressions of the form ${:U...} or $(:U...).
+ * After that, the body is treated like a file from an .include directive.
*
* Interface:
* For_Eval Evaluate the loop in the passed line.
@@ -60,29 +58,29 @@
#include "make.h"
/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $");
+MAKE_RCSID("$NetBSD: for.c,v 1.134 2021/01/10 21:20:46 rillig Exp $");
static int forLevel = 0; /* Nesting level */
/* One of the variables to the left of the "in" in a .for loop. */
typedef struct ForVar {
- char *name;
- size_t len;
+ char *name;
+ size_t nameLen;
} ForVar;
/*
* State of a for loop.
*/
typedef struct For {
- Buffer body; /* Unexpanded body of the loop */
- Vector /* of ForVar */ vars; /* Iteration variables */
- Words items; /* Substitution items */
- Buffer curBody; /* Expanded body of the current iteration */
- /* Is any of the names 1 character long? If so, when the variable values
- * are substituted, the parser must handle $V expressions as well, not
- * only ${V} and $(V). */
- Boolean short_var;
- unsigned int sub_next; /* Where to continue iterating */
+ Buffer body; /* Unexpanded body of the loop */
+ Vector /* of ForVar */ vars; /* Iteration variables */
+ Words items; /* Substitution items */
+ Buffer curBody; /* Expanded body of the current iteration */
+ /* Is any of the names 1 character long? If so, when the variable values
+ * are substituted, the parser must handle $V expressions as well, not
+ * only ${V} and $(V). */
+ Boolean short_var;
+ unsigned int sub_next; /* Where to continue iterating */
} For;
static For *accumFor; /* Loop being accumulated */
@@ -90,42 +88,43 @@ static For *accumFor; /* Loop being accumulated */
static void
ForAddVar(For *f, const char *name, size_t len)
{
- ForVar *var = Vector_Push(&f->vars);
- var->name = bmake_strldup(name, len);
- var->len = len;
+ ForVar *var = Vector_Push(&f->vars);
+ var->name = bmake_strldup(name, len);
+ var->nameLen = len;
}
static void
For_Free(For *f)
{
- Buf_Destroy(&f->body, TRUE);
+ Buf_Destroy(&f->body, TRUE);
- while (f->vars.len > 0) {
- ForVar *var = Vector_Pop(&f->vars);
- free(var->name);
- }
- Vector_Done(&f->vars);
+ while (f->vars.len > 0) {
+ ForVar *var = Vector_Pop(&f->vars);
+ free(var->name);
+ }
+ Vector_Done(&f->vars);
- Words_Free(f->items);
- Buf_Destroy(&f->curBody, TRUE);
+ Words_Free(f->items);
+ Buf_Destroy(&f->curBody, TRUE);
- free(f);
+ free(f);
}
static Boolean
IsFor(const char *p)
{
- return p[0] == 'f' && p[1] == 'o' && p[2] == 'r' && ch_isspace(p[3]);
+ return p[0] == 'f' && p[1] == 'o' && p[2] == 'r' && ch_isspace(p[3]);
}
static Boolean
IsEndfor(const char *p)
{
- return p[0] == 'e' && strncmp(p, "endfor", 6) == 0 &&
- (p[6] == '\0' || ch_isspace(p[6]));
+ return p[0] == 'e' && strncmp(p, "endfor", 6) == 0 &&
+ (p[6] == '\0' || ch_isspace(p[6]));
}
-/* Evaluate the for loop in the passed line. The line looks like this:
+/*
+ * Evaluate the for loop in the passed line. The line looks like this:
* .for <varname...> in <value...>
*
* Input:
@@ -139,98 +138,108 @@ IsEndfor(const char *p)
int
For_Eval(const char *line)
{
- For *f;
- const char *p;
+ For *f;
+ const char *p;
- p = line + 1; /* skip the '.' */
- cpp_skip_whitespace(&p);
+ p = line + 1; /* skip the '.' */
+ cpp_skip_whitespace(&p);
- if (!IsFor(p)) {
- if (IsEndfor(p)) {
- Parse_Error(PARSE_FATAL, "for-less endfor");
- return -1;
+ if (!IsFor(p)) {
+ if (IsEndfor(p)) {
+ Parse_Error(PARSE_FATAL, "for-less endfor");
+ return -1;
+ }
+ return 0;
+ }
+ p += 3;
+
+ /*
+ * we found a for loop, and now we are going to parse it.
+ */
+
+ f = bmake_malloc(sizeof *f);
+ Buf_Init(&f->body);
+ Vector_Init(&f->vars, sizeof(ForVar));
+ f->items.words = NULL;
+ f->items.freeIt = NULL;
+ Buf_Init(&f->curBody);
+ f->short_var = FALSE;
+ f->sub_next = 0;
+
+ /* Grab the variables. Terminate on "in". */
+ for (;;) {
+ size_t len;
+
+ cpp_skip_whitespace(&p);
+ if (*p == '\0') {
+ Parse_Error(PARSE_FATAL, "missing `in' in for");
+ For_Free(f);
+ return -1;
+ }
+
+ /*
+ * XXX: This allows arbitrary variable names;
+ * see directive-for.mk.
+ */
+ for (len = 1; p[len] != '\0' && !ch_isspace(p[len]); len++)
+ continue;
+
+ if (len == 2 && p[0] == 'i' && p[1] == 'n') {
+ p += 2;
+ break;
+ }
+ if (len == 1)
+ f->short_var = TRUE;
+
+ ForAddVar(f, p, len);
+ p += len;
}
- return 0;
- }
- p += 3;
-
- /*
- * we found a for loop, and now we are going to parse it.
- */
-
- f = bmake_malloc(sizeof *f);
- Buf_Init(&f->body);
- Vector_Init(&f->vars, sizeof(ForVar));
- f->items.words = NULL;
- f->items.freeIt = NULL;
- Buf_Init(&f->curBody);
- f->short_var = FALSE;
- f->sub_next = 0;
-
- /* Grab the variables. Terminate on "in". */
- for (;;) {
- size_t len;
- cpp_skip_whitespace(&p);
- if (*p == '\0') {
- Parse_Error(PARSE_FATAL, "missing `in' in for");
- For_Free(f);
- return -1;
+ if (f->vars.len == 0) {
+ Parse_Error(PARSE_FATAL, "no iteration variables in for");
+ For_Free(f);
+ return -1;
}
- /* XXX: This allows arbitrary variable names; see directive-for.mk. */
- for (len = 1; p[len] != '\0' && !ch_isspace(p[len]); len++)
- continue;
+ cpp_skip_whitespace(&p);
+
+ {
+ char *items;
+ if (Var_Subst(p, VAR_GLOBAL, VARE_WANTRES, &items) != VPR_OK) {
+ Parse_Error(PARSE_FATAL, "Error in .for loop items");
+ f->items.len = 0;
+ goto done;
+ }
+
+ f->items = Str_Words(items, FALSE);
+ free(items);
- if (len == 2 && p[0] == 'i' && p[1] == 'n') {
- p += 2;
- break;
+ if (f->items.len == 1 && f->items.words[0][0] == '\0')
+ f->items.len = 0; /* .for var in ${:U} */
}
- if (len == 1)
- f->short_var = TRUE;
-
- ForAddVar(f, p, len);
- p += len;
- }
-
- if (f->vars.len == 0) {
- Parse_Error(PARSE_FATAL, "no iteration variables in for");
- For_Free(f);
- return -1;
- }
-
- cpp_skip_whitespace(&p);
-
- {
- char *items;
- (void)Var_Subst(p, VAR_GLOBAL, VARE_WANTRES, &items);
- /* TODO: handle errors */
- f->items = Str_Words(items, FALSE);
- free(items);
-
- if (f->items.len == 1 && f->items.words[0][0] == '\0')
- f->items.len = 0; /* .for var in ${:U} */
- }
-
- {
- size_t nitems, nvars;
-
- if ((nitems = f->items.len) > 0 && nitems % (nvars = f->vars.len)) {
- Parse_Error(PARSE_FATAL,
- "Wrong number of words (%zu) in .for substitution list"
- " with %zu variables", nitems, nvars);
- /*
- * Return 'success' so that the body of the .for loop is
- * accumulated.
- * Remove all items so that the loop doesn't iterate.
- */
- f->items.len = 0;
+
+ {
+ size_t nitems, nvars;
+
+ if ((nitems = f->items.len) > 0 &&
+ nitems % (nvars = f->vars.len) != 0) {
+ Parse_Error(PARSE_FATAL,
+ "Wrong number of words (%u) in .for "
+ "substitution list with %u variables",
+ (unsigned)nitems, (unsigned)nvars);
+ /*
+ * Return 'success' so that the body of the .for loop
+ * is accumulated.
+ * Remove all items so that the loop doesn't iterate.
+ */
+ f->items.len = 0;
+ }
}
- }
- accumFor = f;
- forLevel = 1;
- return 1;
+done:
+ accumFor = f;
+ forLevel = 1;
+ return 1;
}
/*
@@ -240,240 +249,256 @@ For_Eval(const char *line)
Boolean
For_Accum(const char *line)
{
- const char *ptr = line;
-
- if (*ptr == '.') {
- ptr++;
- cpp_skip_whitespace(&ptr);
-
- if (IsEndfor(ptr)) {
- DEBUG1(FOR, "For: end for %d\n", forLevel);
- if (--forLevel <= 0)
- return FALSE;
- } else if (IsFor(ptr)) {
- forLevel++;
- DEBUG1(FOR, "For: new loop %d\n", forLevel);
+ const char *ptr = line;
+
+ if (*ptr == '.') {
+ ptr++;
+ cpp_skip_whitespace(&ptr);
+
+ if (IsEndfor(ptr)) {
+ DEBUG1(FOR, "For: end for %d\n", forLevel);
+ if (--forLevel <= 0)
+ return FALSE;
+ } else if (IsFor(ptr)) {
+ forLevel++;
+ DEBUG1(FOR, "For: new loop %d\n", forLevel);
+ }
}
- }
- Buf_AddStr(&accumFor->body, line);
- Buf_AddByte(&accumFor->body, '\n');
- return TRUE;
+ Buf_AddStr(&accumFor->body, line);
+ Buf_AddByte(&accumFor->body, '\n');
+ return TRUE;
}
static size_t
for_var_len(const char *var)
{
- char ch, var_start, var_end;
- int depth;
- size_t len;
-
- var_start = *var;
- if (var_start == '\0')
- /* just escape the $ */
- return 0;
-
- if (var_start == '(')
- var_end = ')';
- else if (var_start == '{')
- var_end = '}';
- else
- /* Single char variable */
- return 1;
+ char ch, var_start, var_end;
+ int depth;
+ size_t len;
- depth = 1;
- for (len = 1; (ch = var[len++]) != '\0';) {
- if (ch == var_start)
- depth++;
- else if (ch == var_end && --depth == 0)
- return len;
- }
+ var_start = *var;
+ if (var_start == '\0')
+ /* just escape the $ */
+ return 0;
+
+ if (var_start == '(')
+ var_end = ')';
+ else if (var_start == '{')
+ var_end = '}';
+ else
+ return 1; /* Single char variable */
+
+ depth = 1;
+ for (len = 1; (ch = var[len++]) != '\0';) {
+ if (ch == var_start)
+ depth++;
+ else if (ch == var_end && --depth == 0)
+ return len;
+ }
- /* Variable end not found, escape the $ */
- return 0;
+ /* Variable end not found, escape the $ */
+ return 0;
}
-/* The .for loop substitutes the items as ${:U<value>...}, which means
- * that characters that break this syntax must be backslash-escaped. */
+/*
+ * The .for loop substitutes the items as ${:U<value>...}, which means
+ * that characters that break this syntax must be backslash-escaped.
+ */
static Boolean
NeedsEscapes(const char *word, char endc)
{
- const char *p;
+ const char *p;
- for (p = word; *p != '\0'; p++) {
- if (*p == ':' || *p == '$' || *p == '\\' || *p == endc)
- return TRUE;
- }
- return FALSE;
+ for (p = word; *p != '\0'; p++) {
+ if (*p == ':' || *p == '$' || *p == '\\' || *p == endc)
+ return TRUE;
+ }
+ return FALSE;
}
-/* While expanding the body of a .for loop, write the item in the ${:U...}
+/*
+ * 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. */
+ * The result is later unescaped by ApplyModifier_Defined.
+ */
static void
-Buf_AddEscaped(Buffer *cmds, const char *item, char ech)
+Buf_AddEscaped(Buffer *cmds, const char *item, char endc)
{
- char ch;
+ char ch;
- if (!NeedsEscapes(item, ech)) {
- Buf_AddStr(cmds, item);
- return;
- }
-
- /* Escape ':', '$', '\\' and 'ech' - these will be removed later by
- * :U processing, see ApplyModifier_Defined. */
- while ((ch = *item++) != '\0') {
- if (ch == '$') {
- size_t len = for_var_len(item);
- if (len != 0) {
- Buf_AddBytes(cmds, item - 1, len + 1);
- item += len;
- continue;
- }
- Buf_AddByte(cmds, '\\');
- } else if (ch == ':' || ch == '\\' || ch == ech)
- Buf_AddByte(cmds, '\\');
- Buf_AddByte(cmds, ch);
- }
+ if (!NeedsEscapes(item, endc)) {
+ Buf_AddStr(cmds, item);
+ return;
+ }
+
+ /* Escape ':', '$', '\\' and 'endc' - these will be removed later by
+ * :U processing, see ApplyModifier_Defined. */
+ while ((ch = *item++) != '\0') {
+ if (ch == '$') {
+ size_t len = for_var_len(item);
+ if (len != 0) {
+ Buf_AddBytes(cmds, item - 1, len + 1);
+ item += len;
+ continue;
+ }
+ Buf_AddByte(cmds, '\\');
+ } else if (ch == ':' || ch == '\\' || ch == endc)
+ Buf_AddByte(cmds, '\\');
+ Buf_AddByte(cmds, ch);
+ }
}
-/* While expanding the body of a .for loop, replace expressions like
- * ${i}, ${i:...}, $(i) or $(i:...) with their ${:U...} expansion. */
+/*
+ * While expanding the body of a .for loop, replace the variable name of an
+ * expression like ${i} or ${i:...} or $(i) or $(i:...) with ":Uvalue".
+ */
static void
-SubstVarLong(For *f, const char **pp, const char **inout_mark, char ech)
+SubstVarLong(For *f, const char **pp, const char *bodyEnd, char endc,
+ const char **inout_mark)
{
- size_t i;
- const char *p = *pp;
-
- for (i = 0; i < f->vars.len; i++) {
- ForVar *forVar = Vector_Get(&f->vars, i);
- char *var = forVar->name;
- size_t vlen = forVar->len;
-
- /* XXX: undefined behavior for p if vlen is longer than p? */
- if (memcmp(p, var, vlen) != 0)
- continue;
- /* XXX: why test for backslash here? */
- if (p[vlen] != ':' && p[vlen] != ech && p[vlen] != '\\')
- continue;
-
- /* Found a variable match. Replace with :U<value> */
- Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
- Buf_AddStr(&f->curBody, ":U");
- Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], ech);
-
- p += vlen;
- *inout_mark = p;
- break;
- }
-
- *pp = p;
+ size_t i;
+ const char *p = *pp;
+
+ for (i = 0; i < f->vars.len; i++) {
+ ForVar *forVar = Vector_Get(&f->vars, i);
+ char *varname = forVar->name;
+ size_t varnameLen = forVar->nameLen;
+
+ if (varnameLen >= (size_t)(bodyEnd - p))
+ continue;
+ if (memcmp(p, varname, varnameLen) != 0)
+ continue;
+ /* XXX: why test for backslash here? */
+ if (p[varnameLen] != ':' && p[varnameLen] != endc &&
+ p[varnameLen] != '\\')
+ continue;
+
+ /*
+ * Found a variable match. Skip over the variable name and
+ * instead add ':U<value>' to the current body.
+ */
+ Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
+ Buf_AddStr(&f->curBody, ":U");
+ Buf_AddEscaped(&f->curBody,
+ f->items.words[f->sub_next + i], endc);
+
+ p += varnameLen;
+ *inout_mark = p;
+ *pp = p;
+ return;
+ }
}
-/* While expanding the body of a .for loop, replace single-character
- * variable expressions like $i with their ${:U...} expansion. */
+/*
+ * While expanding the body of a .for loop, replace single-character
+ * variable expressions like $i with their ${:U...} expansion.
+ */
static void
-SubstVarShort(For *f, char const ch, const char **pp, const char **inout_mark)
+SubstVarShort(For *f, const char *p, const char **inout_mark)
{
- const char *p = *pp;
- size_t i;
-
- /* Probably a single character name, ignore $$ and stupid ones. */
- if (!f->short_var || strchr("}):$", ch) != NULL) {
- p++;
- *pp = p;
+ const char ch = *p;
+ ForVar *vars;
+ size_t i;
+
+ /* Skip $$ and stupid ones. */
+ if (!f->short_var || strchr("}):$", ch) != NULL)
+ return;
+
+ vars = Vector_Get(&f->vars, 0);
+ for (i = 0; i < f->vars.len; i++) {
+ const char *varname = vars[i].name;
+ if (varname[0] == ch && varname[1] == '\0')
+ goto found;
+ }
return;
- }
-
- for (i = 0; i < f->vars.len; i++) {
- ForVar *var = Vector_Get(&f->vars, i);
- const char *varname = var->name;
- if (varname[0] != ch || varname[1] != '\0')
- continue;
- /* Found a variable match. Replace with ${:U<value>} */
- Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
+found:
+ /* Replace $<ch> with ${:U<value>} */
+ Buf_AddBytesBetween(&f->curBody, *inout_mark, p), *inout_mark = p + 1;
Buf_AddStr(&f->curBody, "{:U");
Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], '}');
Buf_AddByte(&f->curBody, '}');
-
- *inout_mark = ++p;
- break;
- }
-
- *pp = p;
}
/*
- * Scan the for loop body and replace references to the loop variables
- * with variable references that expand to the required text.
+ * Compute the body for the current iteration by copying the unexpanded body,
+ * replacing the expressions for the iteration variables on the way.
*
- * Using variable expansions ensures that the .for loop can't generate
+ * Using variable expressions ensures that the .for loop can't generate
* syntax, and that the later parsing will still see a variable.
- * We assume that the null variable will never be defined.
+ * This code assumes that the variable with the empty name will never be
+ * defined, see unit-tests/varname-empty.mk for more details.
*
* The detection of substitutions of the loop control variable is naive.
* Many of the modifiers use \ to escape $ (not $) so it is possible
* to contrive a makefile where an unwanted substitution happens.
*/
-static char *
-ForIterate(void *v_arg, size_t *out_len)
+static void
+ForSubstBody(For *f)
{
- For *f = v_arg;
- const char *p;
- const char *mark; /* where the last replacement left off */
- const char *body_end;
- char *cmds_str;
-
- if (f->sub_next + f->vars.len > f->items.len) {
- /* No more iterations */
- For_Free(f);
- return NULL;
- }
-
- Buf_Empty(&f->curBody);
-
- mark = Buf_GetAll(&f->body, NULL);
- body_end = mark + Buf_Len(&f->body);
- for (p = mark; (p = strchr(p, '$')) != NULL;) {
- char ch, ech;
- ch = *++p;
- if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) {
- p++;
- /* Check variable name against the .for loop variables */
- SubstVarLong(f, &p, &mark, ech);
- continue;
+ const char *p, *bodyEnd;
+ const char *mark; /* where the last replacement left off */
+
+ Buf_Empty(&f->curBody);
+
+ mark = f->body.data;
+ bodyEnd = f->body.data + f->body.len;
+ for (p = mark; (p = strchr(p, '$')) != NULL;) {
+ if (p[1] == '{' || p[1] == '(') {
+ p += 2;
+ SubstVarLong(f, &p, bodyEnd, p[-1] == '{' ? '}' : ')',
+ &mark);
+ } else if (p[1] != '\0') {
+ SubstVarShort(f, p + 1, &mark);
+ p += 2;
+ } else
+ break;
}
- if (ch == '\0')
- break;
- SubstVarShort(f, ch, &p, &mark);
- }
- Buf_AddBytesBetween(&f->curBody, mark, body_end);
+ Buf_AddBytesBetween(&f->curBody, mark, bodyEnd);
+}
- *out_len = Buf_Len(&f->curBody);
- cmds_str = Buf_GetAll(&f->curBody, NULL);
- DEBUG1(FOR, "For: loop body:\n%s", cmds_str);
+/*
+ * 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)
+{
+ For *f = v_arg;
+
+ if (f->sub_next == f->items.len) {
+ /* No more iterations */
+ For_Free(f);
+ return NULL;
+ }
- f->sub_next += f->vars.len;
+ ForSubstBody(f);
+ DEBUG1(FOR, "For: loop body:\n%s", f->curBody.data);
+ f->sub_next += (unsigned int)f->vars.len;
- return cmds_str;
+ *out_len = f->curBody.len;
+ return f->curBody.data;
}
-/* Run the for loop, imitating the actions of an include file. */
+/* Run the .for loop, imitating the actions of an include file. */
void
For_Run(int lineno)
{
- For *f = accumFor;
- accumFor = NULL;
-
- if (f->items.len == 0) {
- /* Nothing to expand - possibly due to an earlier syntax error. */
- For_Free(f);
- return;
- }
+ For *f = accumFor;
+ accumFor = NULL;
+
+ if (f->items.len == 0) {
+ /*
+ * Nothing to expand - possibly due to an earlier syntax
+ * error.
+ */
+ For_Free(f);
+ return;
+ }
- Parse_SetInput(NULL, lineno, -1, ForIterate, f);
+ Parse_SetInput(NULL, lineno, -1, ForReadMore, f);
}
diff --git a/hash.c b/hash.c
index a1e3ad3b45b4..82735f59c6fd 100644
--- a/hash.c
+++ b/hash.c
@@ -1,4 +1,4 @@
-/* $NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig Exp $ */
+/* $NetBSD: hash.c,v 1.60 2020/12/30 10:03:16 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.57 2020/11/14 21:29:44 rillig Exp $");
+MAKE_RCSID("$NetBSD: hash.c,v 1.60 2020/12/30 10:03:16 rillig Exp $");
/*
* The ratio of # entries to # buckets at which we rebuild the table to
@@ -86,10 +86,13 @@ MAKE_RCSID("$NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig Exp $");
static unsigned int
hash(const char *key, size_t *out_keylen)
{
- unsigned int h = 0;
- const char *p = key;
- while (*p != '\0')
- h = (h << 5) - h + (unsigned char)*p++;
+ unsigned int h;
+ const char *p;
+
+ h = 0;
+ for (p = key; *p != '\0'; p++)
+ h = 31 * h + (unsigned char)*p;
+
if (out_keylen != NULL)
*out_keylen = (size_t)(p - key);
return h;
@@ -98,7 +101,7 @@ hash(const char *key, size_t *out_keylen)
unsigned int
Hash_Hash(const char *key)
{
- return hash(key, NULL);
+ return hash(key, NULL);
}
static HashEntry *
@@ -177,8 +180,10 @@ HashTable_FindValue(HashTable *t, const char *key)
return he != NULL ? he->value : NULL;
}
-/* Find the value corresponding to the key and the precomputed hash,
- * or return NULL. */
+/*
+ * Find the value corresponding to the key and the precomputed hash,
+ * or return NULL.
+ */
void *
HashTable_FindValueHash(HashTable *t, const char *key, unsigned int h)
{
@@ -186,8 +191,10 @@ HashTable_FindValueHash(HashTable *t, const char *key, unsigned int h)
return he != NULL ? he->value : NULL;
}
-/* Make the hash table larger. Any bucket numbers from the old table become
- * invalid; the hash codes stay valid though. */
+/*
+ * Make the hash table larger. Any bucket numbers from the old table become
+ * invalid; the hash codes stay valid though.
+ */
static void
HashTable_Enlarge(HashTable *t)
{
@@ -221,8 +228,10 @@ HashTable_Enlarge(HashTable *t)
t->maxchain = 0;
}
-/* Find or create an entry corresponding to the key.
- * Return in out_isNew whether a new entry has been created. */
+/*
+ * Find or create an entry corresponding to the key.
+ * Return in out_isNew whether a new entry has been created.
+ */
HashEntry *
HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew)
{
@@ -288,8 +297,10 @@ HashIter_Init(HashIter *hi, HashTable *t)
hi->entry = NULL;
}
-/* Return the next entry in the hash table, or NULL if the end of the table
- * is reached. */
+/*
+ * Return the next entry in the hash table, or NULL if the end of the table
+ * is reached.
+ */
HashEntry *
HashIter_Next(HashIter *hi)
{
diff --git a/hash.h b/hash.h
index 30a8485bbd2e..b101137aa0ce 100644
--- a/hash.h
+++ b/hash.h
@@ -1,4 +1,4 @@
-/* $NetBSD: hash.h,v 1.33 2020/11/14 21:29:44 rillig Exp $ */
+/* $NetBSD: hash.h,v 1.38 2020/12/15 01:23:55 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -74,45 +74,50 @@
/* Hash tables with strings as keys and arbitrary pointers as values. */
-#ifndef MAKE_HASH_H
-#define MAKE_HASH_H
+#ifndef MAKE_HASH_H
+#define MAKE_HASH_H
/* A single key-value entry in the hash table. */
typedef struct HashEntry {
- struct HashEntry *next; /* Used to link together all the entries
+ struct HashEntry *next; /* Used to link together all the entries
* associated with the same bucket. */
- void *value;
- unsigned int key_hash; /* hash value of the key */
- char key[1]; /* key string, variable length */
+ void *value;
+ unsigned int key_hash; /* hash value of the key */
+ char key[1]; /* key string, variable length */
} HashEntry;
/* The hash table containing the entries. */
typedef struct HashTable {
- HashEntry **buckets; /* Pointers to HashEntry, one
+ HashEntry **buckets; /* Pointers to HashEntry, one
* for each bucket in the table. */
- unsigned int bucketsSize;
- unsigned int numEntries; /* Number of entries in the table. */
- unsigned int bucketsMask; /* Used to select the bucket for a hash. */
- unsigned int maxchain; /* max length of chain detected */
+ unsigned int bucketsSize;
+ unsigned int numEntries; /* Number of entries in the table. */
+ unsigned int bucketsMask; /* Used to select the bucket for a hash. */
+ unsigned int maxchain; /* max length of chain detected */
} HashTable;
/* State of an iteration over all entries in a table. */
typedef struct HashIter {
- HashTable *table; /* Table being searched. */
- unsigned int nextBucket; /* Next bucket to check (after current). */
- HashEntry *entry; /* Next entry to check in current bucket. */
+ HashTable *table; /* Table being searched. */
+ unsigned int nextBucket; /* Next bucket to check (after current). */
+ HashEntry *entry; /* Next entry to check in current bucket. */
} HashIter;
+/* A set of strings. */
+typedef struct HashSet {
+ HashTable tbl;
+} HashSet;
+
MAKE_INLINE void *
HashEntry_Get(HashEntry *h)
{
- return h->value;
+ return h->value;
}
MAKE_INLINE void
HashEntry_Set(HashEntry *h, void *datum)
{
- h->value = datum;
+ h->value = datum;
}
void HashTable_Init(HashTable *);
@@ -129,4 +134,37 @@ void HashTable_DebugStats(HashTable *, const char *);
void HashIter_Init(HashIter *, HashTable *);
HashEntry *HashIter_Next(HashIter *);
+MAKE_INLINE void
+HashSet_Init(HashSet *set)
+{
+ HashTable_Init(&set->tbl);
+}
+
+MAKE_INLINE void
+HashSet_Done(HashSet *set)
+{
+ HashTable_Done(&set->tbl);
+}
+
+MAKE_INLINE Boolean
+HashSet_Add(HashSet *set, const char *key)
+{
+ Boolean isNew;
+
+ (void)HashTable_CreateEntry(&set->tbl, key, &isNew);
+ return isNew;
+}
+
+MAKE_INLINE Boolean
+HashSet_Contains(HashSet *set, const char *key)
+{
+ return HashTable_FindEntry(&set->tbl, key) != NULL;
+}
+
+MAKE_INLINE void
+HashIter_InitSet(HashIter *hi, HashSet *set)
+{
+ HashIter_Init(hi, &set->tbl);
+}
+
#endif /* MAKE_HASH_H */
diff --git a/import.sh b/import.sh
new file mode 100755
index 000000000000..d4554a078951
--- /dev/null
+++ b/import.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+# Import bmake
+
+ECHO=
+GIT=${GIT:-git}
+
+# For consistency...
+Error() {
+ echo ERROR: ${1+"$@"} >&2
+ exit 1
+}
+
+Cd() {
+ [ $# -eq 1 ] || Error "Cd() takes a single parameter."
+ cd $1 || Error "cannot \"cd $1\" from $PWD"
+}
+
+# Call this function and then follow it by any specific import script additions
+option_parsing() {
+ local _shift=$#
+ # Parse command line options
+ while :
+ do
+ case "$1" in
+ *=*) eval "$1"; shift;;
+ --) shift; break;;
+ -a) TARBALL=$2; shift 2;;
+ -n) ECHO=echo; shift;;
+ -P) PR=$2; shift 2;;
+ -r) REVIEWER=$2; shift 2;;
+ -u) url=$2; shift 2;;
+ -h) echo "Usage:";
+ echo " "$0 '[-ahnPr] [TARBALL=] [PR=] [REVIEWER=]'
+ echo " "$0 '-a <filename> # (a)rchive'
+ echo " "$0 '-h # print usage'
+ echo " "$0 '-n # do not import, check only.'
+ echo " "$0 '-P <PR Number> # Use PR'
+ echo " "$0 '-r <reviewer(s) list> # (r)eviewed by'
+ echo " "$0 'PR=<PR Number>'
+ echo " "$0 'REVIEWER=<reviewer(s) list>'
+ exit 1;;
+ *) break;;
+ esac
+ done
+ return $(($_shift - $#))
+}
+
+###
+
+option_parsing "$@"
+shift $?
+
+TF=/tmp/.$USER.$$
+Cd `dirname $0`
+test -s ${TARBALL:-/dev/null} || Error need TARBALL
+here=`pwd`
+# thing should match what the TARBALL contains
+thing=`basename $here`
+
+case "$thing" in
+bmake) (cd .. && tar zxf $TARBALL);;
+*) Error "we should be in bmake";;
+esac
+
+VERSION=`grep '^_MAKE_VERSION' VERSION | sed 's,.*=[[:space:]]*,,'`
+
+rm -f *~
+mkdir -p ../tmp
+
+if [ -z "$ECHO" ]; then
+ # new files are handled automatically
+ # but we need to rm if needed
+ $GIT diff FILES | sed -n '/^-[^-]/s,^-,,p' > $TF.rm
+ test -s $TF.rm && xargs rm -f < $TF.rm
+ $GIT add -A
+ $GIT diff --staged | tee ../tmp/bmake-import.diff
+ echo "$GIT tag -a vendor/NetBSD/bmake/$VERSION" > ../tmp/bmake-post.sh
+ echo "After you commit, run $here/../tmp/bmake-post.sh"
+else
+ # FILES is kept sorted so we can determine what was added and deleted
+ $GIT diff FILES | sed -n '/^+[^+]/s,^+,,p' > $TF.add
+ $GIT diff FILES | sed -n '/^-[^-]/s,^-,,p' > $TF.rm
+ test -s $TF.rm && { echo Removing:; cat $TF.rm; }
+ test -s $TF.add && { echo Adding:; cat $TF.add; }
+ $GIT diff
+fi
diff --git a/job.c b/job.c
index ff0f1bcb8f4c..2a43a64e9749 100644
--- a/job.c
+++ b/job.c
@@ -1,4 +1,4 @@
-/* $NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $ */
+/* $NetBSD: job.c,v 1.397 2021/01/10 23:59:53 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -156,9 +156,10 @@
#include "trace.h"
/* "@(#)job.c 8.2 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $");
+MAKE_RCSID("$NetBSD: job.c,v 1.397 2021/01/10 23:59:53 rillig Exp $");
-/* A shell defines how the commands are run. All commands for a target are
+/*
+ * A shell defines how the commands are run. All commands for a target are
* written into a single file, which is then given to the shell to execute
* the commands from it. The commands are written to the file using a few
* templates for echo control and error control.
@@ -174,12 +175,12 @@ MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $");
* it is filtered out using noPrint and noPrintLen.
*
* The error checking for individual commands is controlled using hasErrCtl,
- * errOnOrEcho, errOffOrExecIgnore and errExit.
+ * errOn, errOff and runChkTmpl.
*
- * If a shell doesn't have error control, errOnOrEcho becomes a printf template
- * for echoing the command, should echoing be on; errOffOrExecIgnore becomes
+ * If a shell doesn't have error control, echoTmpl becomes a printf template
+ * for echoing the command, should echoing be on; runIgnTmpl becomes
* another printf template for executing the command while ignoring the return
- * status. Finally errExit is a printf template for running the command and
+ * status. Finally runChkTmpl is a printf template for running the command and
* causing the shell to exit on error. If any of these strings are empty when
* hasErrCtl is FALSE, the command will be executed anyway as is, and if it
* causes an error, so be it. Any templates set up to echo the command will
@@ -193,64 +194,91 @@ MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $");
*/
typedef struct Shell {
- /* The name of the shell. For Bourne and C shells, this is used only to
- * find the shell description when used as the single source of a .SHELL
- * target. For user-defined shells, this is the full path of the shell. */
- const char *name;
+ /*
+ * The name of the shell. For Bourne and C shells, this is used only
+ * to find the shell description when used as the single source of a
+ * .SHELL target. For user-defined shells, this is the full path of
+ * the shell.
+ */
+ const char *name;
- Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */
- const char *echoOff; /* command to turn off echo */
- const char *echoOn; /* command to turn it back on again */
- const char *noPrint; /* text to skip when printing output from
+ Boolean hasEchoCtl; /* whether both echoOff and echoOn are there */
+ const char *echoOff; /* command to turn echoing off */
+ const char *echoOn; /* command to turn echoing back on */
+ const char *noPrint; /* text to skip when printing output from the
* shell. This is usually the same as echoOff */
- size_t noPrintLen; /* length of noPrint command */
+ size_t noPrintLen; /* length of noPrint command */
+
+ Boolean hasErrCtl; /* whether error checking can be controlled
+ * for individual commands */
+ const char *errOn; /* command to turn on error checking */
+ const char *errOff; /* command to turn off error checking */
+
+ const char *echoTmpl; /* template to echo a command */
+ const char *runIgnTmpl; /* template to run a command
+ * without error checking */
+ const char *runChkTmpl; /* template to run a command
+ * with error checking */
+
+ /* string literal that results in a newline character when it appears
+ * outside of any 'quote' or "quote" characters */
+ const char *newline;
+ char commentChar; /* character used by shell for comment lines */
+
+ const char *echoFlag; /* shell flag to echo commands */
+ const char *errFlag; /* shell flag to exit on error */
+} Shell;
- Boolean hasErrCtl; /* set if can control error checking for
- * individual commands */
- /* XXX: split into errOn and echoCmd */
- const char *errOnOrEcho; /* template to turn on error checking */
- /* XXX: split into errOff and execIgnore */
- const char *errOffOrExecIgnore; /* template to turn off error checking */
- const char *errExit; /* template to use for testing exit code */
+typedef struct CommandFlags {
+ /* Whether to echo the command before running it. */
+ Boolean echo;
- /* string literal that results in a newline character when it appears
- * outside of any 'quote' or "quote" characters */
- const char *newline;
- char commentChar; /* character used by shell for comment lines */
+ /* Run the command even in -n or -N mode. */
+ Boolean always;
- /*
- * command-line flags
- */
- const char *echo; /* echo commands */
- const char *exit; /* exit on error */
-} Shell;
+ /*
+ * true if we turned error checking off before printing the command
+ * and need to turn it back on
+ */
+ Boolean ignerr;
+} CommandFlags;
+
+/*
+ * Write shell commands to a file.
+ *
+ * TODO: keep track of whether commands are echoed.
+ * TODO: keep track of whether error checking is active.
+ */
+typedef struct ShellWriter {
+ FILE *f;
+
+ /* we've sent 'set -x' */
+ Boolean xtraced;
+
+} ShellWriter;
/*
* error handling variables
*/
-static int errors = 0; /* number of errors reported */
+static int job_errors = 0; /* number of errors reported */
typedef enum AbortReason { /* why is the make aborting? */
- ABORT_NONE,
- ABORT_ERROR, /* Because of an error */
- ABORT_INTERRUPT, /* Because it was interrupted */
- ABORT_WAIT /* Waiting for jobs to finish */
+ ABORT_NONE,
+ ABORT_ERROR, /* Because of an error */
+ ABORT_INTERRUPT, /* Because it was interrupted */
+ ABORT_WAIT /* Waiting for jobs to finish */
} AbortReason;
static AbortReason aborting = ABORT_NONE;
-#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */
+#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */
/*
* this tracks the number of tokens currently "out" to build jobs.
*/
int jobTokensRunning = 0;
-/* The number of commands actually printed to the shell commands file for
- * the current job. Should this number be 0, no shell will be executed. */
-static int numCommands;
-
typedef enum JobStartResult {
- JOB_RUNNING, /* Job is running */
- JOB_ERROR, /* Error in starting the job */
- JOB_FINISHED /* The job is already finished */
+ JOB_RUNNING, /* Job is running */
+ JOB_ERROR, /* Error in starting the job */
+ JOB_FINISHED /* The job is already finished */
} JobStartResult;
/*
@@ -283,7 +311,7 @@ typedef enum JobStartResult {
#define DEFSHELL_INDEX 0 /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */
#endif /* !DEFSHELL_INDEX */
-static Shell shells[] = {
+static Shell shells[] = {
#ifdef DEFSHELL_CUSTOM
/*
* An sh-compatible shell with a non-standard name.
@@ -292,93 +320,103 @@ static Shell shells[] = {
* non-portable features that might not be supplied by all
* sh-compatible shells.
*/
-{
- DEFSHELL_CUSTOM, /* .name */
- FALSE, /* .hasEchoCtl */
- "", /* .echoOff */
- "", /* .echoOn */
- "", /* .noPrint */
- 0, /* .noPrintLen */
- FALSE, /* .hasErrCtl */
- "echo \"%s\"\n", /* .errOnOrEcho */
- "%s\n", /* .errOffOrExecIgnore */
- "{ %s \n} || exit $?\n", /* .errExit */
- "'\n'", /* .newline */
- '#', /* .commentChar */
- "", /* .echo */
- "", /* .exit */
-},
+ {
+ DEFSHELL_CUSTOM, /* .name */
+ FALSE, /* .hasEchoCtl */
+ "", /* .echoOff */
+ "", /* .echoOn */
+ "", /* .noPrint */
+ 0, /* .noPrintLen */
+ FALSE, /* .hasErrCtl */
+ "", /* .errOn */
+ "", /* .errOff */
+ "echo \"%s\"\n", /* .echoTmpl */
+ "%s\n", /* .runIgnTmpl */
+ "{ %s \n} || exit $?\n", /* .runChkTmpl */
+ "'\n'", /* .newline */
+ '#', /* .commentChar */
+ "", /* .echoFlag */
+ "", /* .errFlag */
+ },
#endif /* DEFSHELL_CUSTOM */
/*
* SH description. Echo control is also possible and, under
* sun UNIX anyway, one can even control error checking.
*/
-{
- "sh", /* .name */
- FALSE, /* .hasEchoCtl */
- "", /* .echoOff */
- "", /* .echoOn */
- "", /* .noPrint */
- 0, /* .noPrintLen */
- FALSE, /* .hasErrCtl */
- "echo \"%s\"\n", /* .errOnOrEcho */
- "%s\n", /* .errOffOrExecIgnore */
- "{ %s \n} || exit $?\n", /* .errExit */
- "'\n'", /* .newline */
- '#', /* .commentChar*/
+ {
+ "sh", /* .name */
+ FALSE, /* .hasEchoCtl */
+ "", /* .echoOff */
+ "", /* .echoOn */
+ "", /* .noPrint */
+ 0, /* .noPrintLen */
+ FALSE, /* .hasErrCtl */
+ "", /* .errOn */
+ "", /* .errOff */
+ "echo \"%s\"\n", /* .echoTmpl */
+ "%s\n", /* .runIgnTmpl */
+ "{ %s \n} || exit $?\n", /* .runChkTmpl */
+ "'\n'", /* .newline */
+ '#', /* .commentChar*/
#if defined(MAKE_NATIVE) && defined(__NetBSD__)
- "q", /* .echo */
+ /* XXX: -q is not really echoFlag, it's more like noEchoInSysFlag. */
+ "q", /* .echoFlag */
#else
- "", /* .echo */
+ "", /* .echoFlag */
#endif
- "", /* .exit */
-},
+ "", /* .errFlag */
+ },
/*
* KSH description.
*/
-{
- "ksh", /* .name */
- TRUE, /* .hasEchoCtl */
- "set +v", /* .echoOff */
- "set -v", /* .echoOn */
- "set +v", /* .noPrint */
- 6, /* .noPrintLen */
- FALSE, /* .hasErrCtl */
- "echo \"%s\"\n", /* .errOnOrEcho */
- "%s\n", /* .errOffOrExecIgnore */
- "{ %s \n} || exit $?\n", /* .errExit */
- "'\n'", /* .newline */
- '#', /* .commentChar */
- "v", /* .echo */
- "", /* .exit */
-},
+ {
+ "ksh", /* .name */
+ TRUE, /* .hasEchoCtl */
+ "set +v", /* .echoOff */
+ "set -v", /* .echoOn */
+ "set +v", /* .noPrint */
+ 6, /* .noPrintLen */
+ FALSE, /* .hasErrCtl */
+ "", /* .errOn */
+ "", /* .errOff */
+ "echo \"%s\"\n", /* .echoTmpl */
+ "%s\n", /* .runIgnTmpl */
+ "{ %s \n} || exit $?\n", /* .runChkTmpl */
+ "'\n'", /* .newline */
+ '#', /* .commentChar */
+ "v", /* .echoFlag */
+ "", /* .errFlag */
+ },
/*
* CSH description. The csh can do echo control by playing
* with the setting of the 'echo' shell variable. Sadly,
* however, it is unable to do error control nicely.
*/
-{
- "csh", /* .name */
- TRUE, /* .hasEchoCtl */
- "unset verbose", /* .echoOff */
- "set verbose", /* .echoOn */
- "unset verbose", /* .noPrint */
- 13, /* .noPrintLen */
- FALSE, /* .hasErrCtl */
- "echo \"%s\"\n", /* .errOnOrEcho */
- /* XXX: Mismatch between errOn and execIgnore */
- "csh -c \"%s || exit 0\"\n", /* .errOffOrExecIgnore */
- "", /* .errExit */
- "'\\\n'", /* .newline */
- '#', /* .commentChar */
- "v", /* .echo */
- "e", /* .exit */
-}
+ {
+ "csh", /* .name */
+ TRUE, /* .hasEchoCtl */
+ "unset verbose", /* .echoOff */
+ "set verbose", /* .echoOn */
+ "unset verbose", /* .noPrint */
+ 13, /* .noPrintLen */
+ FALSE, /* .hasErrCtl */
+ "", /* .errOn */
+ "", /* .errOff */
+ "echo \"%s\"\n", /* .echoTmpl */
+ "csh -c \"%s || exit 0\"\n", /* .runIgnTmpl */
+ "", /* .runChkTmpl */
+ "'\\\n'", /* .newline */
+ '#', /* .commentChar */
+ "v", /* .echoFlag */
+ "e", /* .errFlag */
+ }
};
-/* This is the shell to which we pass all commands in the Makefile.
- * It is set by the Job_ParseShell function. */
-static Shell *commandShell = &shells[DEFSHELL_INDEX];
+/*
+ * This is the shell to which we pass all commands in the Makefile.
+ * It is set by the Job_ParseShell function.
+ */
+static Shell *shell = &shells[DEFSHELL_INDEX];
const char *shellPath = NULL; /* full pathname of executable image */
const char *shellName = NULL; /* last component of shellPath */
char *shellErrFlag = NULL;
@@ -389,62 +427,82 @@ static Job *job_table; /* The structures that describe them */
static Job *job_table_end; /* job_table + maxJobs */
static unsigned int wantToken; /* we want a token */
static Boolean lurking_children = FALSE;
-static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */
+static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */
/*
* Set of descriptors of pipes connected to
* the output channels of children
*/
static struct pollfd *fds = NULL;
-static Job **jobfds = NULL;
-static nfds_t nfds = 0;
+static Job **allJobs = NULL;
+static nfds_t nJobs = 0;
static void watchfd(Job *);
static void clearfd(Job *);
-static int readyfd(Job *);
+static Boolean readyfd(Job *);
-static GNode *lastNode; /* The node for which output was most recently
- * produced. */
-static char *targPrefix = NULL; /* What we print at the start of TARG_FMT */
+static char *targPrefix = NULL; /* To identify a job change in the output. */
static Job tokenWaitJob; /* token wait pseudo-job */
static Job childExitJob; /* child exit pseudo-job */
-#define CHILD_EXIT "."
-#define DO_JOB_RESUME "R"
+#define CHILD_EXIT "."
+#define DO_JOB_RESUME "R"
-enum { npseudojobs = 2 }; /* number of pseudo-jobs */
-
-#define TARG_FMT "%s %s ---\n" /* Default format */
-#define MESSAGE(fp, gn) \
- if (opts.maxJobs != 1 && targPrefix && *targPrefix) \
- (void)fprintf(fp, TARG_FMT, targPrefix, gn->name)
+enum {
+ npseudojobs = 2 /* number of pseudo-jobs */
+};
static sigset_t caught_signals; /* Set of signals we handle */
static void JobDoOutput(Job *, Boolean);
-static void JobInterrupt(int, int) MAKE_ATTR_DEAD;
+static void JobInterrupt(Boolean, int) MAKE_ATTR_DEAD;
static void JobRestartJobs(void);
static void JobSigReset(void);
+static void
+SwitchOutputTo(GNode *gn)
+{
+ /* The node for which output was most recently produced. */
+ static GNode *lastNode = NULL;
+
+ if (gn == lastNode)
+ return;
+ lastNode = gn;
+
+ if (opts.maxJobs != 1 && targPrefix != NULL && targPrefix[0] != '\0')
+ (void)fprintf(stdout, "%s %s ---\n", targPrefix, gn->name);
+}
+
static unsigned
nfds_per_job(void)
{
#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV)
- if (useMeta)
- return 2;
+ if (useMeta)
+ return 2;
#endif
- return 1;
+ return 1;
+}
+
+void
+Job_FlagsToString(const Job *job, char *buf, size_t bufsize)
+{
+ snprintf(buf, bufsize, "%c%c%c",
+ job->ignerr ? 'i' : '-',
+ !job->echo ? 's' : '-',
+ job->special ? 'S' : '-');
}
static void
job_table_dump(const char *where)
{
- Job *job;
+ Job *job;
+ char flags[4];
- debug_printf("job table @ %s\n", where);
- for (job = job_table; job < job_table_end; job++) {
- debug_printf("job %d, status %d, flags %d, pid %d\n",
- (int)(job - job_table), job->status, job->flags, job->pid);
- }
+ debug_printf("job table @ %s\n", where);
+ for (job = job_table; job < job_table_end; job++) {
+ Job_FlagsToString(job, flags, sizeof flags);
+ debug_printf("job %d, status %d, flags %s, pid %d\n",
+ (int)(job - job_table), job->status, flags, job->pid);
+ }
}
/*
@@ -454,20 +512,20 @@ job_table_dump(const char *where)
static void
JobDeleteTarget(GNode *gn)
{
- const char *file;
-
- if (gn->type & OP_JOIN)
- return;
- if (gn->type & OP_PHONY)
- return;
- if (Targ_Precious(gn))
- return;
- if (opts.noExecute)
- return;
-
- file = GNode_Path(gn);
- if (eunlink(file) != -1)
- Error("*** %s removed", file);
+ const char *file;
+
+ if (gn->type & OP_JOIN)
+ return;
+ if (gn->type & OP_PHONY)
+ return;
+ if (Targ_Precious(gn))
+ return;
+ if (opts.noExecute)
+ return;
+
+ file = GNode_Path(gn);
+ if (eunlink(file) != -1)
+ Error("*** %s removed", file);
}
/*
@@ -476,7 +534,8 @@ JobDeleteTarget(GNode *gn)
* Signal lock routines to get exclusive access. Currently used to
* protect `jobs' and `stoppedJobs' list manipulations.
*/
-static void JobSigLock(sigset_t *omaskp)
+static void
+JobSigLock(sigset_t *omaskp)
{
if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) {
Punt("JobSigLock: sigprocmask: %s", strerror(errno));
@@ -484,7 +543,8 @@ static void JobSigLock(sigset_t *omaskp)
}
}
-static void JobSigUnlock(sigset_t *omaskp)
+static void
+JobSigUnlock(sigset_t *omaskp)
{
(void)sigprocmask(SIG_SETMASK, omaskp, NULL);
}
@@ -492,458 +552,499 @@ static void JobSigUnlock(sigset_t *omaskp)
static void
JobCreatePipe(Job *job, int minfd)
{
- int i, fd, flags;
- int pipe_fds[2];
-
- if (pipe(pipe_fds) == -1)
- Punt("Cannot create pipe: %s", strerror(errno));
-
- for (i = 0; i < 2; i++) {
- /* Avoid using low numbered fds */
- fd = fcntl(pipe_fds[i], F_DUPFD, minfd);
- if (fd != -1) {
- close(pipe_fds[i]);
- pipe_fds[i] = fd;
+ int i, fd, flags;
+ int pipe_fds[2];
+
+ if (pipe(pipe_fds) == -1)
+ Punt("Cannot create pipe: %s", strerror(errno));
+
+ for (i = 0; i < 2; i++) {
+ /* Avoid using low numbered fds */
+ fd = fcntl(pipe_fds[i], F_DUPFD, minfd);
+ if (fd != -1) {
+ close(pipe_fds[i]);
+ pipe_fds[i] = fd;
+ }
}
- }
- job->inPipe = pipe_fds[0];
- job->outPipe = pipe_fds[1];
+ job->inPipe = pipe_fds[0];
+ job->outPipe = pipe_fds[1];
- /* Set close-on-exec flag for both */
- if (fcntl(job->inPipe, F_SETFD, FD_CLOEXEC) == -1)
- Punt("Cannot set close-on-exec: %s", strerror(errno));
- if (fcntl(job->outPipe, F_SETFD, FD_CLOEXEC) == -1)
- Punt("Cannot set close-on-exec: %s", strerror(errno));
+ /* Set close-on-exec flag for both */
+ if (fcntl(job->inPipe, F_SETFD, FD_CLOEXEC) == -1)
+ Punt("Cannot set close-on-exec: %s", strerror(errno));
+ if (fcntl(job->outPipe, F_SETFD, FD_CLOEXEC) == -1)
+ Punt("Cannot set close-on-exec: %s", strerror(errno));
- /*
- * We mark the input side of the pipe non-blocking; we poll(2) the
- * pipe when we're waiting for a job token, but we might lose the
- * race for the token when a new one becomes available, so the read
- * from the pipe should not block.
- */
- flags = fcntl(job->inPipe, F_GETFL, 0);
- if (flags == -1)
- Punt("Cannot get flags: %s", strerror(errno));
- flags |= O_NONBLOCK;
- if (fcntl(job->inPipe, F_SETFL, flags) == -1)
- Punt("Cannot set flags: %s", strerror(errno));
+ /*
+ * We mark the input side of the pipe non-blocking; we poll(2) the
+ * pipe when we're waiting for a job token, but we might lose the
+ * race for the token when a new one becomes available, so the read
+ * from the pipe should not block.
+ */
+ flags = fcntl(job->inPipe, F_GETFL, 0);
+ if (flags == -1)
+ Punt("Cannot get flags: %s", strerror(errno));
+ flags |= O_NONBLOCK;
+ if (fcntl(job->inPipe, F_SETFL, flags) == -1)
+ Punt("Cannot set flags: %s", strerror(errno));
}
/* Pass the signal to each running job. */
static void
JobCondPassSig(int signo)
{
- Job *job;
+ Job *job;
- DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo);
+ DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo);
- for (job = job_table; job < job_table_end; job++) {
- if (job->status != JOB_ST_RUNNING)
- continue;
- DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n",
- signo, job->pid);
- KILLPG(job->pid, signo);
- }
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->status != JOB_ST_RUNNING)
+ continue;
+ DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n",
+ signo, job->pid);
+ KILLPG(job->pid, signo);
+ }
}
-/* SIGCHLD handler.
+/*
+ * SIGCHLD handler.
*
- * Sends a token on the child exit pipe to wake us up from select()/poll(). */
+ * Sends a token on the child exit pipe to wake us up from select()/poll().
+ */
+/*ARGSUSED*/
static void
JobChildSig(int signo MAKE_ATTR_UNUSED)
{
- while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN)
- continue;
+ while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 &&
+ errno == EAGAIN)
+ continue;
}
/* Resume all stopped jobs. */
+/*ARGSUSED*/
static void
JobContinueSig(int signo MAKE_ATTR_UNUSED)
{
- /*
- * Defer sending SIGCONT to our stopped children until we return
- * from the signal handler.
- */
- while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 &&
- errno == EAGAIN)
- continue;
+ /*
+ * Defer sending SIGCONT to our stopped children until we return
+ * from the signal handler.
+ */
+ while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 &&
+ errno == EAGAIN)
+ continue;
}
-/* Pass a signal on to all jobs, then resend to ourselves.
- * We die by the same signal. */
+/*
+ * Pass a signal on to all jobs, then resend to ourselves.
+ * We die by the same signal.
+ */
MAKE_ATTR_DEAD static void
JobPassSig_int(int signo)
{
- /* Run .INTERRUPT target then exit */
- JobInterrupt(TRUE, signo);
+ /* Run .INTERRUPT target then exit */
+ JobInterrupt(TRUE, signo);
}
-/* Pass a signal on to all jobs, then resend to ourselves.
- * We die by the same signal. */
+/*
+ * Pass a signal on to all jobs, then resend to ourselves.
+ * We die by the same signal.
+ */
MAKE_ATTR_DEAD static void
JobPassSig_term(int signo)
{
- /* Dont run .INTERRUPT target then exit */
- JobInterrupt(FALSE, signo);
+ /* Dont run .INTERRUPT target then exit */
+ JobInterrupt(FALSE, signo);
}
static void
JobPassSig_suspend(int signo)
{
- sigset_t nmask, omask;
- struct sigaction act;
+ sigset_t nmask, omask;
+ struct sigaction act;
- /* Suppress job started/continued messages */
- make_suspended = TRUE;
+ /* Suppress job started/continued messages */
+ make_suspended = TRUE;
- /* Pass the signal onto every job */
- JobCondPassSig(signo);
+ /* Pass the signal onto every job */
+ JobCondPassSig(signo);
- /*
- * Send ourselves the signal now we've given the message to everyone else.
- * Note we block everything else possible while we're getting the signal.
- * This ensures that all our jobs get continued when we wake up before
- * we take any other signal.
- */
- sigfillset(&nmask);
- sigdelset(&nmask, signo);
- (void)sigprocmask(SIG_SETMASK, &nmask, &omask);
+ /*
+ * Send ourselves the signal now we've given the message to everyone
+ * else. Note we block everything else possible while we're getting
+ * the signal. This ensures that all our jobs get continued when we
+ * wake up before we take any other signal.
+ */
+ sigfillset(&nmask);
+ sigdelset(&nmask, signo);
+ (void)sigprocmask(SIG_SETMASK, &nmask, &omask);
- act.sa_handler = SIG_DFL;
- sigemptyset(&act.sa_mask);
- act.sa_flags = 0;
- (void)sigaction(signo, &act, NULL);
+ act.sa_handler = SIG_DFL;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ (void)sigaction(signo, &act, NULL);
- if (DEBUG(JOB))
- debug_printf("JobPassSig passing signal %d to self.\n", signo);
+ DEBUG1(JOB, "JobPassSig passing signal %d to self.\n", signo);
- (void)kill(getpid(), signo);
+ (void)kill(getpid(), signo);
- /*
- * We've been continued.
- *
- * A whole host of signals continue to happen!
- * SIGCHLD for any processes that actually suspended themselves.
- * SIGCHLD for any processes that exited while we were alseep.
- * The SIGCONT that actually caused us to wakeup.
- *
- * Since we defer passing the SIGCONT on to our children until
- * the main processing loop, we can be sure that all the SIGCHLD
- * events will have happened by then - and that the waitpid() will
- * collect the child 'suspended' events.
- * For correct sequencing we just need to ensure we process the
- * waitpid() before passing on the SIGCONT.
- *
- * In any case nothing else is needed here.
- */
+ /*
+ * We've been continued.
+ *
+ * A whole host of signals continue to happen!
+ * SIGCHLD for any processes that actually suspended themselves.
+ * SIGCHLD for any processes that exited while we were alseep.
+ * The SIGCONT that actually caused us to wakeup.
+ *
+ * Since we defer passing the SIGCONT on to our children until
+ * the main processing loop, we can be sure that all the SIGCHLD
+ * events will have happened by then - and that the waitpid() will
+ * collect the child 'suspended' events.
+ * For correct sequencing we just need to ensure we process the
+ * waitpid() before passing on the SIGCONT.
+ *
+ * In any case nothing else is needed here.
+ */
- /* Restore handler and signal mask */
- act.sa_handler = JobPassSig_suspend;
- (void)sigaction(signo, &act, NULL);
- (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+ /* Restore handler and signal mask */
+ act.sa_handler = JobPassSig_suspend;
+ (void)sigaction(signo, &act, NULL);
+ (void)sigprocmask(SIG_SETMASK, &omask, NULL);
}
static Job *
JobFindPid(int pid, JobStatus status, Boolean isJobs)
{
- Job *job;
+ Job *job;
- for (job = job_table; job < job_table_end; job++) {
- if (job->status == status && job->pid == pid)
- return job;
- }
- if (DEBUG(JOB) && isJobs)
- job_table_dump("no pid");
- return NULL;
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->status == status && job->pid == pid)
+ return job;
+ }
+ if (DEBUG(JOB) && isJobs)
+ job_table_dump("no pid");
+ return NULL;
}
/* Parse leading '@', '-' and '+', which control the exact execution mode. */
static void
-ParseRunOptions(
- char **pp,
- Boolean *out_shutUp, Boolean *out_errOff, Boolean *out_runAlways)
-{
- char *p = *pp;
- *out_shutUp = FALSE;
- *out_errOff = FALSE;
- *out_runAlways = FALSE;
-
- for (;;) {
- if (*p == '@')
- *out_shutUp = !DEBUG(LOUD);
- else if (*p == '-')
- *out_errOff = TRUE;
- else if (*p == '+')
- *out_runAlways = TRUE;
- else
- break;
- p++;
- }
+ParseCommandFlags(char **pp, CommandFlags *out_cmdFlags)
+{
+ char *p = *pp;
+ out_cmdFlags->echo = TRUE;
+ out_cmdFlags->ignerr = FALSE;
+ out_cmdFlags->always = FALSE;
+
+ for (;;) {
+ if (*p == '@')
+ out_cmdFlags->echo = DEBUG(LOUD);
+ else if (*p == '-')
+ out_cmdFlags->ignerr = TRUE;
+ else if (*p == '+')
+ out_cmdFlags->always = TRUE;
+ else
+ break;
+ p++;
+ }
- pp_skip_whitespace(&p);
+ pp_skip_whitespace(&p);
- *pp = p;
+ *pp = p;
}
/* Escape a string for a double-quoted string literal in sh, csh and ksh. */
static char *
EscapeShellDblQuot(const char *cmd)
{
- size_t i, j;
+ size_t i, j;
+
+ /* Worst that could happen is every char needs escaping. */
+ char *esc = bmake_malloc(strlen(cmd) * 2 + 1);
+ for (i = 0, j = 0; cmd[i] != '\0'; i++, j++) {
+ if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' ||
+ cmd[i] == '"')
+ esc[j++] = '\\';
+ esc[j] = cmd[i];
+ }
+ esc[j] = '\0';
+
+ return esc;
+}
+
+static void
+ShellWriter_PrintFmt(ShellWriter *wr, const char *fmt, const char *arg)
+{
+ DEBUG1(JOB, fmt, arg);
- /* Worst that could happen is every char needs escaping. */
- char *esc = bmake_malloc(strlen(cmd) * 2 + 1);
- for (i = 0, j = 0; cmd[i] != '\0'; i++, j++) {
- if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || cmd[i] == '"')
- esc[j++] = '\\';
- esc[j] = cmd[i];
- }
- esc[j] = '\0';
+ (void)fprintf(wr->f, fmt, arg);
+ /* XXX: Is flushing needed in any case, or only if f == stdout? */
+ (void)fflush(wr->f);
+}
- return esc;
+static void
+ShellWriter_Println(ShellWriter *wr, const char *line)
+{
+ ShellWriter_PrintFmt(wr, "%s\n", line);
}
static void
-JobPrintf(Job *job, const char *fmt, const char *arg)
+ShellWriter_EchoOff(ShellWriter *wr)
{
- if (DEBUG(JOB))
- debug_printf(fmt, arg);
+ if (shell->hasEchoCtl)
+ ShellWriter_Println(wr, shell->echoOff);
+}
- (void)fprintf(job->cmdFILE, fmt, arg);
- (void)fflush(job->cmdFILE);
+static void
+ShellWriter_EchoCmd(ShellWriter *wr, const char *escCmd)
+{
+ ShellWriter_PrintFmt(wr, shell->echoTmpl, escCmd);
}
static void
-JobPrintln(Job *job, const char *line)
+ShellWriter_EchoOn(ShellWriter *wr)
{
- JobPrintf(job, "%s\n", line);
+ if (shell->hasEchoCtl)
+ ShellWriter_Println(wr, shell->echoOn);
}
-/*-
- *-----------------------------------------------------------------------
- * JobPrintCommand --
- * Put out another command for the given job. If the command starts
- * with an @ or a - we process it specially. In the former case,
- * so long as the -s and -n flags weren't given to make, we stick
- * a shell-specific echoOff command in the script. In the latter,
- * we ignore errors for the entire job, unless the shell has error
- * control.
- * If the command is just "..." we take all future commands for this
- * job to be commands to be executed once the entire graph has been
- * made and return non-zero to signal that the end of the commands
- * was reached. These commands are later attached to the .END
- * node and executed by Job_End when all things are done.
- *
- * Side Effects:
- * If the command begins with a '-' and the shell has no error control,
- * the JOB_IGNERR flag is set in the job descriptor.
- * numCommands is incremented if the command is actually printed.
- *-----------------------------------------------------------------------
- */
static void
-JobPrintCommand(Job *job, char *cmd)
+ShellWriter_TraceOn(ShellWriter *wr)
{
- const char *const cmdp = cmd;
- Boolean noSpecials; /* true if we shouldn't worry about
- * inserting special commands into
- * the input stream. */
- Boolean shutUp; /* true if we put a no echo command
- * into the command file */
- Boolean errOff; /* true if we turned error checking
- * off before printing the command
- * and need to turn it back on */
- Boolean runAlways;
- const char *cmdTemplate; /* Template to use when printing the
- * command */
- char *cmdStart; /* Start of expanded command */
- char *escCmd = NULL; /* Command with quotes/backticks escaped */
+ if (!wr->xtraced) {
+ ShellWriter_Println(wr, "set -x");
+ wr->xtraced = TRUE;
+ }
+}
- noSpecials = !GNode_ShouldExecute(job->node);
+static void
+ShellWriter_ErrOff(ShellWriter *wr, Boolean echo)
+{
+ if (echo)
+ ShellWriter_EchoOff(wr);
+ ShellWriter_Println(wr, shell->errOff);
+ if (echo)
+ ShellWriter_EchoOn(wr);
+}
- numCommands++;
+static void
+ShellWriter_ErrOn(ShellWriter *wr, Boolean echo)
+{
+ if (echo)
+ ShellWriter_EchoOff(wr);
+ ShellWriter_Println(wr, shell->errOn);
+ if (echo)
+ ShellWriter_EchoOn(wr);
+}
- Var_Subst(cmd, job->node, VARE_WANTRES, &cmd);
- /* TODO: handle errors */
- cmdStart = cmd;
+/*
+ * The shell has no built-in error control, so emulate error control by
+ * enclosing each shell command in a template like "{ %s \n } || exit $?"
+ * (configurable per shell).
+ */
+static void
+JobPrintSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags,
+ const char *escCmd, const char **inout_cmdTemplate)
+{
+ /* XXX: Why is the job modified at this point? */
+ job->ignerr = TRUE;
- cmdTemplate = "%s\n";
+ if (job->echo && inout_cmdFlags->echo) {
+ ShellWriter_EchoOff(wr);
+ ShellWriter_EchoCmd(wr, escCmd);
- ParseRunOptions(&cmd, &shutUp, &errOff, &runAlways);
+ /*
+ * Leave echoing off so the user doesn't see the commands
+ * for toggling the error checking.
+ */
+ inout_cmdFlags->echo = FALSE;
+ } else {
+ if (inout_cmdFlags->echo)
+ ShellWriter_EchoCmd(wr, escCmd);
+ }
+ *inout_cmdTemplate = shell->runIgnTmpl;
- if (runAlways && noSpecials) {
/*
- * We're not actually executing anything...
- * but this one needs to be - use compat mode just for it.
+ * The template runIgnTmpl already takes care of ignoring errors,
+ * so pretend error checking is still on.
+ * XXX: What effects does this have, and why is it necessary?
*/
- Compat_RunCommand(cmdp, job->node);
- free(cmdStart);
- return;
- }
+ inout_cmdFlags->ignerr = FALSE;
+}
- /*
- * If the shell doesn't have error control the alternate echo'ing will
- * be done (to avoid showing additional error checking code)
- * and this will need the characters '$ ` \ "' escaped
- */
+static void
+JobPrintSpecials(Job *job, ShellWriter *wr, const char *escCmd, Boolean run,
+ CommandFlags *inout_cmdFlags, const char **inout_cmdTemplate)
+{
+ if (!run) {
+ /*
+ * If there is no command to run, there is no need to switch
+ * error checking off and on again for nothing.
+ */
+ inout_cmdFlags->ignerr = FALSE;
+ } else if (shell->hasErrCtl)
+ ShellWriter_ErrOff(wr, job->echo && inout_cmdFlags->echo);
+ else if (shell->runIgnTmpl != NULL && shell->runIgnTmpl[0] != '\0') {
+ JobPrintSpecialsEchoCtl(job, wr, inout_cmdFlags, escCmd,
+ inout_cmdTemplate);
+ } else
+ inout_cmdFlags->ignerr = FALSE;
+}
+
+/*
+ * Put out another command for the given job.
+ *
+ * If the command starts with '@' and neither the -s nor the -n flag was
+ * given to make, we stick a shell-specific echoOff command in the script.
+ *
+ * If the command starts with '-' and the shell has no error control (none
+ * of the predefined shells has that), we ignore errors for the entire job.
+ * XXX: Why ignore errors for the entire job?
+ * XXX: Even ignore errors for the commands before this command?
+ *
+ * If the command is just "...", all further commands of this job are skipped
+ * for now. They are attached to the .END node and will be run by Job_Finish
+ * after all other targets have been made.
+ */
+static void
+JobPrintCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd)
+{
+ Boolean run;
- if (!commandShell->hasErrCtl)
- escCmd = EscapeShellDblQuot(cmd);
+ CommandFlags cmdFlags;
+ /* Template for printing a command to the shell file */
+ const char *cmdTemplate;
+ char *xcmd; /* The expanded command */
+ char *xcmdStart;
+ char *escCmd; /* xcmd escaped to be used in double quotes */
- if (shutUp) {
- if (!(job->flags & JOB_SILENT) && !noSpecials &&
- (commandShell->hasEchoCtl)) {
- JobPrintln(job, commandShell->echoOff);
- } else {
- if (commandShell->hasErrCtl)
- shutUp = FALSE;
- }
- }
+ run = GNode_ShouldExecute(job->node);
+
+ Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd);
+ /* TODO: handle errors */
+ xcmdStart = xcmd;
- if (errOff) {
- if (!noSpecials) {
- if (commandShell->hasErrCtl) {
+ cmdTemplate = "%s\n";
+
+ ParseCommandFlags(&xcmd, &cmdFlags);
+
+ /* The '+' command flag overrides the -n or -N options. */
+ if (cmdFlags.always && !run) {
/*
- * we don't want the error-control commands showing
- * up either, so we turn off echoing while executing
- * them. We could put another field in the shell
- * structure to tell JobDoOutput to look for this
- * string too, but why make it any more complex than
- * it already is?
+ * We're not actually executing anything...
+ * but this one needs to be - use compat mode just for it.
*/
- if (!(job->flags & JOB_SILENT) && !shutUp &&
- (commandShell->hasEchoCtl)) {
- JobPrintln(job, commandShell->echoOff);
- JobPrintln(job, commandShell->errOffOrExecIgnore);
- JobPrintln(job, commandShell->echoOn);
+ Compat_RunCommand(ucmd, job->node, ln);
+ free(xcmdStart);
+ return;
+ }
+
+ /*
+ * If the shell doesn't have error control, the alternate echoing
+ * will be done (to avoid showing additional error checking code)
+ * and this needs some characters escaped.
+ */
+ escCmd = shell->hasErrCtl ? NULL : EscapeShellDblQuot(xcmd);
+
+ if (!cmdFlags.echo) {
+ if (job->echo && run && shell->hasEchoCtl) {
+ ShellWriter_EchoOff(wr);
} else {
- JobPrintln(job, commandShell->errOffOrExecIgnore);
+ if (shell->hasErrCtl)
+ cmdFlags.echo = TRUE;
}
- } else if (commandShell->errOffOrExecIgnore &&
- commandShell->errOffOrExecIgnore[0] != '\0') {
+ }
+
+ if (cmdFlags.ignerr) {
+ JobPrintSpecials(job, wr, escCmd, run, &cmdFlags, &cmdTemplate);
+ } else {
+
/*
- * The shell has no error control, so we need to be
- * weird to get it to ignore any errors from the command.
- * If echoing is turned on, we turn it off and use the
- * errOnOrEcho template to echo the command. Leave echoing
- * off so the user doesn't see the weirdness we go through
- * to ignore errors. Set cmdTemplate to use the weirdness
- * instead of the simple "%s\n" template.
+ * If errors are being checked and the shell doesn't have
+ * error control but does supply an runChkTmpl template, then
+ * set up commands to run through it.
*/
- job->flags |= JOB_IGNERR;
- if (!(job->flags & JOB_SILENT) && !shutUp) {
- if (commandShell->hasEchoCtl) {
- JobPrintln(job, commandShell->echoOff);
- }
- JobPrintf(job, commandShell->errOnOrEcho, escCmd);
- shutUp = TRUE;
- } else {
- if (!shutUp)
- JobPrintf(job, commandShell->errOnOrEcho, escCmd);
+
+ if (!shell->hasErrCtl && shell->runChkTmpl != NULL &&
+ shell->runChkTmpl[0] != '\0') {
+ if (job->echo && cmdFlags.echo) {
+ ShellWriter_EchoOff(wr);
+ ShellWriter_EchoCmd(wr, escCmd);
+ cmdFlags.echo = FALSE;
+ }
+ /*
+ * If it's a comment line or blank, avoid the possible
+ * syntax error generated by "{\n} || exit $?".
+ */
+ cmdTemplate = escCmd[0] == shell->commentChar ||
+ escCmd[0] == '\0'
+ ? shell->runIgnTmpl
+ : shell->runChkTmpl;
+ cmdFlags.ignerr = FALSE;
}
- cmdTemplate = commandShell->errOffOrExecIgnore;
- /*
- * The error ignoration (hee hee) is already taken care
- * of by the errOffOrExecIgnore template, so pretend error
- * checking is still on.
- */
- errOff = FALSE;
- } else {
- errOff = FALSE;
- }
- } else {
- errOff = FALSE;
}
- } else {
- /*
- * If errors are being checked and the shell doesn't have error control
- * but does supply an errExit template, then set up commands to run
- * through it.
- */
+ if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0)
+ ShellWriter_TraceOn(wr);
- if (!commandShell->hasErrCtl && commandShell->errExit &&
- commandShell->errExit[0] != '\0') {
- if (!(job->flags & JOB_SILENT) && !shutUp) {
- if (commandShell->hasEchoCtl)
- JobPrintln(job, commandShell->echoOff);
- JobPrintf(job, commandShell->errOnOrEcho, escCmd);
- shutUp = TRUE;
- }
- /* If it's a comment line or blank, treat as an ignored error */
- if (escCmd[0] == commandShell->commentChar ||
- (escCmd[0] == '\0'))
- cmdTemplate = commandShell->errOffOrExecIgnore;
- else
- cmdTemplate = commandShell->errExit;
- errOff = FALSE;
- }
- }
+ ShellWriter_PrintFmt(wr, cmdTemplate, xcmd);
+ free(xcmdStart);
+ free(escCmd);
- if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 &&
- !(job->flags & JOB_TRACED)) {
- JobPrintln(job, "set -x");
- job->flags |= JOB_TRACED;
- }
+ if (cmdFlags.ignerr)
+ ShellWriter_ErrOn(wr, cmdFlags.echo && job->echo);
- JobPrintf(job, cmdTemplate, cmd);
- free(cmdStart);
- free(escCmd);
- if (errOff) {
- /*
- * If echoing is already off, there's no point in issuing the
- * echoOff command. Otherwise we issue it and pretend it was on
- * for the whole command...
- */
- if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl) {
- JobPrintln(job, commandShell->echoOff);
- shutUp = TRUE;
- }
- JobPrintln(job, commandShell->errOnOrEcho);
- }
- if (shutUp && commandShell->hasEchoCtl)
- JobPrintln(job, commandShell->echoOn);
+ if (!cmdFlags.echo)
+ ShellWriter_EchoOn(wr);
}
-/* Print all commands to the shell file that is later executed.
+/*
+ * Print all commands to the shell file that is later executed.
*
* The special command "..." stops printing and saves the remaining commands
- * to be executed later. */
-static void
+ * to be executed later.
+ *
+ * Return whether at least one command was written to the shell file.
+ */
+static Boolean
JobPrintCommands(Job *job)
{
- StringListNode *ln;
+ StringListNode *ln;
+ Boolean seen = FALSE;
+ ShellWriter wr = { job->cmdFILE, FALSE };
+
+ for (ln = job->node->commands.first; ln != NULL; ln = ln->next) {
+ const char *cmd = ln->datum;
- for (ln = job->node->commands->first; ln != NULL; ln = ln->next) {
- const char *cmd = ln->datum;
+ if (strcmp(cmd, "...") == 0) {
+ job->node->type |= OP_SAVE_CMDS;
+ job->tailCmds = ln->next;
+ break;
+ }
- if (strcmp(cmd, "...") == 0) {
- job->node->type |= OP_SAVE_CMDS;
- job->tailCmds = ln->next;
- break;
+ JobPrintCommand(job, &wr, ln, ln->datum);
+ seen = TRUE;
}
- JobPrintCommand(job, ln->datum);
- }
+ return seen;
}
/* Save the delayed commands, to be executed when everything else is done. */
static void
JobSaveCommands(Job *job)
{
- StringListNode *node;
-
- for (node = job->tailCmds; node != NULL; node = node->next) {
- const char *cmd = node->datum;
- char *expanded_cmd;
- /* XXX: This Var_Subst is only intended to expand the dynamic
- * variables such as .TARGET, .IMPSRC. It is not intended to
- * expand the other variables as well; see deptgt-end.mk. */
- (void)Var_Subst(cmd, job->node, VARE_WANTRES, &expanded_cmd);
- /* TODO: handle errors */
- Lst_Append(Targ_GetEndNode()->commands, expanded_cmd);
- }
+ StringListNode *ln;
+
+ for (ln = job->tailCmds; ln != NULL; ln = ln->next) {
+ const char *cmd = ln->datum;
+ char *expanded_cmd;
+ /* XXX: This Var_Subst is only intended to expand the dynamic
+ * variables such as .TARGET, .IMPSRC. It is not intended to
+ * expand the other variables as well; see deptgt-end.mk. */
+ (void)Var_Subst(cmd, job->node, VARE_WANTRES, &expanded_cmd);
+ /* TODO: handle errors */
+ Lst_Append(&Targ_GetEndNode()->commands, expanded_cmd);
+ }
}
@@ -951,21 +1052,81 @@ JobSaveCommands(Job *job)
static void
JobClosePipes(Job *job)
{
- clearfd(job);
- (void)close(job->outPipe);
- job->outPipe = -1;
+ clearfd(job);
+ (void)close(job->outPipe);
+ job->outPipe = -1;
+
+ JobDoOutput(job, TRUE);
+ (void)close(job->inPipe);
+ job->inPipe = -1;
+}
+
+static void
+JobFinishDoneExitedError(Job *job, WAIT_T *inout_status)
+{
+ SwitchOutputTo(job->node);
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_error(job, job->node,
+ job->ignerr, WEXITSTATUS(*inout_status));
+ }
+#endif
+ if (!shouldDieQuietly(job->node, -1)) {
+ (void)printf("*** [%s] Error code %d%s\n",
+ job->node->name, WEXITSTATUS(*inout_status),
+ job->ignerr ? " (ignored)" : "");
+ }
+
+ if (job->ignerr)
+ WAIT_STATUS(*inout_status) = 0;
+ else {
+ if (deleteOnError)
+ JobDeleteTarget(job->node);
+ PrintOnError(job->node, NULL);
+ }
+}
+
+static void
+JobFinishDoneExited(Job *job, WAIT_T *inout_status)
+{
+ DEBUG2(JOB, "Process %d [%s] exited.\n", job->pid, job->node->name);
- JobDoOutput(job, TRUE);
- (void)close(job->inPipe);
- job->inPipe = -1;
+ if (WEXITSTATUS(*inout_status) != 0)
+ JobFinishDoneExitedError(job, inout_status);
+ else if (DEBUG(JOB)) {
+ SwitchOutputTo(job->node);
+ (void)printf("*** [%s] Completed successfully\n",
+ job->node->name);
+ }
}
-/* Do final processing for the given job including updating parent nodes and
+static void
+JobFinishDoneSignaled(Job *job, WAIT_T status)
+{
+ SwitchOutputTo(job->node);
+ (void)printf("*** [%s] Signal %d\n", job->node->name, WTERMSIG(status));
+ if (deleteOnError)
+ JobDeleteTarget(job->node);
+}
+
+static void
+JobFinishDone(Job *job, WAIT_T *inout_status)
+{
+ if (WIFEXITED(*inout_status))
+ JobFinishDoneExited(job, inout_status);
+ else
+ JobFinishDoneSignaled(job, *inout_status);
+
+ (void)fflush(stdout);
+}
+
+/*
+ * Do final processing for the given job including updating parent nodes and
* starting new jobs as available/necessary.
*
* Deferred commands for the job are placed on the .END node.
*
- * If there was a serious error (errors != 0; not an ignored one), no more
+ * If there was a serious error (job_errors != 0; not an ignored one), no more
* jobs will be started.
*
* Input:
@@ -975,203 +1136,155 @@ JobClosePipes(Job *job)
static void
JobFinish (Job *job, WAIT_T status)
{
- Boolean done, return_job_token;
+ Boolean done, return_job_token;
- DEBUG3(JOB, "JobFinish: %d [%s], status %d\n",
- job->pid, job->node->name, status);
+ DEBUG3(JOB, "JobFinish: %d [%s], status %d\n",
+ job->pid, job->node->name, status);
- if ((WIFEXITED(status) &&
- ((WEXITSTATUS(status) != 0 && !(job->flags & JOB_IGNERR)))) ||
- WIFSIGNALED(status))
- {
- /*
- * If it exited non-zero and either we're doing things our
- * way or we're not ignoring errors, the job is finished.
- * Similarly, if the shell died because of a signal
- * the job is also finished. In these
- * cases, finish out the job's output before printing the exit
- * status...
- */
- JobClosePipes(job);
- if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
- (void)fclose(job->cmdFILE);
- job->cmdFILE = NULL;
- }
- done = TRUE;
- } else if (WIFEXITED(status)) {
- /*
- * Deal with ignored errors in -B mode. We need to print a message
- * telling of the ignored error as well as to run the next command.
- *
- */
- done = WEXITSTATUS(status) != 0;
- JobClosePipes(job);
- } else {
- /*
- * No need to close things down or anything.
- */
- done = FALSE;
- }
+ if ((WIFEXITED(status) &&
+ ((WEXITSTATUS(status) != 0 && !job->ignerr))) ||
+ WIFSIGNALED(status)) {
+ /* Finished because of an error. */
- if (done) {
- if (WIFEXITED(status)) {
- DEBUG2(JOB, "Process %d [%s] exited.\n",
- job->pid, job->node->name);
- if (WEXITSTATUS(status) != 0) {
- if (job->node != lastNode) {
- MESSAGE(stdout, job->node);
- lastNode = job->node;
- }
-#ifdef USE_META
- if (useMeta) {
- meta_job_error(job, job->node, job->flags, WEXITSTATUS(status));
- }
-#endif
- if (!shouldDieQuietly(job->node, -1))
- (void)printf("*** [%s] Error code %d%s\n",
- job->node->name,
- WEXITSTATUS(status),
- (job->flags & JOB_IGNERR) ? " (ignored)" : "");
- if (job->flags & JOB_IGNERR) {
- WAIT_STATUS(status) = 0;
- } else {
- if (deleteOnError) {
- JobDeleteTarget(job->node);
- }
- PrintOnError(job->node, NULL);
+ JobClosePipes(job);
+ if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
}
- } else if (DEBUG(JOB)) {
- if (job->node != lastNode) {
- MESSAGE(stdout, job->node);
- lastNode = job->node;
- }
- (void)printf("*** [%s] Completed successfully\n",
- job->node->name);
- }
+ done = TRUE;
+
+ } else if (WIFEXITED(status)) {
+ /*
+ * Deal with ignored errors in -B mode. We need to print a
+ * message telling of the ignored error as well as to run
+ * the next command.
+ */
+ done = WEXITSTATUS(status) != 0;
+
+ JobClosePipes(job);
+
} else {
- if (job->node != lastNode) {
- MESSAGE(stdout, job->node);
- lastNode = job->node;
- }
- (void)printf("*** [%s] Signal %d\n",
- job->node->name, WTERMSIG(status));
- if (deleteOnError) {
- JobDeleteTarget(job->node);
- }
+ /* No need to close things down or anything. */
+ done = FALSE;
}
- (void)fflush(stdout);
- }
+
+ if (done)
+ JobFinishDone(job, &status);
#ifdef USE_META
- if (useMeta) {
- int meta_status = meta_job_finish(job);
- if (meta_status != 0 && status == 0)
- status = meta_status;
- }
+ if (useMeta) {
+ int meta_status = meta_job_finish(job);
+ if (meta_status != 0 && status == 0)
+ status = meta_status;
+ }
#endif
- return_job_token = FALSE;
+ return_job_token = FALSE;
- Trace_Log(JOBEND, job);
- if (!(job->flags & JOB_SPECIAL)) {
- if (WAIT_STATUS(status) != 0 ||
- (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT)
- return_job_token = TRUE;
- }
+ Trace_Log(JOBEND, job);
+ if (!job->special) {
+ if (WAIT_STATUS(status) != 0 ||
+ (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT)
+ return_job_token = TRUE;
+ }
- if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT &&
- (WAIT_STATUS(status) == 0)) {
- /*
- * As long as we aren't aborting and the job didn't return a non-zero
- * status that we shouldn't ignore, we call Make_Update to update
- * the parents.
- */
- JobSaveCommands(job);
- job->node->made = MADE;
- if (!(job->flags & JOB_SPECIAL))
- return_job_token = TRUE;
- Make_Update(job->node);
- job->status = JOB_ST_FREE;
- } else if (WAIT_STATUS(status)) {
- errors++;
- job->status = JOB_ST_FREE;
- }
+ if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT &&
+ (WAIT_STATUS(status) == 0)) {
+ /*
+ * As long as we aren't aborting and the job didn't return a
+ * non-zero status that we shouldn't ignore, we call
+ * Make_Update to update the parents.
+ */
+ JobSaveCommands(job);
+ job->node->made = MADE;
+ if (!job->special)
+ return_job_token = TRUE;
+ Make_Update(job->node);
+ job->status = JOB_ST_FREE;
+ } else if (status != 0) {
+ job_errors++;
+ job->status = JOB_ST_FREE;
+ }
- if (errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT)
- aborting = ABORT_ERROR; /* Prevent more jobs from getting started. */
+ if (job_errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT) {
+ /* Prevent more jobs from getting started. */
+ aborting = ABORT_ERROR;
+ }
- if (return_job_token)
- Job_TokenReturn();
+ if (return_job_token)
+ Job_TokenReturn();
- if (aborting == ABORT_ERROR && jobTokensRunning == 0)
- Finish(errors);
+ if (aborting == ABORT_ERROR && jobTokensRunning == 0)
+ Finish(job_errors);
}
static void
TouchRegular(GNode *gn)
{
- const char *file = GNode_Path(gn);
- struct utimbuf times = { now, now };
- int fd;
- char c;
-
- if (utime(file, &times) >= 0)
- return;
-
- fd = open(file, O_RDWR | O_CREAT, 0666);
- if (fd < 0) {
- (void)fprintf(stderr, "*** couldn't touch %s: %s\n",
- file, strerror(errno));
- (void)fflush(stderr);
- return; /* XXX: What about propagating the error? */
- }
+ const char *file = GNode_Path(gn);
+ struct utimbuf times = { now, now };
+ int fd;
+ char c;
+
+ if (utime(file, &times) >= 0)
+ return;
+
+ fd = open(file, O_RDWR | O_CREAT, 0666);
+ if (fd < 0) {
+ (void)fprintf(stderr, "*** couldn't touch %s: %s\n",
+ file, strerror(errno));
+ (void)fflush(stderr);
+ return; /* XXX: What about propagating the error? */
+ }
- /* Last resort: update the file's time stamps in the traditional way.
- * XXX: This doesn't work for empty files, which are sometimes used
- * as marker files. */
- if (read(fd, &c, 1) == 1) {
- (void)lseek(fd, 0, SEEK_SET);
- while (write(fd, &c, 1) == -1 && errno == EAGAIN)
- continue;
- }
- (void)close(fd); /* XXX: What about propagating the error? */
+ /* Last resort: update the file's time stamps in the traditional way.
+ * XXX: This doesn't work for empty files, which are sometimes used
+ * as marker files. */
+ if (read(fd, &c, 1) == 1) {
+ (void)lseek(fd, 0, SEEK_SET);
+ while (write(fd, &c, 1) == -1 && errno == EAGAIN)
+ continue;
+ }
+ (void)close(fd); /* XXX: What about propagating the error? */
}
-/* Touch the given target. Called by JobStart when the -t flag was given.
+/*
+ * Touch the given target. Called by JobStart when the -t flag was given.
*
* The modification date of the file is changed.
- * If the file did not exist, it is created. */
+ * If the file did not exist, it is created.
+ */
void
-Job_Touch(GNode *gn, Boolean silent)
+Job_Touch(GNode *gn, Boolean echo)
{
- if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL|
- OP_SPECIAL|OP_PHONY)) {
- /* These are "virtual" targets and should not really be created. */
- return;
- }
-
- if (!silent || !GNode_ShouldExecute(gn)) {
- (void)fprintf(stdout, "touch %s\n", gn->name);
- (void)fflush(stdout);
- }
-
- if (!GNode_ShouldExecute(gn))
- return;
+ if (gn->type &
+ (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC | OP_OPTIONAL |
+ OP_SPECIAL | OP_PHONY)) {
+ /*
+ * These are "virtual" targets and should not really be
+ * created.
+ */
+ return;
+ }
- if (gn->type & OP_ARCHV) {
- Arch_Touch(gn);
- return;
- }
+ if (echo || !GNode_ShouldExecute(gn)) {
+ (void)fprintf(stdout, "touch %s\n", gn->name);
+ (void)fflush(stdout);
+ }
- if (gn->type & OP_LIB) {
- Arch_TouchLib(gn);
- return;
- }
+ if (!GNode_ShouldExecute(gn))
+ return;
- TouchRegular(gn);
+ if (gn->type & OP_ARCHV)
+ Arch_Touch(gn);
+ else if (gn->type & OP_LIB)
+ Arch_TouchLib(gn);
+ else
+ TouchRegular(gn);
}
-/* Make sure the given node has all the commands it needs.
+/*
+ * Make sure the given node has all the commands it needs.
*
* The node will have commands from the .DEFAULT rule added to it if it
* needs them.
@@ -1186,504 +1299,498 @@ Job_Touch(GNode *gn, Boolean silent)
Boolean
Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
{
- if (GNode_IsTarget(gn))
- return TRUE;
- if (!Lst_IsEmpty(gn->commands))
- return TRUE;
- if ((gn->type & OP_LIB) && !Lst_IsEmpty(gn->children))
- return TRUE;
+ if (GNode_IsTarget(gn))
+ return TRUE;
+ if (!Lst_IsEmpty(&gn->commands))
+ return TRUE;
+ if ((gn->type & OP_LIB) && !Lst_IsEmpty(&gn->children))
+ return TRUE;
- /*
- * No commands. Look for .DEFAULT rule from which we might infer
- * commands.
- */
- if (defaultNode != NULL && !Lst_IsEmpty(defaultNode->commands) &&
- !(gn->type & OP_SPECIAL)) {
/*
- * The traditional Make only looks for a .DEFAULT if the node was
- * never the target of an operator, so that's what we do too.
- *
- * The .DEFAULT node acts like a transformation rule, in that
- * gn also inherits any attributes or sources attached to
- * .DEFAULT itself.
+ * No commands. Look for .DEFAULT rule from which we might infer
+ * commands.
*/
- Make_HandleUse(defaultNode, gn);
- Var_Set(IMPSRC, GNode_VarTarget(gn), gn);
- return TRUE;
- }
+ if (defaultNode != NULL && !Lst_IsEmpty(&defaultNode->commands) &&
+ !(gn->type & OP_SPECIAL)) {
+ /*
+ * The traditional Make only looks for a .DEFAULT if the node
+ * was never the target of an operator, so that's what we do
+ * too.
+ *
+ * The .DEFAULT node acts like a transformation rule, in that
+ * gn also inherits any attributes or sources attached to
+ * .DEFAULT itself.
+ */
+ Make_HandleUse(defaultNode, gn);
+ Var_Set(IMPSRC, GNode_VarTarget(gn), gn);
+ return TRUE;
+ }
- Dir_UpdateMTime(gn, FALSE);
- if (gn->mtime != 0 || (gn->type & OP_SPECIAL))
- return TRUE;
+ Dir_UpdateMTime(gn, FALSE);
+ if (gn->mtime != 0 || (gn->type & OP_SPECIAL))
+ return TRUE;
- /*
- * The node wasn't the target of an operator. We have no .DEFAULT
- * rule to go on and the target doesn't already exist. There's
- * nothing more we can do for this branch. If the -k flag wasn't
- * given, we stop in our tracks, otherwise we just don't update
- * this node's parents so they never get examined.
- */
+ /*
+ * The node wasn't the target of an operator. We have no .DEFAULT
+ * rule to go on and the target doesn't already exist. There's
+ * nothing more we can do for this branch. If the -k flag wasn't
+ * given, we stop in our tracks, otherwise we just don't update
+ * this node's parents so they never get examined.
+ */
- if (gn->flags & FROM_DEPEND) {
- if (!Job_RunTarget(".STALE", gn->fname))
- fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n",
- progname, gn->fname, gn->lineno, makeDependfile,
- gn->name);
- return TRUE;
- }
+ if (gn->flags & FROM_DEPEND) {
+ if (!Job_RunTarget(".STALE", gn->fname))
+ fprintf(stdout,
+ "%s: %s, %d: ignoring stale %s for %s\n",
+ progname, gn->fname, gn->lineno, makeDependfile,
+ gn->name);
+ return TRUE;
+ }
- if (gn->type & OP_OPTIONAL) {
- (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n",
- progname, gn->name, "ignored");
- (void)fflush(stdout);
- return TRUE;
- }
+ if (gn->type & OP_OPTIONAL) {
+ (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n",
+ progname, gn->name, "ignored");
+ (void)fflush(stdout);
+ return TRUE;
+ }
- if (opts.keepgoing) {
- (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n",
- progname, gn->name, "continuing");
- (void)fflush(stdout);
- return FALSE;
- }
+ if (opts.keepgoing) {
+ (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n",
+ progname, gn->name, "continuing");
+ (void)fflush(stdout);
+ return FALSE;
+ }
- abortProc("%s: don't know how to make %s. Stop", progname, gn->name);
- return FALSE;
+ abortProc("%s: don't know how to make %s. Stop", progname, gn->name);
+ return FALSE;
}
-/* Execute the shell for the given job.
+/*
+ * Execute the shell for the given job.
*
- * See Job_CatchOutput for handling the output of the shell. */
+ * See Job_CatchOutput for handling the output of the shell.
+ */
static void
JobExec(Job *job, char **argv)
{
- int cpid; /* ID of new child */
- sigset_t mask;
-
- job->flags &= ~JOB_TRACED;
+ int cpid; /* ID of new child */
+ sigset_t mask;
- if (DEBUG(JOB)) {
- int i;
+ if (DEBUG(JOB)) {
+ int i;
- debug_printf("Running %s\n", job->node->name);
- debug_printf("\tCommand: ");
- for (i = 0; argv[i] != NULL; i++) {
- debug_printf("%s ", argv[i]);
+ debug_printf("Running %s\n", job->node->name);
+ debug_printf("\tCommand: ");
+ for (i = 0; argv[i] != NULL; i++) {
+ debug_printf("%s ", argv[i]);
+ }
+ debug_printf("\n");
}
- debug_printf("\n");
- }
- /*
- * Some jobs produce no output and it's disconcerting to have
- * no feedback of their running (since they produce no output, the
- * banner with their name in it never appears). This is an attempt to
- * provide that feedback, even if nothing follows it.
- */
- if ((lastNode != job->node) && !(job->flags & JOB_SILENT)) {
- MESSAGE(stdout, job->node);
- lastNode = job->node;
- }
+ /*
+ * Some jobs produce no output and it's disconcerting to have
+ * no feedback of their running (since they produce no output, the
+ * banner with their name in it never appears). This is an attempt to
+ * provide that feedback, even if nothing follows it.
+ */
+ if (job->echo)
+ SwitchOutputTo(job->node);
- /* No interruptions until this job is on the `jobs' list */
- JobSigLock(&mask);
+ /* No interruptions until this job is on the `jobs' list */
+ JobSigLock(&mask);
+
+ /* Pre-emptively mark job running, pid still zero though */
+ job->status = JOB_ST_RUNNING;
- /* Pre-emptively mark job running, pid still zero though */
- job->status = JOB_ST_RUNNING;
+ Var_ReexportVars();
- cpid = vFork();
- if (cpid == -1)
- Punt("Cannot vfork: %s", strerror(errno));
+ cpid = vFork();
+ if (cpid == -1)
+ Punt("Cannot vfork: %s", strerror(errno));
- if (cpid == 0) {
- /* Child */
- sigset_t tmask;
+ if (cpid == 0) {
+ /* Child */
+ sigset_t tmask;
#ifdef USE_META
- if (useMeta) {
- meta_job_child(job);
- }
+ if (useMeta) {
+ meta_job_child(job);
+ }
#endif
- /*
- * Reset all signal handlers; this is necessary because we also
- * need to unblock signals before we exec(2).
- */
- JobSigReset();
+ /*
+ * Reset all signal handlers; this is necessary because we
+ * also need to unblock signals before we exec(2).
+ */
+ JobSigReset();
- /* Now unblock signals */
- sigemptyset(&tmask);
- JobSigUnlock(&tmask);
+ /* Now unblock signals */
+ sigemptyset(&tmask);
+ JobSigUnlock(&tmask);
- /*
- * Must duplicate the input stream down to the child's input and
- * reset it to the beginning (again). Since the stream was marked
- * close-on-exec, we must clear that bit in the new input.
- */
- if (dup2(fileno(job->cmdFILE), 0) == -1)
- execDie("dup2", "job->cmdFILE");
- if (fcntl(0, F_SETFD, 0) == -1)
- execDie("fcntl clear close-on-exec", "stdin");
- if (lseek(0, 0, SEEK_SET) == -1)
- execDie("lseek to 0", "stdin");
-
- if (job->node->type & (OP_MAKE | OP_SUBMAKE)) {
- /*
- * Pass job token pipe to submakes.
- */
- if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1)
- execDie("clear close-on-exec", "tokenWaitJob.inPipe");
- if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1)
- execDie("clear close-on-exec", "tokenWaitJob.outPipe");
- }
+ /*
+ * Must duplicate the input stream down to the child's input
+ * and reset it to the beginning (again). Since the stream
+ * was marked close-on-exec, we must clear that bit in the
+ * new input.
+ */
+ if (dup2(fileno(job->cmdFILE), 0) == -1)
+ execDie("dup2", "job->cmdFILE");
+ if (fcntl(0, F_SETFD, 0) == -1)
+ execDie("fcntl clear close-on-exec", "stdin");
+ if (lseek(0, 0, SEEK_SET) == -1)
+ execDie("lseek to 0", "stdin");
+
+ if (job->node->type & (OP_MAKE | OP_SUBMAKE)) {
+ /*
+ * Pass job token pipe to submakes.
+ */
+ if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1)
+ execDie("clear close-on-exec",
+ "tokenWaitJob.inPipe");
+ if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1)
+ execDie("clear close-on-exec",
+ "tokenWaitJob.outPipe");
+ }
- /*
- * Set up the child's output to be routed through the pipe
- * we've created for it.
- */
- if (dup2(job->outPipe, 1) == -1)
- execDie("dup2", "job->outPipe");
+ /*
+ * Set up the child's output to be routed through the pipe
+ * we've created for it.
+ */
+ if (dup2(job->outPipe, 1) == -1)
+ execDie("dup2", "job->outPipe");
- /*
- * The output channels are marked close on exec. This bit was
- * duplicated by the dup2(on some systems), so we have to clear
- * it before routing the shell's error output to the same place as
- * its standard output.
- */
- if (fcntl(1, F_SETFD, 0) == -1)
- execDie("clear close-on-exec", "stdout");
- if (dup2(1, 2) == -1)
- execDie("dup2", "1, 2");
+ /*
+ * The output channels are marked close on exec. This bit
+ * was duplicated by the dup2(on some systems), so we have
+ * to clear it before routing the shell's error output to
+ * the same place as its standard output.
+ */
+ if (fcntl(1, F_SETFD, 0) == -1)
+ execDie("clear close-on-exec", "stdout");
+ if (dup2(1, 2) == -1)
+ execDie("dup2", "1, 2");
- /*
- * We want to switch the child into a different process family so
- * we can kill it and all its descendants in one fell swoop,
- * by killing its process family, but not commit suicide.
- */
+ /*
+ * We want to switch the child into a different process
+ * family so we can kill it and all its descendants in
+ * one fell swoop, by killing its process family, but not
+ * commit suicide.
+ */
#if defined(HAVE_SETPGID)
- (void)setpgid(0, getpid());
+ (void)setpgid(0, getpid());
#else
#if defined(HAVE_SETSID)
- /* XXX: dsl - I'm sure this should be setpgrp()... */
- (void)setsid();
+ /* XXX: dsl - I'm sure this should be setpgrp()... */
+ (void)setsid();
#else
- (void)setpgrp(0, getpid());
+ (void)setpgrp(0, getpid());
#endif
#endif
- Var_ExportVars();
-
- (void)execv(shellPath, argv);
- execDie("exec", shellPath);
- }
+ (void)execv(shellPath, argv);
+ execDie("exec", shellPath);
+ }
- /* Parent, continuing after the child exec */
- job->pid = cpid;
+ /* Parent, continuing after the child exec */
+ job->pid = cpid;
- Trace_Log(JOBSTART, job);
+ Trace_Log(JOBSTART, job);
#ifdef USE_META
- if (useMeta) {
- meta_job_parent(job, cpid);
- }
+ if (useMeta) {
+ meta_job_parent(job, cpid);
+ }
#endif
- /*
- * Set the current position in the buffer to the beginning
- * and mark another stream to watch in the outputs mask
- */
- job->curPos = 0;
+ /*
+ * Set the current position in the buffer to the beginning
+ * and mark another stream to watch in the outputs mask
+ */
+ job->curPos = 0;
- watchfd(job);
+ watchfd(job);
- if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
- (void)fclose(job->cmdFILE);
- job->cmdFILE = NULL;
- }
+ if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
+ }
- /*
- * Now the job is actually running, add it to the table.
- */
- if (DEBUG(JOB)) {
- debug_printf("JobExec(%s): pid %d added to jobs table\n",
- job->node->name, job->pid);
- job_table_dump("job started");
- }
- JobSigUnlock(&mask);
+ /*
+ * Now the job is actually running, add it to the table.
+ */
+ if (DEBUG(JOB)) {
+ debug_printf("JobExec(%s): pid %d added to jobs table\n",
+ job->node->name, job->pid);
+ job_table_dump("job started");
+ }
+ JobSigUnlock(&mask);
}
/* Create the argv needed to execute the shell for a given job. */
static void
JobMakeArgv(Job *job, char **argv)
{
- int argc;
- static char args[10]; /* For merged arguments */
+ int argc;
+ static char args[10]; /* For merged arguments */
- argv[0] = UNCONST(shellName);
- argc = 1;
-
- if ((commandShell->exit && commandShell->exit[0] != '-') ||
- (commandShell->echo && commandShell->echo[0] != '-'))
- {
- /*
- * At least one of the flags doesn't have a minus before it, so
- * merge them together. Have to do this because the *(&(@*#*&#$#
- * Bourne shell thinks its second argument is a file to source.
- * Grrrr. Note the ten-character limitation on the combined arguments.
- */
- (void)snprintf(args, sizeof args, "-%s%s",
- ((job->flags & JOB_IGNERR) ? "" :
- (commandShell->exit ? commandShell->exit : "")),
- ((job->flags & JOB_SILENT) ? "" :
- (commandShell->echo ? commandShell->echo : "")));
+ argv[0] = UNCONST(shellName);
+ argc = 1;
- if (args[1]) {
- argv[argc] = args;
- argc++;
- }
- } else {
- if (!(job->flags & JOB_IGNERR) && commandShell->exit) {
- argv[argc] = UNCONST(commandShell->exit);
- argc++;
- }
- if (!(job->flags & JOB_SILENT) && commandShell->echo) {
- argv[argc] = UNCONST(commandShell->echo);
- argc++;
+ if ((shell->errFlag != NULL && shell->errFlag[0] != '-') ||
+ (shell->echoFlag != NULL && shell->echoFlag[0] != '-')) {
+ /*
+ * At least one of the flags doesn't have a minus before it,
+ * so merge them together. Have to do this because the Bourne
+ * shell thinks its second argument is a file to source.
+ * Grrrr. Note the ten-character limitation on the combined
+ * arguments.
+ *
+ * TODO: Research until when the above comments were
+ * practically relevant.
+ */
+ (void)snprintf(args, sizeof args, "-%s%s",
+ (job->ignerr ? "" :
+ (shell->errFlag != NULL ? shell->errFlag : "")),
+ (!job->echo ? "" :
+ (shell->echoFlag != NULL ? shell->echoFlag : "")));
+
+ if (args[1] != '\0') {
+ argv[argc] = args;
+ argc++;
+ }
+ } else {
+ if (!job->ignerr && shell->errFlag != NULL) {
+ argv[argc] = UNCONST(shell->errFlag);
+ argc++;
+ }
+ if (job->echo && shell->echoFlag != NULL) {
+ argv[argc] = UNCONST(shell->echoFlag);
+ argc++;
+ }
}
- }
- argv[argc] = NULL;
+ argv[argc] = NULL;
}
-/*-
- *-----------------------------------------------------------------------
- * JobStart --
- * Start a target-creation process going for the target described
- * by the graph node gn.
- *
- * Input:
- * gn target to create
- * flags flags for the job to override normal ones.
- * previous The previous Job structure for this node, if any.
- *
- * Results:
- * JOB_ERROR if there was an error in the commands, JOB_FINISHED
- * if there isn't actually anything left to do for the job and
- * JOB_RUNNING if the job has been started.
- *
- * Side Effects:
- * A new Job node is created and added to the list of running
- * jobs. PMake is forked and a child shell created.
- *
- * NB: The return value is ignored by everyone.
- *-----------------------------------------------------------------------
- */
-static JobStartResult
-JobStart(GNode *gn, JobFlags flags)
+static void
+JobOpenTmpFile(Job *job, GNode *gn, Boolean cmdsOK, Boolean *out_run)
{
- Job *job; /* new job descriptor */
- char *argv[10]; /* Argument vector to shell */
- Boolean cmdsOK; /* true if the nodes commands were all right */
- Boolean noExec; /* Set true if we decide not to run the job */
- int tfd; /* File descriptor to the temp file */
-
- for (job = job_table; job < job_table_end; job++) {
- if (job->status == JOB_ST_FREE)
- break;
- }
- if (job >= job_table_end)
- Punt("JobStart no job slots vacant");
-
- memset(job, 0, sizeof *job);
- job->node = gn;
- job->tailCmds = NULL;
- job->status = JOB_ST_SET_UP;
-
- if (gn->type & OP_SPECIAL)
- flags |= JOB_SPECIAL;
- if (Targ_Ignore(gn))
- flags |= JOB_IGNERR;
- if (Targ_Silent(gn))
- flags |= JOB_SILENT;
- job->flags = flags;
-
- /*
- * Check the commands now so any attributes from .DEFAULT have a chance
- * to migrate to the node
- */
- cmdsOK = Job_CheckCommands(gn, Error);
-
- job->inPollfd = NULL;
- /*
- * If the -n flag wasn't given, we open up OUR (not the child's)
- * temporary file to stuff commands in it. The thing is rd/wr so we don't
- * need to reopen it to feed it to the shell. If the -n flag *was* given,
- * we just set the file to be stdout. Cute, huh?
- */
- if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) ||
- (!opts.noExecute && !opts.touchFlag)) {
/*
- * tfile is the name of a file into which all shell commands are
- * put. It is removed before the child shell is executed, unless
- * DEBUG(SCRIPT) is set.
+ * tfile is the name of a file into which all shell commands
+ * are put. It is removed before the child shell is executed,
+ * unless DEBUG(SCRIPT) is set.
*/
char *tfile;
sigset_t mask;
+ int tfd; /* File descriptor to the temp file */
+
/*
* We're serious here, but if the commands were bogus, we're
* also dead...
*/
if (!cmdsOK) {
- PrintOnError(gn, NULL); /* provide some clue */
- DieHorribly();
+ PrintOnError(gn, NULL); /* provide some clue */
+ DieHorribly();
}
JobSigLock(&mask);
tfd = mkTempFile(TMPPAT, &tfile);
if (!DEBUG(SCRIPT))
- (void)eunlink(tfile);
+ (void)eunlink(tfile);
JobSigUnlock(&mask);
job->cmdFILE = fdopen(tfd, "w+");
if (job->cmdFILE == NULL)
- Punt("Could not fdopen %s", tfile);
+ Punt("Could not fdopen %s", tfile);
(void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC);
/*
- * Send the commands to the command file, flush all its buffers then
- * rewind and remove the thing.
+ * Send the commands to the command file, flush all its
+ * buffers then rewind and remove the thing.
*/
- noExec = FALSE;
+ *out_run = TRUE;
#ifdef USE_META
if (useMeta) {
- meta_job_start(job, gn);
- if (Targ_Silent(gn)) /* might have changed */
- job->flags |= JOB_SILENT;
+ meta_job_start(job, gn);
+ if (gn->type & OP_SILENT) /* might have changed */
+ job->echo = FALSE;
}
#endif
- /*
- * We can do all the commands at once. hooray for sanity
- */
- numCommands = 0;
- JobPrintCommands(job);
- /*
- * If we didn't print out any commands to the shell script,
- * there's not much point in executing the shell, is there?
- */
- if (numCommands == 0) {
- noExec = TRUE;
- }
+ /* We can do all the commands at once. hooray for sanity */
+ if (!JobPrintCommands(job))
+ *out_run = FALSE;
free(tfile);
- } else if (!GNode_ShouldExecute(gn)) {
- /*
- * Not executing anything -- just print all the commands to stdout
- * in one fell swoop. This will still set up job->tailCmds correctly.
- */
- if (lastNode != gn) {
- MESSAGE(stdout, gn);
- lastNode = gn;
+}
+
+/*
+ * Start a target-creation process going for the target described by the
+ * graph node gn.
+ *
+ * Input:
+ * gn target to create
+ * flags flags for the job to override normal ones.
+ * previous The previous Job structure for this node, if any.
+ *
+ * Results:
+ * JOB_ERROR if there was an error in the commands, JOB_FINISHED
+ * if there isn't actually anything left to do for the job and
+ * JOB_RUNNING if the job has been started.
+ *
+ * Side Effects:
+ * A new Job node is created and added to the list of running
+ * jobs. PMake is forked and a child shell created.
+ *
+ * NB: The return value is ignored by everyone.
+ */
+static JobStartResult
+JobStart(GNode *gn, Boolean special)
+{
+ Job *job; /* new job descriptor */
+ char *argv[10]; /* Argument vector to shell */
+ Boolean cmdsOK; /* true if the nodes commands were all right */
+ Boolean run;
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->status == JOB_ST_FREE)
+ break;
}
- job->cmdFILE = stdout;
- /*
- * Only print the commands if they're ok, but don't die if they're
- * not -- just let the user know they're bad and keep going. It
- * doesn't do any harm in this case and may do some good.
- */
- if (cmdsOK)
- JobPrintCommands(job);
- /*
- * Don't execute the shell, thank you.
- */
- noExec = TRUE;
- } else {
+ if (job >= job_table_end)
+ Punt("JobStart no job slots vacant");
+
+ memset(job, 0, sizeof *job);
+ job->node = gn;
+ job->tailCmds = NULL;
+ job->status = JOB_ST_SET_UP;
+
+ job->special = special || gn->type & OP_SPECIAL;
+ job->ignerr = opts.ignoreErrors || gn->type & OP_IGNORE;
+ job->echo = !(opts.beSilent || gn->type & OP_SILENT);
+
/*
- * Just touch the target and note that no shell should be executed.
- * Set cmdFILE to stdout to make life easier. Check the commands, too,
- * but don't die if they're no good -- it does no harm to keep working
- * up the graph.
+ * Check the commands now so any attributes from .DEFAULT have a
+ * chance to migrate to the node.
*/
- job->cmdFILE = stdout;
- Job_Touch(gn, job->flags & JOB_SILENT);
- noExec = TRUE;
- }
- /* Just in case it isn't already... */
- (void)fflush(job->cmdFILE);
+ cmdsOK = Job_CheckCommands(gn, Error);
- /*
- * If we're not supposed to execute a shell, don't.
- */
- if (noExec) {
- if (!(job->flags & JOB_SPECIAL))
- Job_TokenReturn();
+ job->inPollfd = NULL;
/*
- * Unlink and close the command file if we opened one
+ * If the -n flag wasn't given, we open up OUR (not the child's)
+ * temporary file to stuff commands in it. The thing is rd/wr so
+ * we don't need to reopen it to feed it to the shell. If the -n
+ * flag *was* given, we just set the file to be stdout. Cute, huh?
*/
- if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
- (void)fclose(job->cmdFILE);
- job->cmdFILE = NULL;
+ if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) ||
+ (!opts.noExecute && !opts.touchFlag)) {
+ JobOpenTmpFile(job, gn, cmdsOK, &run);
+ } else if (!GNode_ShouldExecute(gn)) {
+ /*
+ * Not executing anything -- just print all the commands to
+ * stdout in one fell swoop. This will still set up
+ * job->tailCmds correctly.
+ */
+ SwitchOutputTo(gn);
+ job->cmdFILE = stdout;
+ /*
+ * Only print the commands if they're ok, but don't die if
+ * they're not -- just let the user know they're bad and
+ * keep going. It doesn't do any harm in this case and may
+ * do some good.
+ */
+ if (cmdsOK)
+ JobPrintCommands(job);
+ /* Don't execute the shell, thank you. */
+ run = FALSE;
+ } else {
+ /*
+ * Just touch the target and note that no shell should be
+ * executed. Set cmdFILE to stdout to make life easier.
+ * Check the commands, too, but don't die if they're no
+ * good -- it does no harm to keep working up the graph.
+ */
+ job->cmdFILE = stdout;
+ Job_Touch(gn, job->echo);
+ run = FALSE;
}
+ /* Just in case it isn't already... */
+ (void)fflush(job->cmdFILE);
+
+ /* If we're not supposed to execute a shell, don't. */
+ if (!run) {
+ if (!job->special)
+ Job_TokenReturn();
+ /* Unlink and close the command file if we opened one */
+ if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
+ }
- /*
- * We only want to work our way up the graph if we aren't here because
- * the commands for the job were no good.
- */
- if (cmdsOK && aborting == ABORT_NONE) {
- JobSaveCommands(job);
- job->node->made = MADE;
- Make_Update(job->node);
+ /*
+ * We only want to work our way up the graph if we aren't
+ * here because the commands for the job were no good.
+ */
+ if (cmdsOK && aborting == ABORT_NONE) {
+ JobSaveCommands(job);
+ job->node->made = MADE;
+ Make_Update(job->node);
+ }
+ job->status = JOB_ST_FREE;
+ return cmdsOK ? JOB_FINISHED : JOB_ERROR;
}
- job->status = JOB_ST_FREE;
- return cmdsOK ? JOB_FINISHED : JOB_ERROR;
- }
- /*
- * Set up the control arguments to the shell. This is based on the flags
- * set earlier for this job.
- */
- JobMakeArgv(job, argv);
+ /*
+ * Set up the control arguments to the shell. This is based on the
+ * flags set earlier for this job.
+ */
+ JobMakeArgv(job, argv);
- /* Create the pipe by which we'll get the shell's output. */
- JobCreatePipe(job, 3);
+ /* Create the pipe by which we'll get the shell's output. */
+ JobCreatePipe(job, 3);
- JobExec(job, argv);
- return JOB_RUNNING;
+ JobExec(job, argv);
+ return JOB_RUNNING;
}
-/* Print the output of the shell command, skipping the noPrint command of
- * the shell, if any. */
+/*
+ * Print the output of the shell command, skipping the noPrint text of the
+ * shell, if any. The default shell does not have noPrint though, which means
+ * that in all practical cases, handling the output is left to the caller.
+ */
static char *
-JobOutput(Job *job, char *cp, char *endp)
+JobOutput(char *cp, char *endp) /* XXX: should all be const */
{
- char *ecp;
+ char *ecp; /* XXX: should be const */
- if (commandShell->noPrint == NULL || commandShell->noPrint[0] == '\0')
- return cp;
+ if (shell->noPrint == NULL || shell->noPrint[0] == '\0')
+ return cp;
- while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) {
- if (ecp != cp) {
- *ecp = '\0';
- /*
- * The only way there wouldn't be a newline after
- * this line is if it were the last in the buffer.
- * however, since the non-printable comes after it,
- * there must be a newline, so we don't print one.
- */
- (void)fprintf(stdout, "%s", cp);
- (void)fflush(stdout);
- }
- cp = ecp + commandShell->noPrintLen;
- if (cp != endp) {
- /*
- * Still more to print, look again after skipping
- * the whitespace following the non-printable
- * command....
- */
- cp++;
- pp_skip_whitespace(&cp);
- } else {
- return cp;
+ /*
+ * XXX: What happens if shell->noPrint occurs on the boundary of
+ * the buffer? To work correctly in all cases, this should rather
+ * be a proper stream filter instead of doing string matching on
+ * selected chunks of the output.
+ */
+ while ((ecp = strstr(cp, shell->noPrint)) != NULL) {
+ if (ecp != cp) {
+ *ecp = '\0'; /* XXX: avoid writing to the buffer */
+ /*
+ * The only way there wouldn't be a newline after
+ * this line is if it were the last in the buffer.
+ * however, since the noPrint output comes after it,
+ * there must be a newline, so we don't print one.
+ */
+ /* XXX: What about null bytes in the output? */
+ (void)fprintf(stdout, "%s", cp);
+ (void)fflush(stdout);
+ }
+ cp = ecp + shell->noPrintLen;
+ if (cp == endp)
+ break;
+ cp++; /* skip over the (XXX: assumed) newline */
+ pp_skip_whitespace(&cp);
}
- }
- return cp;
+ return cp;
}
/*
@@ -1704,168 +1811,170 @@ JobOutput(Job *job, char *cp, char *endp)
static void
JobDoOutput(Job *job, Boolean finish)
{
- Boolean gotNL = FALSE; /* true if got a newline */
- Boolean fbuf; /* true if our buffer filled up */
- size_t nr; /* number of bytes read */
- size_t i; /* auxiliary index into outBuf */
- size_t max; /* limit for i (end of current data) */
- ssize_t nRead; /* (Temporary) number of bytes read */
-
- /*
- * Read as many bytes as will fit in the buffer.
- */
+ Boolean gotNL; /* true if got a newline */
+ Boolean fbuf; /* true if our buffer filled up */
+ size_t nr; /* number of bytes read */
+ size_t i; /* auxiliary index into outBuf */
+ size_t max; /* limit for i (end of current data) */
+ ssize_t nRead; /* (Temporary) number of bytes read */
+
+ /* Read as many bytes as will fit in the buffer. */
again:
- gotNL = FALSE;
- fbuf = FALSE;
-
- nRead = read(job->inPipe, &job->outBuf[job->curPos],
- JOB_BUFSIZE - job->curPos);
- if (nRead < 0) {
- if (errno == EAGAIN)
- return;
- if (DEBUG(JOB)) {
- perror("JobDoOutput(piperead)");
+ gotNL = FALSE;
+ fbuf = FALSE;
+
+ nRead = read(job->inPipe, &job->outBuf[job->curPos],
+ JOB_BUFSIZE - job->curPos);
+ if (nRead < 0) {
+ if (errno == EAGAIN)
+ return;
+ if (DEBUG(JOB)) {
+ perror("JobDoOutput(piperead)");
+ }
+ nr = 0;
+ } else {
+ nr = (size_t)nRead;
}
- nr = 0;
- } else {
- nr = (size_t)nRead;
- }
-
- /*
- * If we hit the end-of-file (the job is dead), we must flush its
- * remaining output, so pretend we read a newline if there's any
- * output remaining in the buffer.
- * Also clear the 'finish' flag so we stop looping.
- */
- if (nr == 0 && job->curPos != 0) {
- job->outBuf[job->curPos] = '\n';
- nr = 1;
- finish = FALSE;
- } else if (nr == 0) {
- finish = FALSE;
- }
- /*
- * Look for the last newline in the bytes we just got. If there is
- * one, break out of the loop with 'i' as its index and gotNL set
- * TRUE.
- */
- max = job->curPos + nr;
- for (i = job->curPos + nr - 1; i >= job->curPos && i != (size_t)-1; i--) {
- if (job->outBuf[i] == '\n') {
- gotNL = TRUE;
- break;
- } else if (job->outBuf[i] == '\0') {
- /*
- * Why?
- */
- job->outBuf[i] = ' ';
+ /*
+ * If we hit the end-of-file (the job is dead), we must flush its
+ * remaining output, so pretend we read a newline if there's any
+ * output remaining in the buffer.
+ * Also clear the 'finish' flag so we stop looping.
+ */
+ if (nr == 0 && job->curPos != 0) {
+ job->outBuf[job->curPos] = '\n';
+ nr = 1;
+ finish = FALSE;
+ } else if (nr == 0) {
+ finish = FALSE;
}
- }
- if (!gotNL) {
- job->curPos += nr;
- if (job->curPos == JOB_BUFSIZE) {
- /*
- * If we've run out of buffer space, we have no choice
- * but to print the stuff. sigh.
- */
- fbuf = TRUE;
- i = job->curPos;
- }
- }
- if (gotNL || fbuf) {
/*
- * Need to send the output to the screen. Null terminate it
- * first, overwriting the newline character if there was one.
- * So long as the line isn't one we should filter (according
- * to the shell description), we print the line, preceded
- * by a target banner if this target isn't the same as the
- * one for which we last printed something.
- * The rest of the data in the buffer are then shifted down
- * to the start of the buffer and curPos is set accordingly.
+ * Look for the last newline in the bytes we just got. If there is
+ * one, break out of the loop with 'i' as its index and gotNL set
+ * TRUE.
*/
- job->outBuf[i] = '\0';
- if (i >= job->curPos) {
- char *cp;
-
- cp = JobOutput(job, job->outBuf, &job->outBuf[i]);
-
- /*
- * There's still more in that thar buffer. This time, though,
- * we know there's no newline at the end, so we add one of
- * our own free will.
- */
- if (*cp != '\0') {
- if (!opts.beSilent && job->node != lastNode) {
- MESSAGE(stdout, job->node);
- lastNode = job->node;
+ max = job->curPos + nr;
+ for (i = job->curPos + nr - 1;
+ i >= job->curPos && i != (size_t)-1; i--) {
+ if (job->outBuf[i] == '\n') {
+ gotNL = TRUE;
+ break;
+ } else if (job->outBuf[i] == '\0') {
+ /*
+ * Why?
+ */
+ job->outBuf[i] = ' ';
}
-#ifdef USE_META
- if (useMeta) {
- meta_job_output(job, cp, gotNL ? "\n" : "");
+ }
+
+ if (!gotNL) {
+ job->curPos += nr;
+ if (job->curPos == JOB_BUFSIZE) {
+ /*
+ * If we've run out of buffer space, we have no choice
+ * but to print the stuff. sigh.
+ */
+ fbuf = TRUE;
+ i = job->curPos;
}
+ }
+ if (gotNL || fbuf) {
+ /*
+ * Need to send the output to the screen. Null terminate it
+ * first, overwriting the newline character if there was one.
+ * So long as the line isn't one we should filter (according
+ * to the shell description), we print the line, preceded
+ * by a target banner if this target isn't the same as the
+ * one for which we last printed something.
+ * The rest of the data in the buffer are then shifted down
+ * to the start of the buffer and curPos is set accordingly.
+ */
+ job->outBuf[i] = '\0';
+ if (i >= job->curPos) {
+ char *cp;
+
+ cp = JobOutput(job->outBuf, &job->outBuf[i]);
+
+ /*
+ * There's still more in that thar buffer. This time,
+ * though, we know there's no newline at the end, so
+ * we add one of our own free will.
+ */
+ if (*cp != '\0') {
+ if (!opts.beSilent)
+ SwitchOutputTo(job->node);
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_output(job, cp,
+ gotNL ? "\n" : "");
+ }
#endif
- (void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : "");
- (void)fflush(stdout);
- }
+ (void)fprintf(stdout, "%s%s", cp,
+ gotNL ? "\n" : "");
+ (void)fflush(stdout);
+ }
+ }
+ /*
+ * max is the last offset still in the buffer. Move any
+ * remaining characters to the start of the buffer and
+ * update the end marker curPos.
+ */
+ if (i < max) {
+ (void)memmove(job->outBuf, &job->outBuf[i + 1],
+ max - (i + 1));
+ job->curPos = max - (i + 1);
+ } else {
+ assert(i == max);
+ job->curPos = 0;
+ }
}
- /*
- * max is the last offset still in the buffer. Move any remaining
- * characters to the start of the buffer and update the end marker
- * curPos.
- */
- if (i < max) {
- (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1));
- job->curPos = max - (i + 1);
- } else {
- assert(i == max);
- job->curPos = 0;
+ if (finish) {
+ /*
+ * If the finish flag is true, we must loop until we hit
+ * end-of-file on the pipe. This is guaranteed to happen
+ * eventually since the other end of the pipe is now closed
+ * (we closed it explicitly and the child has exited). When
+ * we do get an EOF, finish will be set FALSE and we'll fall
+ * through and out.
+ */
+ goto again;
}
- }
- if (finish) {
- /*
- * If the finish flag is true, we must loop until we hit
- * end-of-file on the pipe. This is guaranteed to happen
- * eventually since the other end of the pipe is now closed
- * (we closed it explicitly and the child has exited). When
- * we do get an EOF, finish will be set FALSE and we'll fall
- * through and out.
- */
- goto again;
- }
}
static void
JobRun(GNode *targ)
{
#if 0
- /*
- * Unfortunately it is too complicated to run .BEGIN, .END, and
- * .INTERRUPT job in the parallel job module. As of 2020-09-25,
- * unit-tests/deptgt-end-jobs.mk hangs in an endless loop.
- *
- * Running these jobs in compat mode also guarantees that these
- * jobs do not overlap with other unrelated jobs.
- */
- List *lst = Lst_New();
- Lst_Append(lst, targ);
- (void)Make_Run(lst);
- Lst_Destroy(lst, NULL);
- JobStart(targ, JOB_SPECIAL);
- while (jobTokensRunning) {
- Job_CatchOutput();
- }
+ /*
+ * Unfortunately it is too complicated to run .BEGIN, .END, and
+ * .INTERRUPT job in the parallel job module. As of 2020-09-25,
+ * unit-tests/deptgt-end-jobs.mk hangs in an endless loop.
+ *
+ * Running these jobs in compat mode also guarantees that these
+ * jobs do not overlap with other unrelated jobs.
+ */
+ List *lst = Lst_New();
+ Lst_Append(lst, targ);
+ (void)Make_Run(lst);
+ Lst_Destroy(lst, NULL);
+ JobStart(targ, JOB_SPECIAL);
+ while (jobTokensRunning != 0) {
+ Job_CatchOutput();
+ }
#else
- Compat_Make(targ, targ);
- if (targ->made == ERROR) {
- PrintOnError(targ, "\n\nStop.");
- exit(1);
- }
+ Compat_Make(targ, targ);
+ /* XXX: Replace with GNode_IsError(gn) */
+ if (targ->made == ERROR) {
+ PrintOnError(targ, "\n\nStop.");
+ exit(1);
+ }
#endif
}
-/* Handle the exit of a child. Called from Make_Make.
+/*
+ * Handle the exit of a child. Called from Make_Make.
*
* The job descriptor is removed from the list of children.
*
@@ -1877,20 +1986,18 @@ JobRun(GNode *targ)
void
Job_CatchChildren(void)
{
- int pid; /* pid of dead child */
- WAIT_T status; /* Exit/termination status */
+ int pid; /* pid of dead child */
+ WAIT_T status; /* Exit/termination status */
- /*
- * Don't even bother if we know there's no one around.
- */
- if (jobTokensRunning == 0)
- return;
+ /* Don't even bother if we know there's no one around. */
+ if (jobTokensRunning == 0)
+ return;
- while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) {
- DEBUG2(JOB, "Process %d exited/stopped status %x.\n", pid,
- WAIT_STATUS(status));
- JobReapChild(pid, status, TRUE);
- }
+ while ((pid = waitpid((pid_t)-1, &status, WNOHANG | WUNTRACED)) > 0) {
+ DEBUG2(JOB, "Process %d exited/stopped status %x.\n",
+ pid, WAIT_STATUS(status));
+ JobReapChild(pid, status, TRUE);
+ }
}
/*
@@ -1900,318 +2007,338 @@ Job_CatchChildren(void)
void
JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs)
{
- Job *job; /* job descriptor for dead child */
-
- /*
- * Don't even bother if we know there's no one around.
- */
- if (jobTokensRunning == 0)
- return;
-
- job = JobFindPid(pid, JOB_ST_RUNNING, isJobs);
- if (job == NULL) {
- if (isJobs) {
- if (!lurking_children)
- Error("Child (%d) status %x not in table?", pid, status);
+ Job *job; /* job descriptor for dead child */
+
+ /* Don't even bother if we know there's no one around. */
+ if (jobTokensRunning == 0)
+ return;
+
+ job = JobFindPid(pid, JOB_ST_RUNNING, isJobs);
+ if (job == NULL) {
+ if (isJobs) {
+ if (!lurking_children)
+ Error("Child (%d) status %x not in table?",
+ pid, status);
+ }
+ return; /* not ours */
}
- return; /* not ours */
- }
- if (WIFSTOPPED(status)) {
- DEBUG2(JOB, "Process %d (%s) stopped.\n", job->pid, job->node->name);
- if (!make_suspended) {
- switch (WSTOPSIG(status)) {
- case SIGTSTP:
- (void)printf("*** [%s] Suspended\n", job->node->name);
- break;
- case SIGSTOP:
- (void)printf("*** [%s] Stopped\n", job->node->name);
- break;
- default:
- (void)printf("*** [%s] Stopped -- signal %d\n",
- job->node->name, WSTOPSIG(status));
- }
- job->suspended = TRUE;
+ if (WIFSTOPPED(status)) {
+ DEBUG2(JOB, "Process %d (%s) stopped.\n",
+ job->pid, job->node->name);
+ if (!make_suspended) {
+ switch (WSTOPSIG(status)) {
+ case SIGTSTP:
+ (void)printf("*** [%s] Suspended\n",
+ job->node->name);
+ break;
+ case SIGSTOP:
+ (void)printf("*** [%s] Stopped\n",
+ job->node->name);
+ break;
+ default:
+ (void)printf("*** [%s] Stopped -- signal %d\n",
+ job->node->name, WSTOPSIG(status));
+ }
+ job->suspended = TRUE;
+ }
+ (void)fflush(stdout);
+ return;
}
- (void)fflush(stdout);
- return;
- }
- job->status = JOB_ST_FINISHED;
- job->exit_status = WAIT_STATUS(status);
+ job->status = JOB_ST_FINISHED;
+ job->exit_status = WAIT_STATUS(status);
- JobFinish(job, status);
+ JobFinish(job, status);
}
-/* Catch the output from our children, if we're using pipes do so. Otherwise
+/*
+ * Catch the output from our children, if we're using pipes do so. Otherwise
* just block time until we get a signal(most likely a SIGCHLD) since there's
* no point in just spinning when there's nothing to do and the reaping of a
- * child can wait for a while. */
+ * child can wait for a while.
+ */
void
Job_CatchOutput(void)
{
- int nready;
- Job *job;
- unsigned int i;
+ int nready;
+ Job *job;
+ unsigned int i;
- (void)fflush(stdout);
-
- /* The first fd in the list is the job token pipe */
- do {
- nready = poll(fds + 1 - wantToken, nfds - 1 + wantToken, POLL_MSEC);
- } while (nready < 0 && errno == EINTR);
-
- if (nready < 0)
- Punt("poll: %s", strerror(errno));
+ (void)fflush(stdout);
- if (nready > 0 && readyfd(&childExitJob)) {
- char token = 0;
- ssize_t count;
- count = read(childExitJob.inPipe, &token, 1);
- switch (count) {
- case 0:
- Punt("unexpected eof on token pipe");
- case -1:
- Punt("token pipe read: %s", strerror(errno));
- case 1:
- if (token == DO_JOB_RESUME[0])
- /* Complete relay requested from our SIGCONT handler */
- JobRestartJobs();
- break;
- default:
- abort();
- }
- nready--;
- }
+ /* The first fd in the list is the job token pipe */
+ do {
+ nready = poll(fds + 1 - wantToken, nJobs - 1 + wantToken,
+ POLL_MSEC);
+ } while (nready < 0 && errno == EINTR);
+
+ if (nready < 0)
+ Punt("poll: %s", strerror(errno));
+
+ if (nready > 0 && readyfd(&childExitJob)) {
+ char token = 0;
+ ssize_t count;
+ count = read(childExitJob.inPipe, &token, 1);
+ switch (count) {
+ case 0:
+ Punt("unexpected eof on token pipe");
+ /*NOTREACHED*/
+ case -1:
+ Punt("token pipe read: %s", strerror(errno));
+ /*NOTREACHED*/
+ case 1:
+ if (token == DO_JOB_RESUME[0])
+ /*
+ * Complete relay requested from our SIGCONT
+ * handler
+ */
+ JobRestartJobs();
+ break;
+ default:
+ abort();
+ }
+ nready--;
+ }
- Job_CatchChildren();
- if (nready == 0)
- return;
+ Job_CatchChildren();
+ if (nready == 0)
+ return;
- for (i = npseudojobs * nfds_per_job(); i < nfds; i++) {
- if (!fds[i].revents)
- continue;
- job = jobfds[i];
- if (job->status == JOB_ST_RUNNING)
- JobDoOutput(job, FALSE);
+ for (i = npseudojobs * nfds_per_job(); i < nJobs; i++) {
+ if (fds[i].revents == 0)
+ continue;
+ job = allJobs[i];
+ if (job->status == JOB_ST_RUNNING)
+ JobDoOutput(job, FALSE);
#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV)
- /*
- * With meta mode, we may have activity on the job's filemon
- * descriptor too, which at the moment is any pollfd other than
- * job->inPollfd.
- */
- if (useMeta && job->inPollfd != &fds[i]) {
- if (meta_job_event(job) <= 0) {
- fds[i].events = 0; /* never mind */
- }
- }
+ /*
+ * With meta mode, we may have activity on the job's filemon
+ * descriptor too, which at the moment is any pollfd other
+ * than job->inPollfd.
+ */
+ if (useMeta && job->inPollfd != &fds[i]) {
+ if (meta_job_event(job) <= 0) {
+ fds[i].events = 0; /* never mind */
+ }
+ }
#endif
- if (--nready == 0)
- return;
- }
+ if (--nready == 0)
+ return;
+ }
}
-/* Start the creation of a target. Basically a front-end for JobStart used by
- * the Make module. */
+/*
+ * Start the creation of a target. Basically a front-end for JobStart used by
+ * the Make module.
+ */
void
Job_Make(GNode *gn)
{
- (void)JobStart(gn, JOB_NONE);
+ (void)JobStart(gn, FALSE);
}
-void
-Shell_Init(void)
+static void
+InitShellNameAndPath(void)
{
- if (shellPath == NULL) {
- /*
- * We are using the default shell, which may be an absolute
- * path if DEFSHELL_CUSTOM is defined.
- */
- shellName = commandShell->name;
+ shellName = shell->name;
+
#ifdef DEFSHELL_CUSTOM
- if (*shellName == '/') {
- shellPath = shellName;
- shellName = strrchr(shellPath, '/');
- shellName++;
- } else
+ if (shellName[0] == '/') {
+ shellPath = shellName;
+ shellName = str_basename(shellPath);
+ return;
+ }
#endif
+
shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName);
- }
- Var_SetWithFlags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY);
- if (commandShell->exit == NULL) {
- commandShell->exit = "";
- }
- if (commandShell->echo == NULL) {
- commandShell->echo = "";
- }
- if (commandShell->hasErrCtl && commandShell->exit[0] != '\0') {
- if (shellErrFlag &&
- strcmp(commandShell->exit, &shellErrFlag[1]) != 0) {
- free(shellErrFlag);
- shellErrFlag = NULL;
- }
- if (!shellErrFlag) {
- size_t n = strlen(commandShell->exit) + 2;
-
- shellErrFlag = bmake_malloc(n);
- if (shellErrFlag) {
- snprintf(shellErrFlag, n, "-%s", commandShell->exit);
- }
- }
- } else if (shellErrFlag) {
- free(shellErrFlag);
- shellErrFlag = NULL;
- }
}
-/* Return the string literal that is used in the current command shell
- * to produce a newline character. */
+void
+Shell_Init(void)
+{
+ if (shellPath == NULL)
+ InitShellNameAndPath();
+
+ Var_SetWithFlags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY);
+ if (shell->errFlag == NULL)
+ shell->errFlag = "";
+ if (shell->echoFlag == NULL)
+ shell->echoFlag = "";
+ if (shell->hasErrCtl && shell->errFlag[0] != '\0') {
+ if (shellErrFlag != NULL &&
+ strcmp(shell->errFlag, &shellErrFlag[1]) != 0) {
+ free(shellErrFlag);
+ shellErrFlag = NULL;
+ }
+ if (shellErrFlag == NULL) {
+ size_t n = strlen(shell->errFlag) + 2;
+
+ shellErrFlag = bmake_malloc(n);
+ if (shellErrFlag != NULL)
+ snprintf(shellErrFlag, n, "-%s",
+ shell->errFlag);
+ }
+ } else if (shellErrFlag != NULL) {
+ free(shellErrFlag);
+ shellErrFlag = NULL;
+ }
+}
+
+/*
+ * Return the string literal that is used in the current command shell
+ * to produce a newline character.
+ */
const char *
Shell_GetNewline(void)
{
- return commandShell->newline;
+ return shell->newline;
}
void
Job_SetPrefix(void)
{
- if (targPrefix) {
- free(targPrefix);
- } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) {
- Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL);
- }
+ if (targPrefix != NULL) {
+ free(targPrefix);
+ } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) {
+ Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL);
+ }
- (void)Var_Subst("${" MAKE_JOB_PREFIX "}",
- VAR_GLOBAL, VARE_WANTRES, &targPrefix);
- /* TODO: handle errors */
+ (void)Var_Subst("${" MAKE_JOB_PREFIX "}",
+ VAR_GLOBAL, VARE_WANTRES, &targPrefix);
+ /* TODO: handle errors */
+}
+
+static void
+AddSig(int sig, SignalProc handler)
+{
+ if (bmake_signal(sig, SIG_IGN) != SIG_IGN) {
+ sigaddset(&caught_signals, sig);
+ (void)bmake_signal(sig, handler);
+ }
}
/* Initialize the process module. */
void
Job_Init(void)
{
- Job_SetPrefix();
- /* Allocate space for all the job info */
- job_table = bmake_malloc((size_t)opts.maxJobs * sizeof *job_table);
- memset(job_table, 0, (size_t)opts.maxJobs * sizeof *job_table);
- job_table_end = job_table + opts.maxJobs;
- wantToken = 0;
+ Job_SetPrefix();
+ /* Allocate space for all the job info */
+ job_table = bmake_malloc((size_t)opts.maxJobs * sizeof *job_table);
+ memset(job_table, 0, (size_t)opts.maxJobs * sizeof *job_table);
+ job_table_end = job_table + opts.maxJobs;
+ wantToken = 0;
- aborting = ABORT_NONE;
- errors = 0;
+ aborting = ABORT_NONE;
+ job_errors = 0;
- lastNode = NULL;
-
- /*
- * There is a non-zero chance that we already have children.
- * eg after 'make -f- <<EOF'
- * Since their termination causes a 'Child (pid) not in table' message,
- * Collect the status of any that are already dead, and suppress the
- * error message if there are any undead ones.
- */
- for (;;) {
- int rval, status;
- rval = waitpid((pid_t) -1, &status, WNOHANG);
- if (rval > 0)
- continue;
- if (rval == 0)
- lurking_children = TRUE;
- break;
- }
-
- Shell_Init();
+ /*
+ * There is a non-zero chance that we already have children.
+ * eg after 'make -f- <<EOF'
+ * Since their termination causes a 'Child (pid) not in table'
+ * message, Collect the status of any that are already dead, and
+ * suppress the error message if there are any undead ones.
+ */
+ for (;;) {
+ int rval;
+ WAIT_T status;
+
+ rval = waitpid((pid_t)-1, &status, WNOHANG);
+ if (rval > 0)
+ continue;
+ if (rval == 0)
+ lurking_children = TRUE;
+ break;
+ }
- JobCreatePipe(&childExitJob, 3);
+ Shell_Init();
- /* Preallocate enough for the maximum number of jobs. */
- fds = bmake_malloc(sizeof *fds *
- (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job());
- jobfds = bmake_malloc(sizeof *jobfds *
- (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job());
+ JobCreatePipe(&childExitJob, 3);
- /* These are permanent entries and take slots 0 and 1 */
- watchfd(&tokenWaitJob);
- watchfd(&childExitJob);
+ /* Preallocate enough for the maximum number of jobs. */
+ fds = bmake_malloc(sizeof *fds *
+ (npseudojobs + (size_t)opts.maxJobs) *
+ nfds_per_job());
+ allJobs = bmake_malloc(sizeof *allJobs *
+ (npseudojobs + (size_t)opts.maxJobs) *
+ nfds_per_job());
- sigemptyset(&caught_signals);
- /*
- * Install a SIGCHLD handler.
- */
- (void)bmake_signal(SIGCHLD, JobChildSig);
- sigaddset(&caught_signals, SIGCHLD);
+ /* These are permanent entries and take slots 0 and 1 */
+ watchfd(&tokenWaitJob);
+ watchfd(&childExitJob);
-#define ADDSIG(s,h) \
- if (bmake_signal(s, SIG_IGN) != SIG_IGN) { \
- sigaddset(&caught_signals, s); \
- (void)bmake_signal(s, h); \
- }
-
- /*
- * Catch the four signals that POSIX specifies if they aren't ignored.
- * JobPassSig will take care of calling JobInterrupt if appropriate.
- */
- ADDSIG(SIGINT, JobPassSig_int)
- ADDSIG(SIGHUP, JobPassSig_term)
- ADDSIG(SIGTERM, JobPassSig_term)
- ADDSIG(SIGQUIT, JobPassSig_term)
+ sigemptyset(&caught_signals);
+ /*
+ * Install a SIGCHLD handler.
+ */
+ (void)bmake_signal(SIGCHLD, JobChildSig);
+ sigaddset(&caught_signals, SIGCHLD);
- /*
- * There are additional signals that need to be caught and passed if
- * either the export system wants to be told directly of signals or if
- * we're giving each job its own process group (since then it won't get
- * signals from the terminal driver as we own the terminal)
- */
- ADDSIG(SIGTSTP, JobPassSig_suspend)
- ADDSIG(SIGTTOU, JobPassSig_suspend)
- ADDSIG(SIGTTIN, JobPassSig_suspend)
- ADDSIG(SIGWINCH, JobCondPassSig)
- ADDSIG(SIGCONT, JobContinueSig)
-#undef ADDSIG
+ /*
+ * Catch the four signals that POSIX specifies if they aren't ignored.
+ * JobPassSig will take care of calling JobInterrupt if appropriate.
+ */
+ AddSig(SIGINT, JobPassSig_int);
+ AddSig(SIGHUP, JobPassSig_term);
+ AddSig(SIGTERM, JobPassSig_term);
+ AddSig(SIGQUIT, JobPassSig_term);
- (void)Job_RunTarget(".BEGIN", NULL);
- /* Create the .END node now, even though no code in the unit tests
- * depends on it. See also Targ_GetEndNode in Compat_Run. */
- (void)Targ_GetEndNode();
+ /*
+ * There are additional signals that need to be caught and passed if
+ * either the export system wants to be told directly of signals or if
+ * we're giving each job its own process group (since then it won't get
+ * signals from the terminal driver as we own the terminal)
+ */
+ AddSig(SIGTSTP, JobPassSig_suspend);
+ AddSig(SIGTTOU, JobPassSig_suspend);
+ AddSig(SIGTTIN, JobPassSig_suspend);
+ AddSig(SIGWINCH, JobCondPassSig);
+ AddSig(SIGCONT, JobContinueSig);
+
+ (void)Job_RunTarget(".BEGIN", NULL);
+ /* Create the .END node now, even though no code in the unit tests
+ * depends on it. See also Targ_GetEndNode in Compat_Run. */
+ (void)Targ_GetEndNode();
}
-static void JobSigReset(void)
+static void
+DelSig(int sig)
{
-#define DELSIG(s) \
- if (sigismember(&caught_signals, s)) { \
- (void)bmake_signal(s, SIG_DFL); \
- }
+ if (sigismember(&caught_signals, sig) != 0)
+ (void)bmake_signal(sig, SIG_DFL);
+}
- DELSIG(SIGINT)
- DELSIG(SIGHUP)
- DELSIG(SIGQUIT)
- DELSIG(SIGTERM)
- DELSIG(SIGTSTP)
- DELSIG(SIGTTOU)
- DELSIG(SIGTTIN)
- DELSIG(SIGWINCH)
- DELSIG(SIGCONT)
-#undef DELSIG
- (void)bmake_signal(SIGCHLD, SIG_DFL);
+static void
+JobSigReset(void)
+{
+ DelSig(SIGINT);
+ DelSig(SIGHUP);
+ DelSig(SIGQUIT);
+ DelSig(SIGTERM);
+ DelSig(SIGTSTP);
+ DelSig(SIGTTOU);
+ DelSig(SIGTTIN);
+ DelSig(SIGWINCH);
+ DelSig(SIGCONT);
+ (void)bmake_signal(SIGCHLD, SIG_DFL);
}
/* Find a shell in 'shells' given its name, or return NULL. */
static Shell *
FindShellByName(const char *name)
{
- Shell *sh = shells;
- const Shell *shellsEnd = sh + sizeof shells / sizeof shells[0];
+ Shell *sh = shells;
+ const Shell *shellsEnd = sh + sizeof shells / sizeof shells[0];
- for (sh = shells; sh < shellsEnd; sh++) {
- if (strcmp(name, sh->name) == 0)
- return sh;
- }
- return NULL;
+ for (sh = shells; sh < shellsEnd; sh++) {
+ if (strcmp(name, sh->name) == 0)
+ return sh;
+ }
+ return NULL;
}
-/*-
- *-----------------------------------------------------------------------
- * Job_ParseShell --
- * Parse a shell specification and set up commandShell, shellPath
- * and shellName appropriately.
+/*
+ * Parse a shell specification and set up 'shell', shellPath and
+ * shellName appropriately.
*
* Input:
* line The shell spec
@@ -2220,9 +2347,9 @@ FindShellByName(const char *name)
* FALSE if the specification was incorrect.
*
* Side Effects:
- * commandShell points to a Shell structure (either predefined or
+ * 'shell' points to a Shell structure (either predefined or
* created from the shell spec), shellPath is the full path of the
- * shell described by commandShell, while shellName is just the
+ * shell described by 'shell', while shellName is just the
* final component of shellPath.
*
* Notes:
@@ -2251,166 +2378,174 @@ FindShellByName(const char *name)
* is TRUE or template of command to execute a
* command so as to ignore any errors it returns if
* hasErrCtl is FALSE.
- *
- *-----------------------------------------------------------------------
*/
Boolean
Job_ParseShell(char *line)
{
- Words wordsList;
- char **words;
- char **argv;
- size_t argc;
- char *path;
- Shell newShell;
- Boolean fullSpec = FALSE;
- Shell *sh;
-
- pp_skip_whitespace(&line);
+ Words wordsList;
+ char **words;
+ char **argv;
+ size_t argc;
+ char *path;
+ Shell newShell;
+ Boolean fullSpec = FALSE;
+ Shell *sh;
- free(shellArgv);
+ /* XXX: don't use line as an iterator variable */
+ pp_skip_whitespace(&line);
- memset(&newShell, 0, sizeof newShell);
+ free(shellArgv);
- /*
- * Parse the specification by keyword
- */
- wordsList = Str_Words(line, TRUE);
- words = wordsList.words;
- argc = wordsList.len;
- path = wordsList.freeIt;
- if (words == NULL) {
- Error("Unterminated quoted string [%s]", line);
- return FALSE;
- }
- shellArgv = path;
-
- for (path = NULL, argv = words; argc != 0; argc--, argv++) {
- char *arg = *argv;
- if (strncmp(arg, "path=", 5) == 0) {
- path = arg + 5;
- } else if (strncmp(arg, "name=", 5) == 0) {
- newShell.name = arg + 5;
- } else {
- if (strncmp(arg, "quiet=", 6) == 0) {
- newShell.echoOff = arg + 6;
- } else if (strncmp(arg, "echo=", 5) == 0) {
- newShell.echoOn = arg + 5;
- } else if (strncmp(arg, "filter=", 7) == 0) {
- newShell.noPrint = arg + 7;
- newShell.noPrintLen = strlen(newShell.noPrint);
- } else if (strncmp(arg, "echoFlag=", 9) == 0) {
- newShell.echo = arg + 9;
- } else if (strncmp(arg, "errFlag=", 8) == 0) {
- newShell.exit = arg + 8;
- } else if (strncmp(arg, "hasErrCtl=", 10) == 0) {
- char c = arg[10];
- newShell.hasErrCtl = c == 'Y' || c == 'y' ||
- c == 'T' || c == 't';
- } else if (strncmp(arg, "newline=", 8) == 0) {
- newShell.newline = arg + 8;
- } else if (strncmp(arg, "check=", 6) == 0) {
- newShell.errOnOrEcho = arg + 6;
- } else if (strncmp(arg, "ignore=", 7) == 0) {
- newShell.errOffOrExecIgnore = arg + 7;
- } else if (strncmp(arg, "errout=", 7) == 0) {
- newShell.errExit = arg + 7;
- } else if (strncmp(arg, "comment=", 8) == 0) {
- newShell.commentChar = arg[8];
- } else {
- Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", arg);
- free(words);
- return FALSE;
- }
- fullSpec = TRUE;
- }
- }
+ memset(&newShell, 0, sizeof newShell);
- if (path == NULL) {
/*
- * If no path was given, the user wants one of the pre-defined shells,
- * yes? So we find the one s/he wants with the help of FindShellByName
- * and set things up the right way. shellPath will be set up by
- * Shell_Init.
+ * Parse the specification by keyword
*/
- if (newShell.name == NULL) {
- Parse_Error(PARSE_FATAL, "Neither path nor name specified");
- free(words);
- return FALSE;
- } else {
- if ((sh = FindShellByName(newShell.name)) == NULL) {
- Parse_Error(PARSE_WARNING, "%s: No matching shell",
- newShell.name);
- free(words);
- return FALSE;
- }
- commandShell = sh;
- shellName = newShell.name;
- if (shellPath) {
- /* Shell_Init has already been called! Do it again. */
- free(UNCONST(shellPath));
- shellPath = NULL;
- Shell_Init();
- }
+ wordsList = Str_Words(line, TRUE);
+ words = wordsList.words;
+ argc = wordsList.len;
+ path = wordsList.freeIt;
+ if (words == NULL) {
+ Error("Unterminated quoted string [%s]", line);
+ return FALSE;
}
- } else {
- /*
- * The user provided a path. If s/he gave nothing else (fullSpec is
- * FALSE), try and find a matching shell in the ones we know of.
- * Else we just take the specification at its word and copy it
- * to a new location. In either case, we need to record the
- * path the user gave for the shell.
- */
- shellPath = path;
- path = strrchr(path, '/');
- if (path == NULL) {
- path = UNCONST(shellPath);
- } else {
- path++;
+ shellArgv = path;
+
+ for (path = NULL, argv = words; argc != 0; argc--, argv++) {
+ char *arg = *argv;
+ if (strncmp(arg, "path=", 5) == 0) {
+ path = arg + 5;
+ } else if (strncmp(arg, "name=", 5) == 0) {
+ newShell.name = arg + 5;
+ } else {
+ if (strncmp(arg, "quiet=", 6) == 0) {
+ newShell.echoOff = arg + 6;
+ } else if (strncmp(arg, "echo=", 5) == 0) {
+ newShell.echoOn = arg + 5;
+ } else if (strncmp(arg, "filter=", 7) == 0) {
+ newShell.noPrint = arg + 7;
+ newShell.noPrintLen = strlen(newShell.noPrint);
+ } else if (strncmp(arg, "echoFlag=", 9) == 0) {
+ newShell.echoFlag = arg + 9;
+ } else if (strncmp(arg, "errFlag=", 8) == 0) {
+ newShell.errFlag = arg + 8;
+ } else if (strncmp(arg, "hasErrCtl=", 10) == 0) {
+ char c = arg[10];
+ newShell.hasErrCtl = c == 'Y' || c == 'y' ||
+ c == 'T' || c == 't';
+ } else if (strncmp(arg, "newline=", 8) == 0) {
+ newShell.newline = arg + 8;
+ } else if (strncmp(arg, "check=", 6) == 0) {
+ /* Before 2020-12-10, these two variables
+ * had been a single variable. */
+ newShell.errOn = arg + 6;
+ newShell.echoTmpl = arg + 6;
+ } else if (strncmp(arg, "ignore=", 7) == 0) {
+ /* Before 2020-12-10, these two variables
+ * had been a single variable. */
+ newShell.errOff = arg + 7;
+ newShell.runIgnTmpl = arg + 7;
+ } else if (strncmp(arg, "errout=", 7) == 0) {
+ newShell.runChkTmpl = arg + 7;
+ } else if (strncmp(arg, "comment=", 8) == 0) {
+ newShell.commentChar = arg[8];
+ } else {
+ Parse_Error(PARSE_FATAL,
+ "Unknown keyword \"%s\"", arg);
+ free(words);
+ return FALSE;
+ }
+ fullSpec = TRUE;
+ }
}
- if (newShell.name != NULL) {
- shellName = newShell.name;
- } else {
- shellName = path;
- }
- if (!fullSpec) {
- if ((sh = FindShellByName(shellName)) == NULL) {
- Parse_Error(PARSE_WARNING, "%s: No matching shell",
- shellName);
- free(words);
- return FALSE;
- }
- commandShell = sh;
+
+ if (path == NULL) {
+ /*
+ * If no path was given, the user wants one of the
+ * pre-defined shells, yes? So we find the one s/he wants
+ * with the help of FindShellByName and set things up the
+ * right way. shellPath will be set up by Shell_Init.
+ */
+ if (newShell.name == NULL) {
+ Parse_Error(PARSE_FATAL,
+ "Neither path nor name specified");
+ free(words);
+ return FALSE;
+ } else {
+ if ((sh = FindShellByName(newShell.name)) == NULL) {
+ Parse_Error(PARSE_WARNING,
+ "%s: No matching shell", newShell.name);
+ free(words);
+ return FALSE;
+ }
+ shell = sh;
+ shellName = newShell.name;
+ if (shellPath != NULL) {
+ /*
+ * Shell_Init has already been called!
+ * Do it again.
+ */
+ free(UNCONST(shellPath));
+ shellPath = NULL;
+ Shell_Init();
+ }
+ }
} else {
- commandShell = bmake_malloc(sizeof *commandShell);
- *commandShell = newShell;
+ /*
+ * The user provided a path. If s/he gave nothing else
+ * (fullSpec is FALSE), try and find a matching shell in the
+ * ones we know of. Else we just take the specification at
+ * its word and copy it to a new location. In either case,
+ * we need to record the path the user gave for the shell.
+ */
+ shellPath = path;
+ path = strrchr(path, '/');
+ if (path == NULL) {
+ path = UNCONST(shellPath);
+ } else {
+ path++;
+ }
+ if (newShell.name != NULL) {
+ shellName = newShell.name;
+ } else {
+ shellName = path;
+ }
+ if (!fullSpec) {
+ if ((sh = FindShellByName(shellName)) == NULL) {
+ Parse_Error(PARSE_WARNING,
+ "%s: No matching shell", shellName);
+ free(words);
+ return FALSE;
+ }
+ shell = sh;
+ } else {
+ shell = bmake_malloc(sizeof *shell);
+ *shell = newShell;
+ }
+ /* this will take care of shellErrFlag */
+ Shell_Init();
}
- /* this will take care of shellErrFlag */
- Shell_Init();
- }
- if (commandShell->echoOn && commandShell->echoOff) {
- commandShell->hasEchoCtl = TRUE;
- }
+ if (shell->echoOn != NULL && shell->echoOff != NULL)
+ shell->hasEchoCtl = TRUE;
- if (!commandShell->hasErrCtl) {
- if (commandShell->errOnOrEcho == NULL) {
- commandShell->errOnOrEcho = "";
- }
- if (commandShell->errOffOrExecIgnore == NULL) {
- commandShell->errOffOrExecIgnore = "%s\n";
+ if (!shell->hasErrCtl) {
+ if (shell->echoTmpl == NULL)
+ shell->echoTmpl = "";
+ if (shell->runIgnTmpl == NULL)
+ shell->runIgnTmpl = "%s\n";
}
- }
- /*
- * Do not free up the words themselves, since they might be in use by the
- * shell specification.
- */
- free(words);
- return TRUE;
+ /*
+ * Do not free up the words themselves, since they might be in use
+ * by the shell specification.
+ */
+ free(words);
+ return TRUE;
}
-/* Handle the receipt of an interrupt.
+/*
+ * Handle the receipt of an interrupt.
*
* All children are killed. Another job will be started if the .INTERRUPT
* target is defined.
@@ -2421,59 +2556,63 @@ Job_ParseShell(char *line)
* signo signal received
*/
static void
-JobInterrupt(int runINTERRUPT, int signo)
+JobInterrupt(Boolean runINTERRUPT, int signo)
{
- Job *job; /* job descriptor in that element */
- GNode *interrupt; /* the node describing the .INTERRUPT target */
- sigset_t mask;
- GNode *gn;
+ Job *job; /* job descriptor in that element */
+ GNode *interrupt; /* the node describing the .INTERRUPT target */
+ sigset_t mask;
+ GNode *gn;
- aborting = ABORT_INTERRUPT;
+ aborting = ABORT_INTERRUPT;
- JobSigLock(&mask);
+ JobSigLock(&mask);
- for (job = job_table; job < job_table_end; job++) {
- if (job->status != JOB_ST_RUNNING)
- continue;
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->status != JOB_ST_RUNNING)
+ continue;
- gn = job->node;
+ gn = job->node;
- JobDeleteTarget(gn);
- if (job->pid) {
- DEBUG2(JOB, "JobInterrupt passing signal %d to child %d.\n",
- signo, job->pid);
- KILLPG(job->pid, signo);
+ JobDeleteTarget(gn);
+ if (job->pid != 0) {
+ DEBUG2(JOB,
+ "JobInterrupt passing signal %d to child %d.\n",
+ signo, job->pid);
+ KILLPG(job->pid, signo);
+ }
}
- }
- JobSigUnlock(&mask);
+ JobSigUnlock(&mask);
- if (runINTERRUPT && !opts.touchFlag) {
- interrupt = Targ_FindNode(".INTERRUPT");
- if (interrupt != NULL) {
- opts.ignoreErrors = FALSE;
- JobRun(interrupt);
+ if (runINTERRUPT && !opts.touchFlag) {
+ interrupt = Targ_FindNode(".INTERRUPT");
+ if (interrupt != NULL) {
+ opts.ignoreErrors = FALSE;
+ JobRun(interrupt);
+ }
}
- }
- Trace_Log(MAKEINTR, NULL);
- exit(signo);
+ Trace_Log(MAKEINTR, NULL);
+ exit(signo); /* XXX: why signo? */
}
-/* Do the final processing, i.e. run the commands attached to the .END target.
+/*
+ * Do the final processing, i.e. run the commands attached to the .END target.
*
- * Return the number of errors reported. */
+ * Return the number of errors reported.
+ */
int
Job_Finish(void)
{
- GNode *endNode = Targ_GetEndNode();
- if (!Lst_IsEmpty(endNode->commands) || !Lst_IsEmpty(endNode->children)) {
- if (errors) {
- Error("Errors reported so .END ignored");
- } else {
- JobRun(endNode);
+ GNode *endNode = Targ_GetEndNode();
+ if (!Lst_IsEmpty(&endNode->commands) ||
+ !Lst_IsEmpty(&endNode->children)) {
+ if (job_errors != 0) {
+ Error("Errors reported so .END ignored");
+ } else {
+ JobRun(endNode);
+ }
}
- }
- return errors;
+ return job_errors;
}
/* Clean up any memory used by the jobs module. */
@@ -2481,344 +2620,369 @@ void
Job_End(void)
{
#ifdef CLEANUP
- free(shellArgv);
+ free(shellArgv);
#endif
}
-/* Waits for all running jobs to finish and returns.
- * Sets 'aborting' to ABORT_WAIT to prevent other jobs from starting. */
+/*
+ * Waits for all running jobs to finish and returns.
+ * Sets 'aborting' to ABORT_WAIT to prevent other jobs from starting.
+ */
void
Job_Wait(void)
{
- aborting = ABORT_WAIT;
- while (jobTokensRunning != 0) {
- Job_CatchOutput();
- }
- aborting = ABORT_NONE;
+ aborting = ABORT_WAIT;
+ while (jobTokensRunning != 0) {
+ Job_CatchOutput();
+ }
+ aborting = ABORT_NONE;
}
-/* Abort all currently running jobs without handling output or anything.
+/*
+ * Abort all currently running jobs without handling output or anything.
* This function is to be called only in the event of a major error.
* Most definitely NOT to be called from JobInterrupt.
*
- * All children are killed, not just the firstborn. */
+ * All children are killed, not just the firstborn.
+ */
void
Job_AbortAll(void)
{
- Job *job; /* the job descriptor in that element */
- WAIT_T foo;
-
- aborting = ABORT_ERROR;
-
- if (jobTokensRunning) {
- for (job = job_table; job < job_table_end; job++) {
- if (job->status != JOB_ST_RUNNING)
- continue;
- /*
- * kill the child process with increasingly drastic signals to make
- * darn sure it's dead.
- */
- KILLPG(job->pid, SIGINT);
- KILLPG(job->pid, SIGKILL);
+ Job *job; /* the job descriptor in that element */
+ WAIT_T foo;
+
+ aborting = ABORT_ERROR;
+
+ if (jobTokensRunning != 0) {
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->status != JOB_ST_RUNNING)
+ continue;
+ /*
+ * kill the child process with increasingly drastic
+ * signals to make darn sure it's dead.
+ */
+ KILLPG(job->pid, SIGINT);
+ KILLPG(job->pid, SIGKILL);
+ }
}
- }
- /*
- * Catch as many children as want to report in at first, then give up
- */
- while (waitpid((pid_t) -1, &foo, WNOHANG) > 0)
- continue;
+ /*
+ * Catch as many children as want to report in at first, then give up
+ */
+ while (waitpid((pid_t)-1, &foo, WNOHANG) > 0)
+ continue;
}
-/* Tries to restart stopped jobs if there are slots available.
- * Called in process context in response to a SIGCONT. */
+/*
+ * Tries to restart stopped jobs if there are slots available.
+ * Called in process context in response to a SIGCONT.
+ */
static void
JobRestartJobs(void)
{
- Job *job;
-
- for (job = job_table; job < job_table_end; job++) {
- if (job->status == JOB_ST_RUNNING &&
- (make_suspended || job->suspended)) {
- DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid);
- if (job->suspended) {
- (void)printf("*** [%s] Continued\n", job->node->name);
- (void)fflush(stdout);
- }
- job->suspended = FALSE;
- if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) {
- debug_printf("Failed to send SIGCONT to %d\n", job->pid);
- }
- }
- if (job->status == JOB_ST_FINISHED)
- /* Job exit deferred after calling waitpid() in a signal handler */
- JobFinish(job, job->exit_status);
- }
- make_suspended = FALSE;
+ Job *job;
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->status == JOB_ST_RUNNING &&
+ (make_suspended || job->suspended)) {
+ DEBUG1(JOB, "Restarting stopped job pid %d.\n",
+ job->pid);
+ if (job->suspended) {
+ (void)printf("*** [%s] Continued\n",
+ job->node->name);
+ (void)fflush(stdout);
+ }
+ job->suspended = FALSE;
+ if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) {
+ debug_printf("Failed to send SIGCONT to %d\n",
+ job->pid);
+ }
+ }
+ if (job->status == JOB_ST_FINISHED) {
+ /*
+ * Job exit deferred after calling waitpid() in a
+ * signal handler
+ */
+ JobFinish(job, job->exit_status);
+ }
+ }
+ make_suspended = FALSE;
}
static void
watchfd(Job *job)
{
- if (job->inPollfd != NULL)
- Punt("Watching watched job");
-
- fds[nfds].fd = job->inPipe;
- fds[nfds].events = POLLIN;
- jobfds[nfds] = job;
- job->inPollfd = &fds[nfds];
- nfds++;
+ if (job->inPollfd != NULL)
+ Punt("Watching watched job");
+
+ fds[nJobs].fd = job->inPipe;
+ fds[nJobs].events = POLLIN;
+ allJobs[nJobs] = job;
+ job->inPollfd = &fds[nJobs];
+ nJobs++;
#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV)
- if (useMeta) {
- fds[nfds].fd = meta_job_fd(job);
- fds[nfds].events = fds[nfds].fd == -1 ? 0 : POLLIN;
- jobfds[nfds] = job;
- nfds++;
- }
+ if (useMeta) {
+ fds[nJobs].fd = meta_job_fd(job);
+ fds[nJobs].events = fds[nJobs].fd == -1 ? 0 : POLLIN;
+ allJobs[nJobs] = job;
+ nJobs++;
+ }
#endif
}
static void
clearfd(Job *job)
{
- size_t i;
- if (job->inPollfd == NULL)
- Punt("Unwatching unwatched job");
- i = (size_t)(job->inPollfd - fds);
- nfds--;
+ size_t i;
+ if (job->inPollfd == NULL)
+ Punt("Unwatching unwatched job");
+ i = (size_t)(job->inPollfd - fds);
+ nJobs--;
#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV)
- if (useMeta) {
+ if (useMeta) {
+ /*
+ * Sanity check: there should be two fds per job, so the job's
+ * pollfd number should be even.
+ */
+ assert(nfds_per_job() == 2);
+ if (i % 2 != 0)
+ Punt("odd-numbered fd with meta");
+ nJobs--;
+ }
+#endif
/*
- * Sanity check: there should be two fds per job, so the job's
- * pollfd number should be even.
+ * Move last job in table into hole made by dead job.
*/
- assert(nfds_per_job() == 2);
- if (i % 2)
- Punt("odd-numbered fd with meta");
- nfds--;
- }
-#endif
- /*
- * Move last job in table into hole made by dead job.
- */
- if (nfds != i) {
- fds[i] = fds[nfds];
- jobfds[i] = jobfds[nfds];
- jobfds[i]->inPollfd = &fds[i];
+ if (nJobs != i) {
+ fds[i] = fds[nJobs];
+ allJobs[i] = allJobs[nJobs];
+ allJobs[i]->inPollfd = &fds[i];
#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV)
- if (useMeta) {
- fds[i + 1] = fds[nfds + 1];
- jobfds[i + 1] = jobfds[nfds + 1];
- }
+ if (useMeta) {
+ fds[i + 1] = fds[nJobs + 1];
+ allJobs[i + 1] = allJobs[nJobs + 1];
+ }
#endif
- }
- job->inPollfd = NULL;
+ }
+ job->inPollfd = NULL;
}
-static int
+static Boolean
readyfd(Job *job)
{
- if (job->inPollfd == NULL)
- Punt("Polling unwatched job");
- return (job->inPollfd->revents & POLLIN) != 0;
+ if (job->inPollfd == NULL)
+ Punt("Polling unwatched job");
+ return (job->inPollfd->revents & POLLIN) != 0;
}
-/* Put a token (back) into the job pipe.
- * This allows a make process to start a build job. */
+/*
+ * Put a token (back) into the job pipe.
+ * This allows a make process to start a build job.
+ */
static void
JobTokenAdd(void)
{
- char tok = JOB_TOKENS[aborting], tok1;
+ char tok = JOB_TOKENS[aborting], tok1;
- /* If we are depositing an error token flush everything else */
- while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1)
- continue;
+ /* If we are depositing an error token flush everything else */
+ while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1)
+ continue;
- DEBUG3(JOB, "(%d) aborting %d, deposit token %c\n",
- getpid(), aborting, tok);
- while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
- continue;
+ DEBUG3(JOB, "(%d) aborting %d, deposit token %c\n",
+ getpid(), aborting, tok);
+ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
+ continue;
}
/* Prep the job token pipe in the root make process. */
void
Job_ServerStart(int max_tokens, int jp_0, int jp_1)
{
- int i;
- char jobarg[64];
-
- if (jp_0 >= 0 && jp_1 >= 0) {
- /* Pipe passed in from parent */
- tokenWaitJob.inPipe = jp_0;
- tokenWaitJob.outPipe = jp_1;
- (void)fcntl(jp_0, F_SETFD, FD_CLOEXEC);
- (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC);
- return;
- }
+ int i;
+ char jobarg[64];
+
+ if (jp_0 >= 0 && jp_1 >= 0) {
+ /* Pipe passed in from parent */
+ tokenWaitJob.inPipe = jp_0;
+ tokenWaitJob.outPipe = jp_1;
+ (void)fcntl(jp_0, F_SETFD, FD_CLOEXEC);
+ (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC);
+ return;
+ }
- JobCreatePipe(&tokenWaitJob, 15);
+ JobCreatePipe(&tokenWaitJob, 15);
- snprintf(jobarg, sizeof jobarg, "%d,%d",
+ snprintf(jobarg, sizeof jobarg, "%d,%d",
tokenWaitJob.inPipe, tokenWaitJob.outPipe);
- Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
- Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL);
- /*
- * Preload the job pipe with one token per job, save the one
- * "extra" token for the primary job.
- *
- * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is
- * larger than the write buffer size of the pipe, we will
- * deadlock here.
- */
- for (i = 1; i < max_tokens; i++)
- JobTokenAdd();
+ /*
+ * Preload the job pipe with one token per job, save the one
+ * "extra" token for the primary job.
+ *
+ * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is
+ * larger than the write buffer size of the pipe, we will
+ * deadlock here.
+ */
+ for (i = 1; i < max_tokens; i++)
+ JobTokenAdd();
}
/* Return a withdrawn token to the pool. */
void
Job_TokenReturn(void)
{
- jobTokensRunning--;
- if (jobTokensRunning < 0)
- Punt("token botch");
- if (jobTokensRunning || JOB_TOKENS[aborting] != '+')
- JobTokenAdd();
+ jobTokensRunning--;
+ if (jobTokensRunning < 0)
+ Punt("token botch");
+ if (jobTokensRunning != 0 || JOB_TOKENS[aborting] != '+')
+ JobTokenAdd();
}
-/* Attempt to withdraw a token from the pool.
+/*
+ * Attempt to withdraw a token from the pool.
*
* If pool is empty, set wantToken so that we wake up when a token is
* released.
*
* Returns TRUE if a token was withdrawn, and FALSE if the pool is currently
- * empty. */
+ * empty.
+ */
Boolean
Job_TokenWithdraw(void)
{
- char tok, tok1;
- ssize_t count;
+ char tok, tok1;
+ ssize_t count;
- wantToken = 0;
- DEBUG3(JOB, "Job_TokenWithdraw(%d): aborting %d, running %d\n",
- getpid(), aborting, jobTokensRunning);
+ wantToken = 0;
+ DEBUG3(JOB, "Job_TokenWithdraw(%d): aborting %d, running %d\n",
+ getpid(), aborting, jobTokensRunning);
- if (aborting != ABORT_NONE || (jobTokensRunning >= opts.maxJobs))
- return FALSE;
+ if (aborting != ABORT_NONE || (jobTokensRunning >= opts.maxJobs))
+ return FALSE;
- count = read(tokenWaitJob.inPipe, &tok, 1);
- if (count == 0)
- Fatal("eof on job pipe!");
- if (count < 0 && jobTokensRunning != 0) {
- if (errno != EAGAIN) {
- Fatal("job pipe read: %s", strerror(errno));
+ count = read(tokenWaitJob.inPipe, &tok, 1);
+ if (count == 0)
+ Fatal("eof on job pipe!");
+ if (count < 0 && jobTokensRunning != 0) {
+ if (errno != EAGAIN) {
+ Fatal("job pipe read: %s", strerror(errno));
+ }
+ DEBUG1(JOB, "(%d) blocked for token\n", getpid());
+ wantToken = 1;
+ return FALSE;
}
- DEBUG1(JOB, "(%d) blocked for token\n", getpid());
- return FALSE;
- }
- if (count == 1 && tok != '+') {
- /* make being abvorted - remove any other job tokens */
- DEBUG2(JOB, "(%d) aborted by token %c\n", getpid(), tok);
- while (read(tokenWaitJob.inPipe, &tok1, 1) == 1)
- continue;
- /* And put the stopper back */
- while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
- continue;
- if (shouldDieQuietly(NULL, 1))
- exit(2);
- Fatal("A failure has been detected in another branch of the parallel make");
- }
+ if (count == 1 && tok != '+') {
+ /* make being aborted - remove any other job tokens */
+ DEBUG2(JOB, "(%d) aborted by token %c\n", getpid(), tok);
+ while (read(tokenWaitJob.inPipe, &tok1, 1) == 1)
+ continue;
+ /* And put the stopper back */
+ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 &&
+ errno == EAGAIN)
+ continue;
+ if (shouldDieQuietly(NULL, 1))
+ exit(6); /* we aborted */
+ Fatal("A failure has been detected "
+ "in another branch of the parallel make");
+ }
- if (count == 1 && jobTokensRunning == 0)
- /* We didn't want the token really */
- while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
- continue;
+ if (count == 1 && jobTokensRunning == 0)
+ /* We didn't want the token really */
+ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 &&
+ errno == EAGAIN)
+ continue;
- jobTokensRunning++;
- DEBUG1(JOB, "(%d) withdrew token\n", getpid());
- return TRUE;
+ jobTokensRunning++;
+ DEBUG1(JOB, "(%d) withdrew token\n", getpid());
+ return TRUE;
}
-/* Run the named target if found. If a filename is specified, then set that
+/*
+ * Run the named target if found. If a filename is specified, then set that
* to the sources.
*
- * Exits if the target fails. */
+ * Exits if the target fails.
+ */
Boolean
-Job_RunTarget(const char *target, const char *fname) {
- GNode *gn = Targ_FindNode(target);
- if (gn == NULL)
- return FALSE;
+Job_RunTarget(const char *target, const char *fname)
+{
+ GNode *gn = Targ_FindNode(target);
+ if (gn == NULL)
+ return FALSE;
- if (fname)
- Var_Set(ALLSRC, fname, gn);
+ if (fname != NULL)
+ Var_Set(ALLSRC, fname, gn);
- JobRun(gn);
- if (gn->made == ERROR) {
- PrintOnError(gn, "\n\nStop.");
- exit(1);
- }
- return TRUE;
+ JobRun(gn);
+ /* XXX: Replace with GNode_IsError(gn) */
+ if (gn->made == ERROR) {
+ PrintOnError(gn, "\n\nStop.");
+ exit(1);
+ }
+ return TRUE;
}
#ifdef USE_SELECT
int
emul_poll(struct pollfd *fd, int nfd, int timeout)
{
- fd_set rfds, wfds;
- int i, maxfd, nselect, npoll;
- struct timeval tv, *tvp;
- long usecs;
+ fd_set rfds, wfds;
+ int i, maxfd, nselect, npoll;
+ struct timeval tv, *tvp;
+ long usecs;
- FD_ZERO(&rfds);
- FD_ZERO(&wfds);
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
- maxfd = -1;
- for (i = 0; i < nfd; i++) {
- fd[i].revents = 0;
+ maxfd = -1;
+ for (i = 0; i < nfd; i++) {
+ fd[i].revents = 0;
- if (fd[i].events & POLLIN)
- FD_SET(fd[i].fd, &rfds);
+ if (fd[i].events & POLLIN)
+ FD_SET(fd[i].fd, &rfds);
- if (fd[i].events & POLLOUT)
- FD_SET(fd[i].fd, &wfds);
+ if (fd[i].events & POLLOUT)
+ FD_SET(fd[i].fd, &wfds);
- if (fd[i].fd > maxfd)
- maxfd = fd[i].fd;
- }
+ if (fd[i].fd > maxfd)
+ maxfd = fd[i].fd;
+ }
- if (maxfd >= FD_SETSIZE) {
- Punt("Ran out of fd_set slots; "
- "recompile with a larger FD_SETSIZE.");
- }
+ if (maxfd >= FD_SETSIZE) {
+ Punt("Ran out of fd_set slots; "
+ "recompile with a larger FD_SETSIZE.");
+ }
- if (timeout < 0) {
- tvp = NULL;
- } else {
- usecs = timeout * 1000;
- tv.tv_sec = usecs / 1000000;
- tv.tv_usec = usecs % 1000000;
- tvp = &tv;
- }
+ if (timeout < 0) {
+ tvp = NULL;
+ } else {
+ usecs = timeout * 1000;
+ tv.tv_sec = usecs / 1000000;
+ tv.tv_usec = usecs % 1000000;
+ tvp = &tv;
+ }
- nselect = select(maxfd + 1, &rfds, &wfds, NULL, tvp);
+ nselect = select(maxfd + 1, &rfds, &wfds, NULL, tvp);
- if (nselect <= 0)
- return nselect;
+ if (nselect <= 0)
+ return nselect;
- npoll = 0;
- for (i = 0; i < nfd; i++) {
- if (FD_ISSET(fd[i].fd, &rfds))
- fd[i].revents |= POLLIN;
+ npoll = 0;
+ for (i = 0; i < nfd; i++) {
+ if (FD_ISSET(fd[i].fd, &rfds))
+ fd[i].revents |= POLLIN;
- if (FD_ISSET(fd[i].fd, &wfds))
- fd[i].revents |= POLLOUT;
+ if (FD_ISSET(fd[i].fd, &wfds))
+ fd[i].revents |= POLLOUT;
- if (fd[i].revents)
- npoll++;
- }
+ if (fd[i].revents)
+ npoll++;
+ }
- return npoll;
+ return npoll;
}
#endif /* USE_SELECT */
diff --git a/job.h b/job.h
index d48424858201..9bb5f149766f 100644
--- a/job.h
+++ b/job.h
@@ -1,4 +1,4 @@
-/* $NetBSD: job.h,v 1.63 2020/11/14 13:27:01 rillig Exp $ */
+/* $NetBSD: job.h,v 1.71 2020/12/30 10:03:16 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -97,8 +97,8 @@ struct emul_pollfd {
short revents;
};
-#define POLLIN 0x0001
-#define POLLOUT 0x0004
+#define POLLIN 0x0001
+#define POLLOUT 0x0004
int
emul_poll(struct pollfd *fd, int nfd, int timeout);
@@ -118,27 +118,15 @@ struct pollfd;
#endif
typedef enum JobStatus {
- JOB_ST_FREE = 0, /* Job is available */
- JOB_ST_SET_UP = 1, /* Job is allocated but otherwise invalid */
- /* XXX: What about the 2? */
- JOB_ST_RUNNING = 3, /* Job is running, pid valid */
- JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */
+ JOB_ST_FREE = 0, /* Job is available */
+ JOB_ST_SET_UP = 1, /* Job is allocated but otherwise invalid */
+ /* XXX: What about the 2? */
+ JOB_ST_RUNNING = 3, /* Job is running, pid valid */
+ JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */
} JobStatus;
-typedef enum JobFlags {
- JOB_NONE = 0,
- /* Ignore non-zero exits */
- JOB_IGNERR = 1 << 0,
- /* no output */
- JOB_SILENT = 1 << 1,
- /* Target is a special one. i.e. run it locally
- * if we can't export it and maxLocal is 0 */
- JOB_SPECIAL = 1 << 2,
- /* we've sent 'set -x' */
- JOB_TRACED = 1 << 10
-} JobFlags;
-
-/* A Job manages the shell commands that are run to create a single target.
+/*
+ * A Job manages the shell commands that are run to create a single target.
* Each job is run in a separate subprocess by a shell. Several jobs can run
* in parallel.
*
@@ -148,42 +136,47 @@ typedef enum JobFlags {
*
* When a job is finished, Make_Update updates all parents of the node
* that was just remade, marking them as ready to be made next if all
- * other dependencies are finished as well. */
+ * other dependencies are finished as well.
+ */
typedef struct Job {
- /* The process ID of the shell running the commands */
- int pid;
+ /* The process ID of the shell running the commands */
+ int pid;
- /* The target the child is making */
- GNode *node;
+ /* The target the child is making */
+ GNode *node;
- /* If one of the shell commands is "...", all following commands are
- * delayed until the .END node is made. This list node points to the
- * first of these commands, if any. */
- StringListNode *tailCmds;
+ /* If one of the shell commands is "...", all following commands are
+ * delayed until the .END node is made. This list node points to the
+ * first of these commands, if any. */
+ StringListNode *tailCmds;
- /* When creating the shell script, this is where the commands go.
- * This is only used before the job is actually started. */
- FILE *cmdFILE;
+ /* This is where the shell commands go. */
+ FILE *cmdFILE;
- int exit_status; /* from wait4() in signal handler */
+ int exit_status; /* from wait4() in signal handler */
- JobStatus status;
+ JobStatus status;
- Boolean suspended;
+ Boolean suspended;
- JobFlags flags; /* Flags to control treatment of job */
+ /* Ignore non-zero exits */
+ Boolean ignerr;
+ /* Output the command before or instead of running it. */
+ Boolean echo;
+ /* Target is a special one. */
+ Boolean special;
- int inPipe; /* Pipe for reading output from job */
- int outPipe; /* Pipe for writing control commands */
- struct pollfd *inPollfd; /* pollfd associated with inPipe */
+ int inPipe; /* Pipe for reading output from job */
+ int outPipe; /* Pipe for writing control commands */
+ struct pollfd *inPollfd; /* pollfd associated with inPipe */
#define JOB_BUFSIZE 1024
- /* Buffer for storing the output of the job, line by line. */
- char outBuf[JOB_BUFSIZE + 1];
- size_t curPos; /* Current position in outBuf. */
+ /* Buffer for storing the output of the job, line by line. */
+ char outBuf[JOB_BUFSIZE + 1];
+ size_t curPos; /* Current position in outBuf. */
#ifdef USE_META
- struct BuildMon bm;
+ struct BuildMon bm;
#endif
} Job;
@@ -211,5 +204,6 @@ Boolean Job_TokenWithdraw(void);
void Job_ServerStart(int, int, int);
void Job_SetPrefix(void);
Boolean Job_RunTarget(const char *, const char *);
+void Job_FlagsToString(const Job *, char *, size_t);
#endif /* MAKE_JOB_H */
diff --git a/lst.c b/lst.c
index 71a0b41c1077..8d660b37ff52 100644
--- a/lst.c
+++ b/lst.c
@@ -1,4 +1,4 @@
-/* $NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig Exp $ */
+/* $NetBSD: lst.c,v 1.102 2020/12/30 10:03:16 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -34,278 +34,266 @@
#include "make.h"
-MAKE_RCSID("$NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig Exp $");
-
-#ifdef HAVE_INTTYPES_H
-#include <inttypes.h>
-#elif defined(HAVE_STDINT_H)
-#include <stdint.h>
-#endif
+MAKE_RCSID("$NetBSD: lst.c,v 1.102 2020/12/30 10:03:16 rillig Exp $");
static ListNode *
LstNodeNew(ListNode *prev, ListNode *next, void *datum)
{
- ListNode *ln = bmake_malloc(sizeof *ln);
- ln->prev = prev;
- ln->next = next;
- ln->datum = datum;
- return ln;
+ ListNode *ln = bmake_malloc(sizeof *ln);
+
+ ln->prev = prev;
+ ln->next = next;
+ ln->datum = datum;
+
+ return ln;
}
/* Create and initialize a new, empty list. */
List *
Lst_New(void)
{
- List *list = bmake_malloc(sizeof *list);
+ List *list = bmake_malloc(sizeof *list);
+ Lst_Init(list);
+ return list;
+}
- list->first = NULL;
- list->last = NULL;
+void
+Lst_Done(List *list)
+{
+ ListNode *ln, *next;
+
+ for (ln = list->first; ln != NULL; ln = next) {
+ next = ln->next;
+ free(ln);
+ }
+}
+
+void
+Lst_DoneCall(List *list, LstFreeProc freeProc)
+{
+ ListNode *ln, *next;
- return list;
+ for (ln = list->first; ln != NULL; ln = next) {
+ next = ln->next;
+ freeProc(ln->datum);
+ free(ln);
+ }
}
/* Free a list and all its nodes. The node data are not freed though. */
void
Lst_Free(List *list)
{
- ListNode *ln, *next;
- for (ln = list->first; ln != NULL; ln = next) {
- next = ln->next;
- free(ln);
- }
-
- free(list);
+ Lst_Done(list);
+ free(list);
}
-/* Destroy a list and free all its resources. The freeProc is called with the
- * datum from each node in turn before the node is freed. */
+/*
+ * Destroy a list and free all its resources. The freeProc is called with the
+ * datum from each node in turn before the node is freed.
+ */
void
Lst_Destroy(List *list, LstFreeProc freeProc)
{
- ListNode *ln, *next;
-
- for (ln = list->first; ln != NULL; ln = next) {
- next = ln->next;
- freeProc(ln->datum);
- free(ln);
- }
-
- free(list);
+ Lst_DoneCall(list, freeProc);
+ free(list);
}
/* Insert a new node with the datum before the given node. */
void
Lst_InsertBefore(List *list, ListNode *ln, void *datum)
{
- ListNode *newNode;
+ ListNode *newNode;
- assert(datum != NULL);
+ assert(datum != NULL);
- newNode = LstNodeNew(ln->prev, ln, datum);
+ newNode = LstNodeNew(ln->prev, ln, datum);
- if (ln->prev != NULL)
- ln->prev->next = newNode;
- ln->prev = newNode;
+ if (ln->prev != NULL)
+ ln->prev->next = newNode;
+ ln->prev = newNode;
- if (ln == list->first)
- list->first = newNode;
+ if (ln == list->first)
+ list->first = newNode;
}
/* Add a piece of data at the start of the given list. */
void
Lst_Prepend(List *list, void *datum)
{
- ListNode *ln;
+ ListNode *ln;
- assert(datum != NULL);
+ assert(datum != NULL);
- ln = LstNodeNew(NULL, list->first, datum);
+ ln = LstNodeNew(NULL, list->first, datum);
- if (list->first == NULL) {
- list->first = ln;
- list->last = ln;
- } else {
- list->first->prev = ln;
- list->first = ln;
- }
+ if (list->first == NULL) {
+ list->first = ln;
+ list->last = ln;
+ } else {
+ list->first->prev = ln;
+ list->first = ln;
+ }
}
/* Add a piece of data at the end of the given list. */
void
Lst_Append(List *list, void *datum)
{
- ListNode *ln;
+ ListNode *ln;
- assert(datum != NULL);
+ assert(datum != NULL);
- ln = LstNodeNew(list->last, NULL, datum);
+ ln = LstNodeNew(list->last, NULL, datum);
- if (list->last == NULL) {
- list->first = ln;
- list->last = ln;
- } else {
- list->last->next = ln;
- list->last = ln;
- }
+ if (list->last == NULL) {
+ list->first = ln;
+ list->last = ln;
+ } else {
+ list->last->next = ln;
+ list->last = ln;
+ }
}
-/* Remove the given node from the given list.
- * The datum stored in the node must be freed by the caller, if necessary. */
+/*
+ * Remove the given node from the given list.
+ * The datum stored in the node must be freed by the caller, if necessary.
+ */
void
Lst_Remove(List *list, ListNode *ln)
{
- /* unlink it from its neighbors */
- if (ln->next != NULL)
- ln->next->prev = ln->prev;
- if (ln->prev != NULL)
- ln->prev->next = ln->next;
-
- /* unlink it from the list */
- if (list->first == ln)
- list->first = ln->next;
- if (list->last == ln)
- list->last = ln->prev;
+ /* unlink it from its neighbors */
+ if (ln->next != NULL)
+ ln->next->prev = ln->prev;
+ if (ln->prev != NULL)
+ ln->prev->next = ln->next;
+
+ /* unlink it from the list */
+ if (list->first == ln)
+ list->first = ln->next;
+ if (list->last == ln)
+ list->last = ln->prev;
}
/* Replace the datum in the given node with the new datum. */
void
LstNode_Set(ListNode *ln, void *datum)
{
- assert(datum != NULL);
+ assert(datum != NULL);
- ln->datum = datum;
+ ln->datum = datum;
}
-/* Replace the datum in the given node with NULL.
- * Having NULL values in a list is unusual though. */
+/*
+ * Replace the datum in the given node with NULL.
+ * Having NULL values in a list is unusual though.
+ */
void
LstNode_SetNull(ListNode *ln)
{
- ln->datum = NULL;
+ ln->datum = NULL;
}
-/* Return the first node that contains the given datum, or NULL.
+/*
+ * Return the first node that contains the given datum, or NULL.
*
- * Time complexity: O(length(list)) */
+ * Time complexity: O(length(list))
+ */
ListNode *
Lst_FindDatum(List *list, const void *datum)
{
- ListNode *ln;
-
- assert(datum != NULL);
+ ListNode *ln;
- for (ln = list->first; ln != NULL; ln = ln->next)
- if (ln->datum == datum)
- return ln;
+ assert(datum != NULL);
- return NULL;
-}
+ for (ln = list->first; ln != NULL; ln = ln->next)
+ if (ln->datum == datum)
+ return ln;
-int
-Lst_ForEachUntil(List *list, LstActionUntilProc proc, void *procData)
-{
- ListNode *ln;
- int result = 0;
-
- for (ln = list->first; ln != NULL; ln = ln->next) {
- result = proc(ln->datum, procData);
- if (result != 0)
- break;
- }
- return result;
+ return NULL;
}
-/* Move all nodes from src to the end of dst.
- * The source list is destroyed and freed. */
+/*
+ * Move all nodes from src to the end of dst.
+ * The source list becomes empty but is not freed.
+ */
void
Lst_MoveAll(List *dst, List *src)
{
- if (src->first != NULL) {
- src->first->prev = dst->last;
- if (dst->last != NULL)
- dst->last->next = src->first;
- else
- dst->first = src->first;
-
- dst->last = src->last;
- }
- free(src);
+ if (src->first != NULL) {
+ src->first->prev = dst->last;
+ if (dst->last != NULL)
+ dst->last->next = src->first;
+ else
+ dst->first = src->first;
+
+ dst->last = src->last;
+ }
}
/* Copy the element data from src to the start of dst. */
void
Lst_PrependAll(List *dst, List *src)
{
- ListNode *node;
- for (node = src->last; node != NULL; node = node->prev)
- Lst_Prepend(dst, node->datum);
+ ListNode *ln;
+
+ for (ln = src->last; ln != NULL; ln = ln->prev)
+ Lst_Prepend(dst, ln->datum);
}
/* Copy the element data from src to the end of dst. */
void
Lst_AppendAll(List *dst, List *src)
{
- ListNode *node;
- for (node = src->first; node != NULL; node = node->next)
- Lst_Append(dst, node->datum);
-}
-
-/*
- * for using the list as a queue
- */
+ ListNode *ln;
-/* Add the datum to the tail of the given list. */
-void
-Lst_Enqueue(List *list, void *datum)
-{
- Lst_Append(list, datum);
+ for (ln = src->first; ln != NULL; ln = ln->next)
+ Lst_Append(dst, ln->datum);
}
/* Remove and return the datum at the head of the given list. */
void *
Lst_Dequeue(List *list)
{
- void *datum = list->first->datum;
- Lst_Remove(list, list->first);
- assert(datum != NULL); /* since NULL would mean end of the list */
- return datum;
+ void *datum = list->first->datum;
+ Lst_Remove(list, list->first);
+ assert(datum != NULL); /* since NULL would mean end of the list */
+ return datum;
}
void
Vector_Init(Vector *v, size_t itemSize)
{
- v->len = 0;
- v->priv_cap = 10;
- v->itemSize = itemSize;
- v->items = bmake_malloc(v->priv_cap * v->itemSize);
+ v->len = 0;
+ v->cap = 10;
+ v->itemSize = itemSize;
+ v->items = bmake_malloc(v->cap * v->itemSize);
}
-/* Add space for a new item to the vector and return a pointer to that space.
- * The returned data is valid until the next modifying operation. */
+/*
+ * Add space for a new item to the vector and return a pointer to that space.
+ * The returned data is valid until the next modifying operation.
+ */
void *
Vector_Push(Vector *v)
{
- if (v->len >= v->priv_cap) {
- v->priv_cap *= 2;
- v->items = bmake_realloc(v->items, v->priv_cap * v->itemSize);
- }
- v->len++;
- return Vector_Get(v, v->len - 1);
+ if (v->len >= v->cap) {
+ v->cap *= 2;
+ v->items = bmake_realloc(v->items, v->cap * v->itemSize);
+ }
+ v->len++;
+ return Vector_Get(v, v->len - 1);
}
-/* Return the pointer to the last item in the vector.
- * The returned data is valid until the next modifying operation. */
+/*
+ * Return the pointer to the last item in the vector.
+ * The returned data is valid until the next modifying operation.
+ */
void *
Vector_Pop(Vector *v)
{
- assert(v->len > 0);
- v->len--;
- return Vector_Get(v, v->len);
-}
-
-void
-Vector_Done(Vector *v)
-{
- free(v->items);
+ assert(v->len > 0);
+ v->len--;
+ return Vector_Get(v, v->len);
}
diff --git a/lst.h b/lst.h
index 6965678c1e09..30c28ed7096d 100644
--- a/lst.h
+++ b/lst.h
@@ -1,4 +1,4 @@
-/* $NetBSD: lst.h,v 1.85 2020/11/10 00:32:12 rillig Exp $ */
+/* $NetBSD: lst.h,v 1.95 2021/01/03 21:12:03 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -78,48 +78,62 @@
#ifndef MAKE_LST_H
#define MAKE_LST_H
-#include <sys/param.h>
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#elif defined(HAVE_STDINT_H)
#include <stdint.h>
+#endif
+#ifdef HAVE_STDLIB_H
#include <stdlib.h>
+#endif
/* A doubly-linked list of pointers. */
-typedef struct List List;
+typedef struct List List;
/* A single node in the doubly-linked list. */
-typedef struct ListNode ListNode;
+typedef struct ListNode ListNode;
struct ListNode {
- ListNode *prev; /* previous node in list, or NULL */
- ListNode *next; /* next node in list, or NULL */
- union {
+ ListNode *prev; /* previous node in list, or NULL */
+ ListNode *next; /* next node in list, or NULL */
void *datum; /* datum associated with this element */
- const struct GNode *priv_gnode; /* alias, just for debugging */
- const char *priv_str; /* alias, just for debugging */
- };
};
struct List {
- ListNode *first; /* first node in list */
- ListNode *last; /* last node in list */
+ ListNode *first;
+ ListNode *last;
};
/* Free the datum of a node, called before freeing the node itself. */
typedef void LstFreeProc(void *);
-/* An action for Lst_ForEachUntil and Lst_ForEachUntilConcurrent. */
-typedef int LstActionUntilProc(void *datum, void *args);
/* Create or destroy a list */
/* Create a new list. */
List *Lst_New(void);
+/* Free the list nodes, but not the list itself. */
+void Lst_Done(List *);
+/* Free the list nodes, freeing the node data using the given function. */
+void Lst_DoneCall(List *, LstFreeProc);
/* Free the list, leaving the node data unmodified. */
void Lst_Free(List *);
/* Free the list, freeing the node data using the given function. */
void Lst_Destroy(List *, LstFreeProc);
+#define LST_INIT { NULL, NULL }
+
+/* Initialize a list, without memory allocation. */
+MAKE_INLINE void
+Lst_Init(List *list)
+{
+ list->first = NULL;
+ list->last = NULL;
+}
+
/* Get information about a list */
MAKE_INLINE Boolean
-Lst_IsEmpty(List *list) { return list->first == NULL; }
+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 *);
@@ -145,43 +159,47 @@ void LstNode_Set(ListNode *, void *);
/* Set the value of the node to NULL. Having NULL in a list is unusual. */
void LstNode_SetNull(ListNode *);
-/* Iterating over a list, using a callback function */
-
-/* Run the action for each datum of the list, until the action returns
- * non-zero.
- *
- * During this iteration, the list must not be modified structurally. */
-int Lst_ForEachUntil(List *, LstActionUntilProc, void *);
-
/* Using the list as a queue */
/* Add a datum at the tail of the queue. */
-void Lst_Enqueue(List *, void *);
+MAKE_INLINE void
+Lst_Enqueue(List *list, void *datum) {
+ Lst_Append(list, datum);
+}
+
/* Remove the head node of the queue and return its datum. */
void *Lst_Dequeue(List *);
-/* A vector is an ordered collection of items, allowing for fast indexed
- * access. */
+/*
+ * A vector is an ordered collection of items, allowing for fast indexed
+ * access.
+ */
typedef struct Vector {
- void *items; /* memory holding the items */
- size_t itemSize; /* size of a single item in bytes */
- size_t len; /* number of actually usable elements */
- size_t priv_cap; /* capacity */
+ void *items; /* memory holding the items */
+ size_t itemSize; /* size of a single item */
+ size_t len; /* number of actually usable elements */
+ size_t cap; /* capacity */
} Vector;
void Vector_Init(Vector *, size_t);
-/* Return the pointer to the given item in the vector.
- * The returned data is valid until the next modifying operation. */
+/*
+ * Return the pointer to the given item in the vector.
+ * The returned data is valid until the next modifying operation.
+ */
MAKE_INLINE void *
Vector_Get(Vector *v, size_t i)
{
- unsigned char *items = v->items;
- return items + i * v->itemSize;
+ unsigned char *items = v->items;
+ return items + i * v->itemSize;
}
void *Vector_Push(Vector *);
void *Vector_Pop(Vector *);
-void Vector_Done(Vector *);
+
+MAKE_INLINE void
+Vector_Done(Vector *v) {
+ free(v->items);
+}
#endif /* MAKE_LST_H */
diff --git a/main.c b/main.c
index 6e97a9a12541..f2813d263c2a 100644
--- a/main.c
+++ b/main.c
@@ -1,4 +1,4 @@
-/* $NetBSD: main.c,v 1.476 2020/11/16 22:08:20 rillig Exp $ */
+/* $NetBSD: main.c,v 1.512 2021/01/10 23:59:53 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -68,7 +68,8 @@
* SUCH DAMAGE.
*/
-/* The main file for this entire program. Exit routines etc. reside here.
+/*
+ * The main file for this entire program. Exit routines etc. reside here.
*
* Utility functions defined in this file:
*
@@ -109,17 +110,13 @@
#include "trace.h"
/* "@(#)main.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: main.c,v 1.476 2020/11/16 22:08:20 rillig Exp $");
+MAKE_RCSID("$NetBSD: main.c,v 1.512 2021/01/10 23:59:53 rillig Exp $");
#if defined(MAKE_NATIVE) && !defined(lint)
__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 "
"The Regents of the University of California. "
"All rights reserved.");
#endif
-#ifndef DEFMAXLOCAL
-#define DEFMAXLOCAL DEFMAXJOBS
-#endif
-
#ifndef __arraycount
# define __arraycount(__x) (sizeof(__x) / sizeof(__x[0]))
#endif
@@ -133,7 +130,6 @@ Boolean deleteOnError; /* .DELETE_ON_ERROR: set */
static int maxJobTokens; /* -j argument */
Boolean enterFlagObj; /* -w and objdir != srcdir */
-Boolean preserveUndefined;
static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */
Boolean doing_depend; /* Set while reading .depend */
static Boolean jobsRunning; /* TRUE if the jobs might be running */
@@ -144,13 +140,13 @@ static void purge_relative_cached_realpaths(void);
static Boolean ignorePWD; /* if we use -C, PWD is meaningless */
static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */
char curdir[MAXPATHLEN + 1]; /* Startup directory */
-char *progname; /* the program name */
+const char *progname;
char *makeDependfile;
pid_t myPid;
int makelevel;
Boolean forceJobs = FALSE;
-static int errors = 0;
+static int main_errors = 0;
static HashTable cached_realpaths;
/*
@@ -167,16 +163,16 @@ explode(const char *flags)
if (flags == NULL)
return NULL;
- for (f = flags; *f; f++)
+ for (f = flags; *f != '\0'; f++)
if (!ch_isalpha(*f))
break;
- if (*f)
+ if (*f != '\0')
return bmake_strdup(flags);
len = strlen(flags);
st = nf = bmake_malloc(len * 3 + 1);
- while (*flags) {
+ while (*flags != '\0') {
*nf++ = '-';
*nf++ = *flags++;
*nf++ = ' ';
@@ -251,7 +247,7 @@ parse_debug_options(const char *argvalue)
const char *modules;
DebugFlags debug = opts.debug;
- for (modules = argvalue; *modules; ++modules) {
+ for (modules = argvalue; *modules != '\0'; ++modules) {
switch (*modules) {
case '0': /* undocumented, only intended for tests */
debug = DEBUG_NONE;
@@ -296,7 +292,7 @@ parse_debug_options(const char *argvalue)
debug |= DEBUG_JOB;
break;
case 'L':
- opts.lint = TRUE;
+ opts.strict = TRUE;
break;
case 'l':
debug |= DEBUG_LOUD;
@@ -381,7 +377,7 @@ MainParseArgChdir(const char *argvalue)
if (chdir(argvalue) == -1) {
(void)fprintf(stderr, "%s: chdir %s: %s\n",
progname, argvalue, strerror(errno));
- exit(1);
+ exit(2); /* Not 1 so -q can distinguish error */
}
if (getcwd(curdir, MAXPATHLEN) == NULL) {
(void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno));
@@ -434,7 +430,7 @@ MainParseArgJobs(const char *argvalue)
(void)fprintf(stderr,
"%s: illegal argument to -j -- must be positive integer!\n",
progname);
- exit(1); /* XXX: why not 2? */
+ exit(2); /* Not 1 so -q can distinguish error */
}
Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL);
Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
@@ -504,7 +500,7 @@ MainParseArg(char c, const char *argvalue)
case 'V':
case 'v':
opts.printVars = c == 'v' ? PVM_EXPANDED : PVM_UNEXPANDED;
- Lst_Append(opts.variables, bmake_strdup(argvalue));
+ Lst_Append(&opts.variables, bmake_strdup(argvalue));
/* XXX: Why always -V? */
Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL);
Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
@@ -532,7 +528,7 @@ MainParseArg(char c, const char *argvalue)
Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL);
break;
case 'f':
- Lst_Append(opts.makefiles, bmake_strdup(argvalue));
+ Lst_Append(&opts.makefiles, bmake_strdup(argvalue));
break;
case 'i':
opts.ignoreErrors = TRUE;
@@ -581,13 +577,15 @@ MainParseArg(char c, const char *argvalue)
return TRUE;
}
-/* Parse the given arguments. Called from main() and from
+/*
+ * Parse the given arguments. Called from main() and from
* Main_ParseArgLine() when the .MAKEFLAGS target is used.
*
* The arguments must be treated as read-only and will be freed after the
* call.
*
- * XXX: Deal with command line overriding .MAKEFLAGS in makefile */
+ * XXX: Deal with command line overriding .MAKEFLAGS in makefile
+ */
static void
MainParseArgs(int argc, char **argv)
{
@@ -668,7 +666,7 @@ rearg:
Punt("illegal (null) argument.");
if (argv[1][0] == '-' && !dashDash)
goto rearg;
- Lst_Append(opts.create, bmake_strdup(argv[1]));
+ Lst_Append(&opts.create, bmake_strdup(argv[1]));
}
}
@@ -679,10 +677,12 @@ noarg:
usage();
}
-/* Break a line of arguments into words and parse them.
+/*
+ * Break a line of arguments into words and parse them.
*
* Used when a .MFLAGS or .MAKEFLAGS target is encountered during parsing and
- * by main() when reading the MAKEFLAGS environment variable. */
+ * by main() when reading the MAKEFLAGS environment variable.
+ */
void
Main_ParseArgLine(const char *line)
{
@@ -691,6 +691,7 @@ Main_ParseArgLine(const char *line)
if (line == NULL)
return;
+ /* XXX: don't use line as an iterator variable */
for (; *line == ' '; ++line)
continue;
if (line[0] == '\0')
@@ -711,10 +712,9 @@ Main_ParseArgLine(const char *line)
}
#endif
{
- void *freeIt;
- const char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &freeIt);
- buf = str_concat3(argv0, " ", line);
- free(freeIt);
+ FStr argv0 = Var_Value(".MAKE", VAR_GLOBAL);
+ buf = str_concat3(argv0.str, " ", line);
+ FStr_Done(&argv0);
}
words = Str_Words(buf, TRUE);
@@ -772,34 +772,34 @@ Main_SetObjdir(Boolean writable, const char *fmt, ...)
static Boolean
SetVarObjdir(Boolean writable, const char *var, const char *suffix)
{
- void *path_freeIt;
- const char *path = Var_Value(var, VAR_CMDLINE, &path_freeIt);
- const char *xpath;
- char *xpath_freeIt;
+ FStr path = Var_Value(var, VAR_CMDLINE);
+ FStr xpath;
- if (path == NULL || path[0] == '\0') {
- bmake_free(path_freeIt);
+ if (path.str == NULL || path.str[0] == '\0') {
+ FStr_Done(&path);
return FALSE;
}
/* expand variable substitutions */
- xpath = path;
- xpath_freeIt = NULL;
- if (strchr(path, '$') != 0) {
- (void)Var_Subst(path, VAR_GLOBAL, VARE_WANTRES, &xpath_freeIt);
+ xpath = FStr_InitRefer(path.str);
+ if (strchr(path.str, '$') != 0) {
+ char *expanded;
+ (void)Var_Subst(path.str, VAR_GLOBAL, VARE_WANTRES, &expanded);
/* TODO: handle errors */
- xpath = xpath_freeIt;
+ xpath = FStr_InitOwn(expanded);
}
- (void)Main_SetObjdir(writable, "%s%s", xpath, suffix);
+ (void)Main_SetObjdir(writable, "%s%s", xpath.str, suffix);
- bmake_free(xpath_freeIt);
- bmake_free(path_freeIt);
+ FStr_Done(&xpath);
+ FStr_Done(&path);
return TRUE;
}
-/* Splits str into words, adding them to the list.
- * The string must be kept alive as long as the list. */
+/*
+ * Splits str into words, adding them to the list.
+ * The string must be kept alive as long as the list.
+ */
int
str2Lst_Append(StringList *lp, char *str)
{
@@ -808,7 +808,7 @@ str2Lst_Append(StringList *lp, char *str)
const char *sep = " \t";
- for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) {
+ for (n = 0, cp = strtok(str, sep); cp != NULL; cp = strtok(NULL, sep)) {
Lst_Append(lp, cp);
n++;
}
@@ -831,39 +831,38 @@ siginfo(int signo MAKE_ATTR_UNUSED)
}
#endif
-/*
- * Allow makefiles some control over the mode we run in.
- */
-void
-MakeMode(const char *mode)
+/* Allow makefiles some control over the mode we run in. */
+static void
+MakeMode(void)
{
- char *mode_freeIt = NULL;
+ FStr mode = FStr_InitRefer(NULL);
- if (mode == NULL) {
+ if (mode.str == NULL) {
+ char *expanded;
(void)Var_Subst("${" MAKE_MODE ":tl}",
- VAR_GLOBAL, VARE_WANTRES, &mode_freeIt);
+ VAR_GLOBAL, VARE_WANTRES, &expanded);
/* TODO: handle errors */
- mode = mode_freeIt;
+ mode = FStr_InitOwn(expanded);
}
- if (mode[0] != '\0') {
- if (strstr(mode, "compat")) {
+ if (mode.str[0] != '\0') {
+ if (strstr(mode.str, "compat") != NULL) {
opts.compatMake = TRUE;
forceJobs = FALSE;
}
#if USE_META
- if (strstr(mode, "meta"))
- meta_mode_init(mode);
+ if (strstr(mode.str, "meta") != NULL)
+ meta_mode_init(mode.str);
#endif
}
- free(mode_freeIt);
+ FStr_Done(&mode);
}
static void
PrintVar(const char *varname, Boolean expandVars)
{
- if (strchr(varname, '$')) {
+ if (strchr(varname, '$') != NULL) {
char *evalue;
(void)Var_Subst(varname, VAR_GLOBAL, VARE_WANTRES, &evalue);
/* TODO: handle errors */
@@ -880,10 +879,9 @@ PrintVar(const char *varname, Boolean expandVars)
bmake_free(evalue);
} else {
- void *freeIt;
- const char *value = Var_Value(varname, VAR_GLOBAL, &freeIt);
- printf("%s\n", value ? value : "");
- bmake_free(freeIt);
+ FStr value = Var_Value(varname, VAR_GLOBAL);
+ printf("%s\n", value.str != NULL ? value.str : "");
+ FStr_Done(&value);
}
}
@@ -922,7 +920,7 @@ doPrintVars(void)
else
expandVars = GetBooleanVar(".MAKE.EXPAND_VARIABLES", FALSE);
- for (ln = opts.variables->first; ln != NULL; ln = ln->next) {
+ for (ln = opts.variables.first; ln != NULL; ln = ln->next) {
const char *varname = ln->datum;
PrintVar(varname, expandVars);
}
@@ -931,7 +929,7 @@ doPrintVars(void)
static Boolean
runTargets(void)
{
- GNodeList *targs; /* target nodes to create */
+ GNodeList targs = LST_INIT; /* target nodes to create */
Boolean outOfDate; /* FALSE if all targets up to date */
/*
@@ -940,10 +938,10 @@ runTargets(void)
* we consult the parsing module to find the main target(s)
* to create.
*/
- if (Lst_IsEmpty(opts.create))
- targs = Parse_MainName();
+ if (Lst_IsEmpty(&opts.create))
+ Parse_MainName(&targs);
else
- targs = Targ_FindList(opts.create);
+ Targ_FindList(&targs, &opts.create);
if (!opts.compatMake) {
/*
@@ -959,16 +957,16 @@ runTargets(void)
}
/* Traverse the graph, checking on all the targets */
- outOfDate = Make_Run(targs);
+ outOfDate = Make_Run(&targs);
} else {
/*
* Compat_Init will take care of creating all the
* targets as well as initializing the module.
*/
- Compat_Run(targs);
+ Compat_Run(&targs);
outOfDate = FALSE;
}
- Lst_Free(targs);
+ Lst_Done(&targs); /* Don't free the nodes. */
return outOfDate;
}
@@ -982,12 +980,12 @@ InitVarTargets(void)
{
StringListNode *ln;
- if (Lst_IsEmpty(opts.create)) {
+ if (Lst_IsEmpty(&opts.create)) {
Var_Set(".TARGETS", "", VAR_GLOBAL);
return;
}
- for (ln = opts.create->first; ln != NULL; ln = ln->next) {
+ for (ln = opts.create.first; ln != NULL; ln = ln->next) {
char *name = ln->datum;
Var_Append(".TARGETS", name, VAR_GLOBAL);
}
@@ -1040,11 +1038,11 @@ InitVarMachineArch(void)
const int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
size_t len = sizeof machine_arch_buf;
- if (sysctl(mib, __arraycount(mib), machine_arch_buf,
- &len, NULL, 0) < 0) {
- (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname,
- strerror(errno));
- exit(2);
+ if (sysctl(mib, (unsigned int)__arraycount(mib),
+ machine_arch_buf, &len, NULL, 0) < 0) {
+ (void)fprintf(stderr, "%s: sysctl failed (%s).\n",
+ progname, strerror(errno));
+ exit(2);
}
return machine_arch_buf;
@@ -1077,21 +1075,20 @@ static void
HandlePWD(const struct stat *curdir_st)
{
char *pwd;
- void *prefix_freeIt, *makeobjdir_freeIt;
- const char *makeobjdir;
+ FStr prefix, makeobjdir;
struct stat pwd_st;
if (ignorePWD || (pwd = getenv("PWD")) == NULL)
return;
- if (Var_Value("MAKEOBJDIRPREFIX", VAR_CMDLINE, &prefix_freeIt) !=
- NULL) {
- bmake_free(prefix_freeIt);
+ prefix = Var_Value("MAKEOBJDIRPREFIX", VAR_CMDLINE);
+ if (prefix.str != NULL) {
+ FStr_Done(&prefix);
return;
}
- makeobjdir = Var_Value("MAKEOBJDIR", VAR_CMDLINE, &makeobjdir_freeIt);
- if (makeobjdir != NULL && strchr(makeobjdir, '$') != NULL)
+ makeobjdir = Var_Value("MAKEOBJDIR", VAR_CMDLINE);
+ if (makeobjdir.str != NULL && strchr(makeobjdir.str, '$') != NULL)
goto ignore_pwd;
if (stat(pwd, &pwd_st) == 0 &&
@@ -1100,7 +1097,7 @@ HandlePWD(const struct stat *curdir_st)
(void)strncpy(curdir, pwd, MAXPATHLEN);
ignore_pwd:
- bmake_free(makeobjdir_freeIt);
+ FStr_Done(&makeobjdir);
}
#endif
@@ -1118,7 +1115,7 @@ InitObjdir(const char *machine, const char *machine_arch)
{
Boolean writable;
- Dir_InitDir(curdir);
+ Dir_InitCur(curdir);
writable = GetBooleanVar("MAKE_OBJDIR_CHECK_WRITABLE", TRUE);
(void)Main_SetObjdir(FALSE, "%s", curdir);
@@ -1147,35 +1144,37 @@ UnlimitFiles(void)
static void
CmdOpts_Init(void)
{
- opts.compatMake = FALSE; /* No compat mode */
- opts.debug = 0; /* No debug verbosity, please. */
+ opts.compatMake = FALSE;
+ opts.debug = DEBUG_NONE;
/* opts.debug_file has been initialized earlier */
- opts.lint = FALSE;
+ opts.strict = FALSE;
opts.debugVflag = FALSE;
opts.checkEnvFirst = FALSE;
- opts.makefiles = Lst_New();
+ Lst_Init(&opts.makefiles);
opts.ignoreErrors = FALSE; /* Pay attention to non-zero returns */
- opts.maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */
+ opts.maxJobs = 1;
opts.keepgoing = FALSE; /* Stop on error */
opts.noRecursiveExecute = FALSE; /* Execute all .MAKE targets */
opts.noExecute = FALSE; /* Execute all commands */
- opts.queryFlag = FALSE; /* This is not just a check-run */
+ opts.queryFlag = FALSE;
opts.noBuiltins = FALSE; /* Read the built-in rules */
opts.beSilent = FALSE; /* Print commands as executed */
- opts.touchFlag = FALSE; /* Actually update targets */
+ opts.touchFlag = FALSE;
opts.printVars = PVM_NONE;
- opts.variables = Lst_New();
+ Lst_Init(&opts.variables);
opts.parseWarnFatal = FALSE;
opts.enterFlag = FALSE;
opts.varNoExportEnv = FALSE;
- opts.create = Lst_New();
+ Lst_Init(&opts.create);
}
-/* Initialize MAKE and .MAKE to the path of the executable, so that it can be
+/*
+ * Initialize MAKE and .MAKE to the path of the executable, so that it can be
* found by execvp(3) and the shells, even after a chdir.
*
* If it's a relative path and contains a '/', resolve it to an absolute path.
- * Otherwise keep it as is, assuming it will be found in the PATH. */
+ * Otherwise keep it as is, assuming it will be found in the PATH.
+ */
static void
InitVarMake(const char *argv0)
{
@@ -1183,18 +1182,21 @@ InitVarMake(const char *argv0)
if (argv0[0] != '/' && strchr(argv0, '/') != NULL) {
char pathbuf[MAXPATHLEN];
- const char *abs = cached_realpath(argv0, pathbuf);
+ const char *abspath = cached_realpath(argv0, pathbuf);
struct stat st;
- if (abs != NULL && abs[0] == '/' && stat(make, &st) == 0)
- make = abs;
+ if (abspath != NULL && abspath[0] == '/' &&
+ stat(make, &st) == 0)
+ make = abspath;
}
Var_Set("MAKE", make, VAR_GLOBAL);
Var_Set(".MAKE", make, VAR_GLOBAL);
}
-/* Add the directories from the colon-separated syspath to defSysIncPath.
- * After returning, the contents of syspath is unspecified. */
+/*
+ * Add the directories from the colon-separated syspath to defSysIncPath.
+ * After returning, the contents of syspath is unspecified.
+ */
static void
InitDefSysIncPath(char *syspath)
{
@@ -1237,25 +1239,25 @@ static void
ReadBuiltinRules(void)
{
StringListNode *ln;
- StringList *sysMkPath = Lst_New();
+ StringList sysMkPath = LST_INIT;
Dir_Expand(_PATH_DEFSYSMK,
Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath,
- sysMkPath);
- if (Lst_IsEmpty(sysMkPath))
+ &sysMkPath);
+ if (Lst_IsEmpty(&sysMkPath))
Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK);
- for (ln = sysMkPath->first; ln != NULL; ln = ln->next)
+ for (ln = sysMkPath.first; ln != NULL; ln = ln->next)
if (ReadMakefile(ln->datum) == 0)
break;
if (ln == NULL)
Fatal("%s: cannot open %s.",
- progname, (const char *)sysMkPath->first->datum);
+ progname, (const char *)sysMkPath.first->datum);
/* Free the list but not the actual filenames since these may still
* be used in GNodes. */
- Lst_Free(sysMkPath);
+ Lst_Done(&sysMkPath);
}
static void
@@ -1276,7 +1278,7 @@ InitMaxJobs(void)
"%s: illegal value for .MAKE.JOBS "
"-- must be positive integer!\n",
progname);
- exit(1);
+ exit(2); /* Not 1 so -q can distinguish error */
}
if (n != opts.maxJobs) {
@@ -1315,7 +1317,7 @@ InitVpath(void)
savec = *cp;
*cp = '\0';
/* Add directory to search path */
- (void)Dir_AddDir(dirSearchPath, path);
+ (void)Dir_AddDir(&dirSearchPath, path);
*cp = savec;
path = cp + 1;
} while (savec == ':');
@@ -1348,18 +1350,20 @@ ReadFirstDefaultMakefile(void)
* 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(&opts.makefiles, prefs);
- for (ln = opts.makefiles->first; ln != NULL; ln = ln->next)
+ for (ln = opts.makefiles.first; ln != NULL; ln = ln->next)
if (ReadMakefile(ln->datum) == 0)
break;
free(prefs);
}
-/* Initialize variables such as MAKE, MACHINE, .MAKEFLAGS.
+/*
+ * Initialize variables such as MAKE, MACHINE, .MAKEFLAGS.
* Initialize a few modules.
- * Parse the arguments from MAKEFLAGS and the command line. */
+ * Parse the arguments from MAKEFLAGS and the command line.
+ */
static void
main_Init(int argc, char **argv)
{
@@ -1380,10 +1384,7 @@ main_Init(int argc, char **argv)
InitRandom();
- if ((progname = strrchr(argv[0], '/')) != NULL)
- progname++;
- else
- progname = argv[0];
+ progname = str_basename(argv[0]);
UnlimitFiles();
@@ -1469,6 +1470,10 @@ main_Init(int argc, char **argv)
Var_Set(".MAKE.PID", tmp, VAR_GLOBAL);
snprintf(tmp, sizeof tmp, "%u", getppid());
Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL);
+ snprintf(tmp, sizeof tmp, "%u", getuid());
+ Var_Set(".MAKE.UID", tmp, VAR_GLOBAL);
+ snprintf(tmp, sizeof tmp, "%u", getgid());
+ Var_Set(".MAKE.GID", tmp, VAR_GLOBAL);
}
if (makelevel > 0) {
char pn[1024];
@@ -1545,8 +1550,10 @@ main_Init(int argc, char **argv)
InitDefSysIncPath(syspath);
}
-/* Read the system makefile followed by either makefile, Makefile or the
- * files given by the -f option. Exit on parse errors. */
+/*
+ * Read the system makefile followed by either makefile, Makefile or the
+ * files given by the -f option. Exit on parse errors.
+ */
static void
main_ReadFiles(void)
{
@@ -1554,8 +1561,8 @@ main_ReadFiles(void)
if (!opts.noBuiltins)
ReadBuiltinRules();
- if (!Lst_IsEmpty(opts.makefiles))
- ReadAllMakefiles(opts.makefiles);
+ if (!Lst_IsEmpty(&opts.makefiles))
+ ReadAllMakefiles(&opts.makefiles);
else
ReadFirstDefaultMakefile();
}
@@ -1566,8 +1573,7 @@ main_PrepareMaking(void)
{
/* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */
if (!opts.noBuiltins || opts.printVars == PVM_NONE) {
- /* ignore /dev/null and anything starting with "no" */
- (void)Var_Subst("${.MAKE.DEPENDFILE:N/dev/null:Nno*:T}",
+ (void)Var_Subst("${.MAKE.DEPENDFILE}",
VAR_CMDLINE, VARE_WANTRES, &makeDependfile);
if (makeDependfile[0] != '\0') {
/* TODO: handle errors */
@@ -1580,13 +1586,12 @@ main_PrepareMaking(void)
if (enterFlagObj)
printf("%s: Entering directory `%s'\n", progname, objdir);
- MakeMode(NULL);
+ MakeMode();
{
- void *freeIt;
- Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &freeIt),
- VAR_GLOBAL);
- bmake_free(freeIt);
+ FStr makeflags = Var_Value(MAKEFLAGS, VAR_GLOBAL);
+ Var_Append("MFLAGS", makeflags.str, VAR_GLOBAL);
+ FStr_Done(&makeflags);
}
InitMaxJobs();
@@ -1624,9 +1629,11 @@ main_PrepareMaking(void)
Targ_PrintGraph(1);
}
-/* Make the targets.
+/*
+ * Make the targets.
* If the -v or -V options are given, print variables instead.
- * Return whether any of the targets is out-of-date. */
+ * Return whether any of the targets is out-of-date.
+ */
static Boolean
main_Run(void)
{
@@ -1644,9 +1651,13 @@ static void
main_CleanUp(void)
{
#ifdef CLEANUP
- Lst_Destroy(opts.variables, free);
- Lst_Free(opts.makefiles); /* don't free, may be used in GNodes */
- Lst_Destroy(opts.create, free);
+ Lst_DoneCall(&opts.variables, free);
+ /*
+ * Don't free the actual strings from opts.makefiles, they may be
+ * used in GNodes.
+ */
+ Lst_Done(&opts.makefiles);
+ Lst_DoneCall(&opts.create, free);
#endif
/* print the graph now it's been processed if the user requested it */
@@ -1677,7 +1688,7 @@ main_CleanUp(void)
static int
main_Exit(Boolean outOfDate)
{
- if (opts.lint && (errors > 0 || Parse_GetFatals() > 0))
+ if (opts.strict && (main_errors > 0 || Parse_GetFatals() > 0))
return 2; /* Not 1 so -q can distinguish error */
return outOfDate ? 1 : 0;
}
@@ -1695,7 +1706,8 @@ main(int argc, char **argv)
return main_Exit(outOfDate);
}
-/* Open and parse the given makefile, with all its side effects.
+/*
+ * Open and parse the given makefile, with all its side effects.
*
* Results:
* 0 if ok. -1 if couldn't open file.
@@ -1776,7 +1788,7 @@ char *
Cmd_Exec(const char *cmd, const char **errfmt)
{
const char *args[4]; /* Args for invoking the shell */
- int fds[2]; /* Pipe streams */
+ int pipefds[2];
int cpid; /* Child PID */
int pid; /* PID from wait() */
int status; /* command exit status */
@@ -1789,7 +1801,7 @@ Cmd_Exec(const char *cmd, const char **errfmt)
*errfmt = NULL;
- if (!shellName)
+ if (shellName == NULL)
Shell_Init();
/*
* Set up arguments for shell
@@ -1802,27 +1814,27 @@ Cmd_Exec(const char *cmd, const char **errfmt)
/*
* Open a pipe for fetching its output
*/
- if (pipe(fds) == -1) {
+ if (pipe(pipefds) == -1) {
*errfmt = "Couldn't create pipe for \"%s\"";
goto bad;
}
+ Var_ReexportVars();
+
/*
* Fork
*/
switch (cpid = vFork()) {
case 0:
- (void)close(fds[0]); /* Close input side of pipe */
+ (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(fds[1], 1);
- (void)close(fds[1]);
-
- Var_ExportVars();
+ (void)dup2(pipefds[1], 1);
+ (void)close(pipefds[1]);
(void)execv(shellPath, UNCONST(args));
_exit(1);
@@ -1833,14 +1845,14 @@ Cmd_Exec(const char *cmd, const char **errfmt)
goto bad;
default:
- (void)close(fds[1]); /* No need for the writing half */
+ (void)close(pipefds[1]); /* No need for the writing half */
savederr = 0;
Buf_Init(&buf);
do {
char result[BUFSIZ];
- bytes_read = read(fds[0], result, sizeof result);
+ bytes_read = read(pipefds[0], result, sizeof result);
if (bytes_read > 0)
Buf_AddBytes(&buf, result, (size_t)bytes_read);
} while (bytes_read > 0 ||
@@ -1848,8 +1860,7 @@ Cmd_Exec(const char *cmd, const char **errfmt)
if (bytes_read == -1)
savederr = errno;
- (void)close(
- fds[0]); /* Close the input side of the pipe. */
+ (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)
@@ -1879,10 +1890,12 @@ bad:
return bmake_strdup("");
}
-/* Print a printf-style error message.
+/*
+ * 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. */
+ * does not affect the exit status. Only in lint mode (-dL) it does.
+ */
void
Error(const char *fmt, ...)
{
@@ -1904,14 +1917,16 @@ Error(const char *fmt, ...)
break;
err_file = stderr;
}
- errors++;
+ main_errors++;
}
-/* Wait for any running jobs to finish, then produce an error message,
+/*
+ * Wait for any running jobs to finish, then produce an error message,
* finally exit immediately.
*
* Exiting immediately differs from Parse_Error, which exits only after the
- * current top-level makefile has been parsed completely. */
+ * current top-level makefile has been parsed completely.
+ */
void
Fatal(const char *fmt, ...)
{
@@ -1935,8 +1950,10 @@ Fatal(const char *fmt, ...)
exit(2); /* Not 1 so -q can distinguish error */
}
-/* Major exception once jobs are being created.
- * Kills all jobs, prints a message and exits. */
+/*
+ * Major exception once jobs are being created.
+ * Kills all jobs, prints a message and exits.
+ */
void
Punt(const char *fmt, ...)
{
@@ -1964,12 +1981,14 @@ DieHorribly(void)
if (DEBUG(GRAPH2))
Targ_PrintGraph(2);
Trace_Log(MAKEERROR, NULL);
- exit(2); /* Not 1, so -q can distinguish error */
+ exit(2); /* Not 1 so -q can distinguish error */
}
-/* Called when aborting due to errors in child shell to signal abnormal exit.
+/*
+ * Called when aborting due to errors in child shell to signal abnormal exit.
* The program exits.
- * Errors is the number of errors encountered in Make_Make. */
+ * Errors is the number of errors encountered in Make_Make.
+ */
void
Finish(int errs)
{
@@ -2101,7 +2120,7 @@ shouldDieQuietly(GNode *gn, int bf)
else if (bf >= 0)
quietly = bf;
else
- quietly = gn != NULL && (gn->type & OP_MAKE);
+ quietly = (gn != NULL && (gn->type & OP_MAKE)) ? 1 : 0;
}
return quietly;
}
@@ -2117,7 +2136,7 @@ SetErrorVars(GNode *gn)
Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL);
Var_Delete(".ERROR_CMD", VAR_GLOBAL);
- for (ln = gn->commands->first; ln != NULL; ln = ln->next) {
+ for (ln = gn->commands.first; ln != NULL; ln = ln->next) {
const char *cmd = ln->datum;
if (cmd == NULL)
@@ -2126,8 +2145,10 @@ SetErrorVars(GNode *gn)
}
}
-/* Print some helpful information in case of an error.
- * The caller should exit soon after calling this function. */
+/*
+ * Print some helpful information in case of an error.
+ * The caller should exit soon after calling this function.
+ */
void
PrintOnError(GNode *gn, const char *msg)
{
@@ -2138,16 +2159,16 @@ PrintOnError(GNode *gn, const char *msg)
Var_Stats();
}
- /* we generally want to keep quiet if a sub-make died */
- if (shouldDieQuietly(gn, -1))
- return;
+ if (errorNode != NULL)
+ return; /* we've been here! */
if (msg != NULL)
printf("%s", msg);
printf("\n%s: stopped in %s\n", progname, curdir);
- if (errorNode != NULL)
- return; /* we've been here! */
+ /* we generally want to keep quiet if a sub-make died */
+ if (shouldDieQuietly(gn, -1))
+ return;
if (gn != NULL)
SetErrorVars(gn);
@@ -2241,11 +2262,10 @@ mkTempFile(const char *pattern, char **out_fname)
if ((fd = mkstemp(tfile)) < 0)
Punt("Could not create temporary file %s: %s", tfile,
strerror(errno));
- if (out_fname) {
+ if (out_fname != NULL) {
*out_fname = bmake_strdup(tfile);
} else {
- unlink(
- tfile); /* we just want the descriptor */
+ unlink(tfile); /* we just want the descriptor */
}
return fd;
}
diff --git a/make-conf.h b/make-conf.h
index bc3b9e7e4915..dcf5b3162dea 100644
--- a/make-conf.h
+++ b/make-conf.h
@@ -1,4 +1,4 @@
-/* $NetBSD: config.h,v 1.25 2020/10/19 23:43:55 rillig Exp $ */
+/* $NetBSD: config.h,v 1.28 2020/12/11 22:53:08 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -73,19 +73,6 @@
*/
/*
- * DEFMAXJOBS
- * DEFMAXLOCAL
- * These control the default concurrency. On no occasion will more
- * than DEFMAXJOBS targets be created at once (locally or remotely).
- *
- * DEFMAXLOCAL is the highest number of targets which will be
- * created on the local machine at once. Note that if you set this
- * to 0, nothing will ever happen.
- */
-#define DEFMAXJOBS 4
-#define DEFMAXLOCAL 1
-
-/*
* INCLUDES
* LIBRARIES
* These control the handling of the .INCLUDES and .LIBS variables.
@@ -104,7 +91,7 @@
* Is the suffix used to denote libraries and is used by the Suff module
* to find the search path on which to seek any -l<xx> targets.
*/
-#define LIBSUFF ".a"
+#define LIBSUFF ".a"
/*
* RECHECK
@@ -119,14 +106,13 @@
* On systems that don't have this problem, you should define this.
* Under NFS you probably should not, unless you aren't exporting jobs.
*/
-#define RECHECK
+#define RECHECK
/*
* POSIX
* Adhere to the POSIX 1003.2 draft for the make(1) program.
* - Use MAKEFLAGS instead of MAKE to pick arguments from the
* environment.
- * - Allow empty command lines if starting with tab.
*/
#define POSIX
diff --git a/make.1 b/make.1
index a18c44442b49..c711b38a9731 100644
--- a/make.1
+++ b/make.1
@@ -1,4 +1,4 @@
-.\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig Exp $
+.\" $NetBSD: make.1,v 1.295 2020/12/23 13:49:12 rillig Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
-.Dd November 14, 2020
+.Dd December 22, 2020
.Dt MAKE 1
.Os
.Sh NAME
@@ -1019,6 +1019,12 @@ If set to false,
becomes
.Ql $
per normal evaluation rules.
+.It Va .MAKE.UID
+The user-id running
+.Nm .
+.It Va .MAKE.GID
+The group-id running
+.Nm .
.It Va MAKE_PRINT_VAR_ON_ERROR
When
.Nm
@@ -1097,7 +1103,7 @@ to that directory before executing any targets.
.Pp
Except in the case of an explicit
.Ql Ic .OBJDIR
-target,
+target,
.Nm
will check that the specified directory is writable and ignore it if not.
This check can be skipped by setting the environment variable
@@ -1743,9 +1749,9 @@ The same as
except that variables in the value are not expanded.
.It Ic .info Ar message
The message is printed along with the name of the makefile and line number.
-.It Ic .undef Ar variable
-Un-define the specified global variable.
-Only global variables may be un-defined.
+.It Ic .undef Ar variable ...
+Un-define the specified global variables.
+Only global variables can be un-defined.
.It Ic .unexport Ar variable ...
The opposite of
.Ql .export .
diff --git a/make.c b/make.c
index d0502e80c8ef..7745e038c814 100644
--- a/make.c
+++ b/make.c
@@ -1,4 +1,4 @@
-/* $NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig Exp $ */
+/* $NetBSD: make.c,v 1.234 2021/01/10 21:20:46 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -68,7 +68,8 @@
* SUCH DAMAGE.
*/
-/* Examination of targets and their suitability for creation.
+/*
+ * Examination of targets and their suitability for creation.
*
* Interface:
* Make_Run Initialize things for the module. Returns TRUE if
@@ -102,117 +103,122 @@
#include "job.h"
/* "@(#)make.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig Exp $");
+MAKE_RCSID("$NetBSD: make.c,v 1.234 2021/01/10 21:20:46 rillig Exp $");
/* Sequence # to detect recursion. */
static unsigned int checked_seqno = 1;
-/* The current fringe of the graph.
+/*
+ * The current fringe of the graph.
* These are nodes which await examination by MakeOODate.
- * It is added to by Make_Update and subtracted from by MakeStartJobs */
-static GNodeList *toBeMade;
+ * It is added to by Make_Update and subtracted from by MakeStartJobs
+ */
+static GNodeList toBeMade = LST_INIT;
-static int MakeBuildParent(void *, void *);
void
debug_printf(const char *fmt, ...)
{
- va_list args;
+ va_list args;
- va_start(args, fmt);
- vfprintf(opts.debug_file, fmt, args);
- va_end(args);
+ va_start(args, fmt);
+ vfprintf(opts.debug_file, fmt, args);
+ va_end(args);
}
MAKE_ATTR_DEAD static void
-make_abort(GNode *gn, int line)
+make_abort(GNode *gn, int lineno)
{
- debug_printf("make_abort from line %d\n", line);
- Targ_PrintNode(gn, 2);
- Targ_PrintNodes(toBeMade, 2);
- Targ_PrintGraph(3);
- abort();
+
+ debug_printf("make_abort from line %d\n", lineno);
+ Targ_PrintNode(gn, 2);
+ Targ_PrintNodes(&toBeMade, 2);
+ Targ_PrintGraph(3);
+ abort();
}
ENUM_VALUE_RTTI_8(GNodeMade,
- UNMADE, DEFERRED, REQUESTED, BEINGMADE,
- MADE, UPTODATE, ERROR, ABORTED);
+ UNMADE, DEFERRED, REQUESTED, BEINGMADE,
+ MADE, UPTODATE, ERROR, ABORTED);
ENUM_FLAGS_RTTI_31(GNodeType,
- OP_DEPENDS, OP_FORCE, OP_DOUBLEDEP,
- /* OP_OPMASK is omitted since it combines other flags */
- OP_OPTIONAL, OP_USE, OP_EXEC, OP_IGNORE,
- OP_PRECIOUS, OP_SILENT, OP_MAKE, OP_JOIN,
- OP_MADE, OP_SPECIAL, OP_USEBEFORE, OP_INVISIBLE,
- OP_NOTMAIN, OP_PHONY, OP_NOPATH, OP_WAIT,
- OP_NOMETA, OP_META, OP_NOMETA_CMP, OP_SUBMAKE,
- OP_TRANSFORM, OP_MEMBER, OP_LIB, OP_ARCHV,
- OP_HAS_COMMANDS, OP_SAVE_CMDS, OP_DEPS_FOUND, OP_MARK);
+ OP_DEPENDS, OP_FORCE, OP_DOUBLEDEP,
+/* OP_OPMASK is omitted since it combines other flags */
+ OP_OPTIONAL, OP_USE, OP_EXEC, OP_IGNORE,
+ OP_PRECIOUS, OP_SILENT, OP_MAKE, OP_JOIN,
+ OP_MADE, OP_SPECIAL, OP_USEBEFORE, OP_INVISIBLE,
+ OP_NOTMAIN, OP_PHONY, OP_NOPATH, OP_WAIT,
+ OP_NOMETA, OP_META, OP_NOMETA_CMP, OP_SUBMAKE,
+ OP_TRANSFORM, OP_MEMBER, OP_LIB, OP_ARCHV,
+ OP_HAS_COMMANDS, OP_SAVE_CMDS, OP_DEPS_FOUND, OP_MARK);
ENUM_FLAGS_RTTI_10(GNodeFlags,
- REMAKE, CHILDMADE, FORCE, DONE_WAIT,
- DONE_ORDER, FROM_DEPEND, DONE_ALLSRC, CYCLE,
- DONECYCLE, INTERNAL);
+ REMAKE, CHILDMADE, FORCE, DONE_WAIT,
+ DONE_ORDER, FROM_DEPEND, DONE_ALLSRC, CYCLE,
+ DONECYCLE, INTERNAL);
void
GNode_FprintDetails(FILE *f, const char *prefix, const GNode *gn,
const char *suffix)
{
- char type_buf[GNodeType_ToStringSize];
- char flags_buf[GNodeFlags_ToStringSize];
+ char type_buf[GNodeType_ToStringSize];
+ char flags_buf[GNodeFlags_ToStringSize];
- fprintf(f, "%smade %s, type %s, flags %s%s",
+ fprintf(f, "%smade %s, type %s, flags %s%s",
prefix,
Enum_ValueToString(gn->made, GNodeMade_ToStringSpecs),
Enum_FlagsToString(type_buf, sizeof type_buf,
- gn->type, GNodeType_ToStringSpecs),
+ gn->type, GNodeType_ToStringSpecs),
Enum_FlagsToString(flags_buf, sizeof flags_buf,
- gn->flags, GNodeFlags_ToStringSpecs),
+ gn->flags, GNodeFlags_ToStringSpecs),
suffix);
}
Boolean
GNode_ShouldExecute(GNode *gn)
{
- return !((gn->type & OP_MAKE) ? opts.noRecursiveExecute : opts.noExecute);
+ return !((gn->type & OP_MAKE)
+ ? opts.noRecursiveExecute
+ : opts.noExecute);
}
/* Update the youngest child of the node, according to the given child. */
void
GNode_UpdateYoungestChild(GNode *gn, GNode *cgn)
{
- if (gn->youngestChild == NULL || cgn->mtime > gn->youngestChild->mtime)
- gn->youngestChild = cgn;
+ if (gn->youngestChild == NULL || cgn->mtime > gn->youngestChild->mtime)
+ gn->youngestChild = cgn;
}
static Boolean
IsOODateRegular(GNode *gn)
{
- /* These rules are inherited from the original Make. */
-
- if (gn->youngestChild != NULL) {
- if (gn->mtime < gn->youngestChild->mtime) {
- DEBUG1(MAKE, "modified before source \"%s\"...",
- GNode_Path(gn->youngestChild));
- return TRUE;
+ /* These rules are inherited from the original Make. */
+
+ if (gn->youngestChild != NULL) {
+ if (gn->mtime < gn->youngestChild->mtime) {
+ DEBUG1(MAKE, "modified before source \"%s\"...",
+ GNode_Path(gn->youngestChild));
+ return TRUE;
+ }
+ return FALSE;
}
- return FALSE;
- }
- if (gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) {
- DEBUG0(MAKE, "non-existent and no sources...");
- return TRUE;
- }
+ if (gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) {
+ DEBUG0(MAKE, "nonexistent and no sources...");
+ return TRUE;
+ }
- if (gn->type & OP_DOUBLEDEP) {
- DEBUG0(MAKE, ":: operator and no sources...");
- return TRUE;
- }
+ if (gn->type & OP_DOUBLEDEP) {
+ DEBUG0(MAKE, ":: operator and no sources...");
+ return TRUE;
+ }
- return FALSE;
+ return FALSE;
}
-/* See if the node is out of date with respect to its sources.
+/*
+ * See if the node is out of date with respect to its sources.
*
* Used by Make_Run when deciding which nodes to place on the
* toBeMade queue initially and by Make_Update to screen out .USE and
@@ -226,129 +232,139 @@ IsOODateRegular(GNode *gn)
Boolean
GNode_IsOODate(GNode *gn)
{
- Boolean oodate;
-
- /*
- * Certain types of targets needn't even be sought as their datedness
- * doesn't depend on their modification time...
- */
- if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
- Dir_UpdateMTime(gn, TRUE);
- if (DEBUG(MAKE)) {
- if (gn->mtime != 0)
- debug_printf("modified %s...", Targ_FmtTime(gn->mtime));
- else
- debug_printf("non-existent...");
- }
- }
-
- /*
- * A target is remade in one of the following circumstances:
- * its modification time is smaller than that of its youngest child and
- * it would actually be run (has commands or is not GNode_IsTarget)
- * it's the object of a force operator
- * it has no children, was on the lhs of an operator and doesn't exist
- * already.
- *
- * Libraries are only considered out-of-date if the archive module says
- * they are.
- *
- * These weird rules are brought to you by Backward-Compatibility and
- * the strange people who wrote 'Make'.
- */
- if (gn->type & (OP_USE|OP_USEBEFORE)) {
- /*
- * If the node is a USE node it is *never* out of date
- * no matter *what*.
- */
- DEBUG0(MAKE, ".USE node...");
- oodate = FALSE;
- } else if ((gn->type & OP_LIB) && (gn->mtime == 0 || Arch_IsLib(gn))) {
- DEBUG0(MAKE, "library...");
+ Boolean oodate;
/*
- * always out of date if no children and :: target
- * or non-existent.
+ * Certain types of targets needn't even be sought as their datedness
+ * doesn't depend on their modification time...
*/
- oodate = (gn->mtime == 0 || Arch_LibOODate(gn) ||
- (gn->youngestChild == NULL && (gn->type & OP_DOUBLEDEP)));
- } else if (gn->type & OP_JOIN) {
- /*
- * A target with the .JOIN attribute is only considered
- * out-of-date if any of its children was out-of-date.
- */
- DEBUG0(MAKE, ".JOIN node...");
- DEBUG1(MAKE, "source %smade...", gn->flags & CHILDMADE ? "" : "not ");
- oodate = (gn->flags & CHILDMADE) != 0;
- } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) {
- /*
- * A node which is the object of the force (!) operator or which has
- * the .EXEC attribute is always considered out-of-date.
- */
- if (DEBUG(MAKE)) {
- if (gn->type & OP_FORCE) {
- debug_printf("! operator...");
- } else if (gn->type & OP_PHONY) {
- debug_printf(".PHONY node...");
- } else {
- debug_printf(".EXEC node...");
- }
+ if (!(gn->type & (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC))) {
+ Dir_UpdateMTime(gn, TRUE);
+ if (DEBUG(MAKE)) {
+ if (gn->mtime != 0)
+ debug_printf("modified %s...",
+ Targ_FmtTime(gn->mtime));
+ else
+ debug_printf("nonexistent...");
+ }
}
- oodate = TRUE;
- } else if (IsOODateRegular(gn)) {
- oodate = TRUE;
- } else {
+
/*
- * When a non-existing child with no sources
- * (such as a typically used FORCE source) has been made and
- * the target of the child (usually a directory) has the same
- * timestamp as the timestamp just given to the non-existing child
- * after it was considered made.
+ * A target is remade in one of the following circumstances:
+ *
+ * its modification time is smaller than that of its youngest
+ * child and it would actually be run (has commands or is not
+ * GNode_IsTarget)
+ *
+ * it's the object of a force operator
+ *
+ * it has no children, was on the lhs of an operator and doesn't
+ * exist already.
+ *
+ * Libraries are only considered out-of-date if the archive module
+ * says they are.
+ *
+ * These weird rules are brought to you by Backward-Compatibility
+ * and the strange people who wrote 'Make'.
*/
- if (DEBUG(MAKE)) {
- if (gn->flags & FORCE)
- debug_printf("non existing child...");
+ if (gn->type & (OP_USE | OP_USEBEFORE)) {
+ /*
+ * If the node is a USE node it is *never* out of date
+ * no matter *what*.
+ */
+ DEBUG0(MAKE, ".USE node...");
+ oodate = FALSE;
+ } else if ((gn->type & OP_LIB) && (gn->mtime == 0 || Arch_IsLib(gn))) {
+ DEBUG0(MAKE, "library...");
+
+ /*
+ * always out of date if no children and :: target
+ * or nonexistent.
+ */
+ oodate = (gn->mtime == 0 || Arch_LibOODate(gn) ||
+ (gn->youngestChild == NULL &&
+ (gn->type & OP_DOUBLEDEP)));
+ } else if (gn->type & OP_JOIN) {
+ /*
+ * A target with the .JOIN attribute is only considered
+ * out-of-date if any of its children was out-of-date.
+ */
+ DEBUG0(MAKE, ".JOIN node...");
+ DEBUG1(MAKE, "source %smade...",
+ gn->flags & CHILDMADE ? "" : "not ");
+ oodate = (gn->flags & CHILDMADE) != 0;
+ } else if (gn->type & (OP_FORCE | OP_EXEC | OP_PHONY)) {
+ /*
+ * A node which is the object of the force (!) operator or
+ * which has the .EXEC attribute is always considered
+ * out-of-date.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->type & OP_FORCE) {
+ debug_printf("! operator...");
+ } else if (gn->type & OP_PHONY) {
+ debug_printf(".PHONY node...");
+ } else {
+ debug_printf(".EXEC node...");
+ }
+ }
+ oodate = TRUE;
+ } else if (IsOODateRegular(gn)) {
+ oodate = TRUE;
+ } else {
+ /*
+ * When a nonexistent child with no sources
+ * (such as a typically used FORCE source) has been made and
+ * the target of the child (usually a directory) has the same
+ * timestamp as the timestamp just given to the nonexistent
+ * child after it was considered made.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->flags & FORCE)
+ debug_printf("non existing child...");
+ }
+ oodate = (gn->flags & FORCE) != 0;
}
- oodate = (gn->flags & FORCE) != 0;
- }
#ifdef USE_META
- if (useMeta) {
- oodate = meta_oodate(gn, oodate);
- }
+ if (useMeta) {
+ oodate = meta_oodate(gn, oodate);
+ }
#endif
- /*
- * If the target isn't out-of-date, the parents need to know its
- * modification time. Note that targets that appear to be out-of-date
- * but aren't, because they have no commands and are GNode_IsTarget,
- * have their mtime stay below their children's mtime to keep parents from
- * thinking they're out-of-date.
- */
- if (!oodate) {
- GNodeListNode *ln;
- for (ln = gn->parents->first; ln != NULL; ln = ln->next)
- GNode_UpdateYoungestChild(ln->datum, gn);
- }
+ /*
+ * If the target isn't out-of-date, the parents need to know its
+ * modification time. Note that targets that appear to be out-of-date
+ * but aren't, because they have no commands and are GNode_IsTarget,
+ * have their mtime stay below their children's mtime to keep parents
+ * from thinking they're out-of-date.
+ */
+ if (!oodate) {
+ GNodeListNode *ln;
+ for (ln = gn->parents.first; ln != NULL; ln = ln->next)
+ GNode_UpdateYoungestChild(ln->datum, gn);
+ }
- return oodate;
+ return oodate;
}
static void
PretendAllChildrenAreMade(GNode *pgn)
{
- GNodeListNode *ln;
+ GNodeListNode *ln;
- for (ln = pgn->children->first; ln != NULL; ln = ln->next) {
- GNode *cgn = ln->datum;
+ for (ln = pgn->children.first; ln != NULL; ln = ln->next) {
+ GNode *cgn = ln->datum;
- Dir_UpdateMTime(cgn, FALSE); /* cgn->path may get updated as well */
- GNode_UpdateYoungestChild(pgn, cgn);
- pgn->unmade--;
- }
+ /* This may also update cgn->path. */
+ Dir_UpdateMTime(cgn, FALSE);
+ GNode_UpdateYoungestChild(pgn, cgn);
+ pgn->unmade--;
+ }
}
-/* Called by Make_Run and SuffApplyTransform on the downward pass to handle
+/*
+ * Called by Make_Run and SuffApplyTransform on the downward pass to handle
* .USE and transformation nodes, by copying the child node's commands, type
* flags and children to the parent node.
*
@@ -364,57 +380,62 @@ PretendAllChildrenAreMade(GNode *pgn)
void
Make_HandleUse(GNode *cgn, GNode *pgn)
{
- GNodeListNode *ln; /* An element in the children list */
+ GNodeListNode *ln; /* An element in the children list */
#ifdef DEBUG_SRC
- if (!(cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM))) {
- debug_printf("Make_HandleUse: called for plain node %s\n", cgn->name);
- return; /* XXX: debug mode should not affect control flow */
- }
+ if (!(cgn->type & (OP_USE | OP_USEBEFORE | OP_TRANSFORM))) {
+ debug_printf("Make_HandleUse: called for plain node %s\n",
+ cgn->name);
+ /* XXX: debug mode should not affect control flow */
+ return;
+ }
#endif
- if ((cgn->type & (OP_USE|OP_USEBEFORE)) || Lst_IsEmpty(pgn->commands)) {
- if (cgn->type & OP_USEBEFORE) {
- /* .USEBEFORE */
- Lst_PrependAll(pgn->commands, cgn->commands);
- } else {
- /* .USE, or target has no commands */
- Lst_AppendAll(pgn->commands, cgn->commands);
- }
- }
+ if ((cgn->type & (OP_USE | OP_USEBEFORE)) ||
+ Lst_IsEmpty(&pgn->commands)) {
+ if (cgn->type & OP_USEBEFORE) {
+ /* .USEBEFORE */
+ Lst_PrependAll(&pgn->commands, &cgn->commands);
+ } else {
+ /* .USE, or target has no commands */
+ Lst_AppendAll(&pgn->commands, &cgn->commands);
+ }
+ }
- for (ln = cgn->children->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
+ for (ln = cgn->children.first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
- /*
- * Expand variables in the .USE node's name
- * and save the unexpanded form.
- * We don't need to do this for commands.
- * They get expanded properly when we execute.
- */
- if (gn->uname == NULL) {
- gn->uname = gn->name;
- } else {
- free(gn->name);
- }
- (void)Var_Subst(gn->uname, pgn, VARE_WANTRES, &gn->name);
- /* TODO: handle errors */
- if (gn->uname && strcmp(gn->name, gn->uname) != 0) {
- /* See if we have a target for this node. */
- GNode *tgn = Targ_FindNode(gn->name);
- if (tgn != NULL)
- gn = tgn;
+ /*
+ * Expand variables in the .USE node's name
+ * and save the unexpanded form.
+ * We don't need to do this for commands.
+ * They get expanded properly when we execute.
+ */
+ if (gn->uname == NULL) {
+ gn->uname = gn->name;
+ } else {
+ free(gn->name);
+ }
+ (void)Var_Subst(gn->uname, pgn, VARE_WANTRES, &gn->name);
+ /* TODO: handle errors */
+ if (gn->uname != NULL && strcmp(gn->name, gn->uname) != 0) {
+ /* See if we have a target for this node. */
+ GNode *tgn = Targ_FindNode(gn->name);
+ if (tgn != NULL)
+ gn = tgn;
+ }
+
+ Lst_Append(&pgn->children, gn);
+ Lst_Append(&gn->parents, pgn);
+ pgn->unmade++;
}
- Lst_Append(pgn->children, gn);
- Lst_Append(gn->parents, pgn);
- pgn->unmade++;
- }
-
- pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_USEBEFORE|OP_TRANSFORM);
+ pgn->type |=
+ cgn->type & ~(OP_OPMASK | OP_USE | OP_USEBEFORE | OP_TRANSFORM);
}
-/* Used by Make_Run on the downward pass to handle .USE nodes. Should be
+/*
+ * Used by Make_Run on the downward pass to handle .USE nodes. Should be
* called before the children are enqueued to be looked at by MakeAddChild.
*
* For a .USE child, the commands, type flags and children are copied to the
@@ -428,113 +449,116 @@ Make_HandleUse(GNode *cgn, GNode *pgn)
static void
MakeHandleUse(GNode *cgn, GNode *pgn, GNodeListNode *ln)
{
- Boolean unmarked;
-
- unmarked = !(cgn->type & OP_MARK);
- cgn->type |= OP_MARK;
-
- if (!(cgn->type & (OP_USE|OP_USEBEFORE)))
- return;
-
- if (unmarked)
- Make_HandleUse(cgn, pgn);
-
- /*
- * This child node is now "made", so we decrement the count of
- * unmade children in the parent... We also remove the child
- * from the parent's list to accurately reflect the number of decent
- * children the parent has. This is used by Make_Run to decide
- * whether to queue the parent or examine its children...
- */
- Lst_Remove(pgn->children, ln);
- pgn->unmade--;
+ Boolean unmarked;
+
+ unmarked = !(cgn->type & OP_MARK);
+ cgn->type |= OP_MARK;
+
+ if (!(cgn->type & (OP_USE | OP_USEBEFORE)))
+ return;
+
+ if (unmarked)
+ Make_HandleUse(cgn, pgn);
+
+ /*
+ * This child node is now "made", so we decrement the count of
+ * unmade children in the parent... We also remove the child
+ * from the parent's list to accurately reflect the number of decent
+ * children the parent has. This is used by Make_Run to decide
+ * whether to queue the parent or examine its children...
+ */
+ Lst_Remove(&pgn->children, ln);
+ pgn->unmade--;
}
static void
HandleUseNodes(GNode *gn)
{
- GNodeListNode *ln, *nln;
- for (ln = gn->children->first; ln != NULL; ln = nln) {
- nln = ln->next;
- MakeHandleUse(ln->datum, gn, ln);
- }
+ GNodeListNode *ln, *nln;
+ for (ln = gn->children.first; ln != NULL; ln = nln) {
+ nln = ln->next;
+ MakeHandleUse(ln->datum, gn, ln);
+ }
}
-/* Check the modification time of a gnode, and update it if necessary.
- * Return 0 if the gnode does not exist, or its filesystem time if it does. */
+/*
+ * Check the modification time of a gnode, and update it if necessary.
+ * Return 0 if the gnode does not exist, or its filesystem time if it does.
+ */
time_t
Make_Recheck(GNode *gn)
{
- time_t mtime;
+ time_t mtime;
- Dir_UpdateMTime(gn, TRUE);
- mtime = gn->mtime;
+ Dir_UpdateMTime(gn, TRUE);
+ mtime = gn->mtime;
#ifndef RECHECK
- /*
- * We can't re-stat the thing, but we can at least take care of rules
- * where a target depends on a source that actually creates the
- * target, but only if it has changed, e.g.
- *
- * parse.h : parse.o
- *
- * parse.o : parse.y
- * yacc -d parse.y
- * cc -c y.tab.c
- * mv y.tab.o parse.o
- * cmp -s y.tab.h parse.h || mv y.tab.h parse.h
- *
- * In this case, if the definitions produced by yacc haven't changed
- * from before, parse.h won't have been updated and gn->mtime will
- * reflect the current modification time for parse.h. This is
- * something of a kludge, I admit, but it's a useful one.
- *
- * XXX: People like to use a rule like "FRC:" to force things that
- * depend on FRC to be made, so we have to check for gn->children
- * being empty as well.
- */
- if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
- gn->mtime = now;
- }
+ /*
+ * We can't re-stat the thing, but we can at least take care of rules
+ * where a target depends on a source that actually creates the
+ * target, but only if it has changed, e.g.
+ *
+ * parse.h : parse.o
+ *
+ * parse.o : parse.y
+ * yacc -d parse.y
+ * cc -c y.tab.c
+ * mv y.tab.o parse.o
+ * cmp -s y.tab.h parse.h || mv y.tab.h parse.h
+ *
+ * In this case, if the definitions produced by yacc haven't changed
+ * from before, parse.h won't have been updated and gn->mtime will
+ * reflect the current modification time for parse.h. This is
+ * something of a kludge, I admit, but it's a useful one.
+ *
+ * XXX: People like to use a rule like "FRC:" to force things that
+ * depend on FRC to be made, so we have to check for gn->children
+ * being empty as well.
+ */
+ if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
+ gn->mtime = now;
+ }
#else
- /*
- * This is what Make does and it's actually a good thing, as it
- * allows rules like
- *
- * cmp -s y.tab.h parse.h || cp y.tab.h parse.h
- *
- * to function as intended. Unfortunately, thanks to the stateless
- * nature of NFS (by which I mean the loose coupling of two clients
- * using the same file from a common server), there are times
- * when the modification time of a file created on a remote
- * machine will not be modified before the local stat() implied by
- * the Dir_UpdateMTime occurs, thus leading us to believe that the file
- * is unchanged, wreaking havoc with files that depend on this one.
- *
- * I have decided it is better to make too much than to make too
- * little, so this stuff is commented out unless you're sure it's ok.
- * -- ardeb 1/12/88
- */
- /*
- * Christos, 4/9/92: If we are saving commands, pretend that
- * the target is made now. Otherwise archives with '...' rules
- * don't work!
- */
- if (!GNode_ShouldExecute(gn) || (gn->type & OP_SAVE_CMDS) ||
+ /*
+ * This is what Make does and it's actually a good thing, as it
+ * allows rules like
+ *
+ * cmp -s y.tab.h parse.h || cp y.tab.h parse.h
+ *
+ * to function as intended. Unfortunately, thanks to the stateless
+ * nature of NFS (by which I mean the loose coupling of two clients
+ * using the same file from a common server), there are times when
+ * the modification time of a file created on a remote machine
+ * will not be modified before the local stat() implied by the
+ * Dir_UpdateMTime occurs, thus leading us to believe that the file
+ * is unchanged, wreaking havoc with files that depend on this one.
+ *
+ * I have decided it is better to make too much than to make too
+ * little, so this stuff is commented out unless you're sure it's ok.
+ * -- ardeb 1/12/88
+ */
+ /*
+ * Christos, 4/9/92: If we are saving commands, pretend that
+ * the target is made now. Otherwise archives with '...' rules
+ * don't work!
+ */
+ if (!GNode_ShouldExecute(gn) || (gn->type & OP_SAVE_CMDS) ||
(mtime == 0 && !(gn->type & OP_WAIT))) {
- DEBUG2(MAKE, " recheck(%s): update time from %s to now\n",
- gn->name, Targ_FmtTime(gn->mtime));
- gn->mtime = now;
- } else {
- DEBUG2(MAKE, " recheck(%s): current update time: %s\n",
- gn->name, Targ_FmtTime(gn->mtime));
- }
+ DEBUG2(MAKE, " recheck(%s): update time from %s to now\n",
+ gn->name,
+ gn->mtime == 0 ? "nonexistent" : Targ_FmtTime(gn->mtime));
+ gn->mtime = now;
+ } else {
+ DEBUG2(MAKE, " recheck(%s): current update time: %s\n",
+ gn->name, Targ_FmtTime(gn->mtime));
+ }
#endif
- /* XXX: The returned mtime may differ from gn->mtime.
- * Intentionally? */
- return mtime;
+ /* XXX: The returned mtime may differ from gn->mtime.
+ * Intentionally? */
+ return mtime;
}
/*
@@ -544,39 +568,54 @@ Make_Recheck(GNode *gn)
static void
UpdateImplicitParentsVars(GNode *cgn, const char *cname)
{
- GNodeListNode *ln;
- const char *cpref = GNode_VarPrefix(cgn);
-
- for (ln = cgn->implicitParents->first; ln != NULL; ln = ln->next) {
- GNode *pgn = ln->datum;
- if (pgn->flags & REMAKE) {
- Var_Set(IMPSRC, cname, pgn);
- if (cpref != NULL)
- Var_Set(PREFIX, cpref, pgn);
+ GNodeListNode *ln;
+ const char *cpref = GNode_VarPrefix(cgn);
+
+ for (ln = cgn->implicitParents.first; ln != NULL; ln = ln->next) {
+ GNode *pgn = ln->datum;
+ if (pgn->flags & REMAKE) {
+ Var_Set(IMPSRC, cname, pgn);
+ if (cpref != NULL)
+ Var_Set(PREFIX, cpref, pgn);
+ }
}
- }
}
/* See if a .ORDER rule stops us from building this node. */
static Boolean
IsWaitingForOrder(GNode *gn)
{
- GNodeListNode *ln;
+ GNodeListNode *ln;
+
+ for (ln = gn->order_pred.first; ln != NULL; ln = ln->next) {
+ GNode *ogn = ln->datum;
- for (ln = gn->order_pred->first; ln != NULL; ln = ln->next) {
- GNode *ogn = ln->datum;
+ if (GNode_IsDone(ogn) || !(ogn->flags & REMAKE))
+ continue;
- if (ogn->made >= MADE || !(ogn->flags & REMAKE))
- continue;
+ DEBUG2(MAKE,
+ "IsWaitingForOrder: Waiting for .ORDER node \"%s%s\"\n",
+ ogn->name, ogn->cohort_num);
+ return TRUE;
+ }
+ return FALSE;
+}
- DEBUG2(MAKE, "IsWaitingForOrder: Waiting for .ORDER node \"%s%s\"\n",
- ogn->name, ogn->cohort_num);
- return TRUE;
- }
- return FALSE;
+static int MakeBuildParent(GNode *, GNodeListNode *);
+
+static void
+ScheduleOrderSuccessors(GNode *gn)
+{
+ GNodeListNode *toBeMadeNext = toBeMade.first;
+ GNodeListNode *ln;
+
+ for (ln = gn->order_succ.first; ln != NULL; ln = ln->next)
+ if (MakeBuildParent(ln->datum, toBeMadeNext) != 0)
+ break;
}
-/* Perform update on the parents of a node. Used by JobFinish once
+/*
+ * Perform update on the parents of a node. Used by JobFinish once
* a node has been dealt with and by MakeStartJobs if it finds an
* up-to-date node.
*
@@ -598,149 +637,159 @@ IsWaitingForOrder(GNode *gn)
void
Make_Update(GNode *cgn)
{
- const char *cname; /* the child's name */
- time_t mtime = -1;
- GNodeList *parents;
- GNodeListNode *ln;
- GNode *centurion;
-
- /* It is save to re-examine any nodes again */
- checked_seqno++;
-
- cname = GNode_VarTarget(cgn);
-
- DEBUG2(MAKE, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num);
-
- /*
- * If the child was actually made, see what its modification time is
- * now -- some rules won't actually update the file. If the file still
- * doesn't exist, make its mtime now.
- */
- if (cgn->made != UPTODATE) {
- mtime = Make_Recheck(cgn);
- }
-
- /*
- * If this is a `::' node, we must consult its first instance
- * which is where all parents are linked.
- */
- if ((centurion = cgn->centurion) != NULL) {
- if (!Lst_IsEmpty(cgn->parents))
- Punt("%s%s: cohort has parents", cgn->name, cgn->cohort_num);
- centurion->unmade_cohorts--;
- if (centurion->unmade_cohorts < 0)
- Error("Graph cycles through centurion %s", centurion->name);
- } else {
- centurion = cgn;
- }
- parents = centurion->parents;
-
- /* If this was a .ORDER node, schedule the RHS */
- Lst_ForEachUntil(centurion->order_succ, MakeBuildParent, toBeMade->first);
-
- /* Now mark all the parents as having one less unmade child */
- for (ln = parents->first; ln != NULL; ln = ln->next) {
- GNode *pgn = ln->datum;
+ const char *cname; /* the child's name */
+ time_t mtime = -1;
+ GNodeList *parents;
+ GNodeListNode *ln;
+ GNode *centurion;
- if (DEBUG(MAKE)) {
- debug_printf("inspect parent %s%s: ", pgn->name, pgn->cohort_num);
- GNode_FprintDetails(opts.debug_file, "", pgn, "");
- debug_printf(", unmade %d ", pgn->unmade - 1);
- }
+ /* It is save to re-examine any nodes again */
+ checked_seqno++;
- if (!(pgn->flags & REMAKE)) {
- /* This parent isn't needed */
- DEBUG0(MAKE, "- not needed\n");
- continue;
- }
- if (mtime == 0 && !(cgn->type & OP_WAIT))
- pgn->flags |= FORCE;
+ cname = GNode_VarTarget(cgn);
+
+ DEBUG2(MAKE, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num);
/*
- * If the parent has the .MADE attribute, its timestamp got
- * updated to that of its newest child, and its unmade
- * child count got set to zero in Make_ExpandUse().
- * However other things might cause us to build one of its
- * children - and so we mustn't do any processing here when
- * the child build finishes.
+ * If the child was actually made, see what its modification time is
+ * now -- some rules won't actually update the file. If the file
+ * still doesn't exist, make its mtime now.
*/
- if (pgn->type & OP_MADE) {
- DEBUG0(MAKE, "- .MADE\n");
- continue;
- }
-
- if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE))) {
- if (cgn->made == MADE)
- pgn->flags |= CHILDMADE;
- GNode_UpdateYoungestChild(pgn, cgn);
+ if (cgn->made != UPTODATE) {
+ mtime = Make_Recheck(cgn);
}
/*
- * A parent must wait for the completion of all instances
- * of a `::' dependency.
+ * If this is a `::' node, we must consult its first instance
+ * which is where all parents are linked.
*/
- if (centurion->unmade_cohorts != 0 || centurion->made < MADE) {
- DEBUG2(MAKE, "- centurion made %d, %d unmade cohorts\n",
- centurion->made, centurion->unmade_cohorts);
- continue;
+ if ((centurion = cgn->centurion) != NULL) {
+ if (!Lst_IsEmpty(&cgn->parents))
+ Punt("%s%s: cohort has parents", cgn->name,
+ cgn->cohort_num);
+ centurion->unmade_cohorts--;
+ if (centurion->unmade_cohorts < 0)
+ Error("Graph cycles through centurion %s",
+ centurion->name);
+ } else {
+ centurion = cgn;
}
+ parents = &centurion->parents;
+
+ /* If this was a .ORDER node, schedule the RHS */
+ ScheduleOrderSuccessors(centurion);
+
+ /* Now mark all the parents as having one less unmade child */
+ for (ln = parents->first; ln != NULL; ln = ln->next) {
+ GNode *pgn = ln->datum;
+
+ if (DEBUG(MAKE)) {
+ debug_printf("inspect parent %s%s: ", pgn->name,
+ pgn->cohort_num);
+ GNode_FprintDetails(opts.debug_file, "", pgn, "");
+ debug_printf(", unmade %d ", pgn->unmade - 1);
+ }
+
+ if (!(pgn->flags & REMAKE)) {
+ /* This parent isn't needed */
+ DEBUG0(MAKE, "- not needed\n");
+ continue;
+ }
+ if (mtime == 0 && !(cgn->type & OP_WAIT))
+ pgn->flags |= FORCE;
- /* One more child of this parent is now made */
- pgn->unmade--;
- if (pgn->unmade < 0) {
- if (DEBUG(MAKE)) {
- debug_printf("Graph cycles through %s%s\n",
- pgn->name, pgn->cohort_num);
- Targ_PrintGraph(2);
- }
- Error("Graph cycles through %s%s", pgn->name, pgn->cohort_num);
- }
+ /*
+ * If the parent has the .MADE attribute, its timestamp got
+ * updated to that of its newest child, and its unmade
+ * child count got set to zero in Make_ExpandUse().
+ * However other things might cause us to build one of its
+ * children - and so we mustn't do any processing here when
+ * the child build finishes.
+ */
+ if (pgn->type & OP_MADE) {
+ DEBUG0(MAKE, "- .MADE\n");
+ continue;
+ }
- /* We must always rescan the parents of .WAIT and .ORDER nodes. */
- if (pgn->unmade != 0 && !(centurion->type & OP_WAIT)
- && !(centurion->flags & DONE_ORDER)) {
- DEBUG0(MAKE, "- unmade children\n");
- continue;
- }
- if (pgn->made != DEFERRED) {
- /*
- * Either this parent is on a different branch of the tree,
- * or it on the RHS of a .WAIT directive
- * or it is already on the toBeMade list.
- */
- DEBUG0(MAKE, "- not deferred\n");
- continue;
- }
+ if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE))) {
+ if (cgn->made == MADE)
+ pgn->flags |= CHILDMADE;
+ GNode_UpdateYoungestChild(pgn, cgn);
+ }
- if (IsWaitingForOrder(pgn))
- continue;
+ /*
+ * A parent must wait for the completion of all instances
+ * of a `::' dependency.
+ */
+ if (centurion->unmade_cohorts != 0 ||
+ !GNode_IsDone(centurion)) {
+ DEBUG2(MAKE,
+ "- centurion made %d, %d unmade cohorts\n",
+ centurion->made, centurion->unmade_cohorts);
+ continue;
+ }
+
+ /* One more child of this parent is now made */
+ pgn->unmade--;
+ if (pgn->unmade < 0) {
+ if (DEBUG(MAKE)) {
+ debug_printf("Graph cycles through %s%s\n",
+ pgn->name, pgn->cohort_num);
+ Targ_PrintGraph(2);
+ }
+ Error("Graph cycles through %s%s", pgn->name,
+ pgn->cohort_num);
+ }
- if (DEBUG(MAKE)) {
- debug_printf("- %s%s made, schedule %s%s (made %d)\n",
- cgn->name, cgn->cohort_num,
- pgn->name, pgn->cohort_num, pgn->made);
- Targ_PrintNode(pgn, 2);
+ /*
+ * We must always rescan the parents of .WAIT and .ORDER
+ * nodes.
+ */
+ if (pgn->unmade != 0 && !(centurion->type & OP_WAIT)
+ && !(centurion->flags & DONE_ORDER)) {
+ DEBUG0(MAKE, "- unmade children\n");
+ continue;
+ }
+ if (pgn->made != DEFERRED) {
+ /*
+ * Either this parent is on a different branch of
+ * the tree, or it on the RHS of a .WAIT directive
+ * or it is already on the toBeMade list.
+ */
+ DEBUG0(MAKE, "- not deferred\n");
+ continue;
+ }
+
+ if (IsWaitingForOrder(pgn))
+ continue;
+
+ if (DEBUG(MAKE)) {
+ debug_printf("- %s%s made, schedule %s%s (made %d)\n",
+ cgn->name, cgn->cohort_num,
+ pgn->name, pgn->cohort_num, pgn->made);
+ Targ_PrintNode(pgn, 2);
+ }
+ /* Ok, we can schedule the parent again */
+ pgn->made = REQUESTED;
+ Lst_Enqueue(&toBeMade, pgn);
}
- /* Ok, we can schedule the parent again */
- pgn->made = REQUESTED;
- Lst_Enqueue(toBeMade, pgn);
- }
- UpdateImplicitParentsVars(cgn, cname);
+ UpdateImplicitParentsVars(cgn, cname);
}
static void
UnmarkChildren(GNode *gn)
{
- GNodeListNode *ln;
+ GNodeListNode *ln;
- for (ln = gn->children->first; ln != NULL; ln = ln->next) {
- GNode *child = ln->datum;
- child->type &= ~OP_MARK;
- }
+ for (ln = gn->children.first; ln != NULL; ln = ln->next) {
+ GNode *child = ln->datum;
+ child->type &= ~OP_MARK;
+ }
}
-/* Add a child's name to the ALLSRC and OODATE variables of the given
+/*
+ * Add a child's name to the ALLSRC and OODATE variables of the given
* node, but only if it has not been given the .EXEC, .USE or .INVISIBLE
* attributes. .EXEC and .USE children are very rarely going to be files,
* so...
@@ -761,53 +810,55 @@ UnmarkChildren(GNode *gn)
static void
MakeAddAllSrc(GNode *cgn, GNode *pgn)
{
- if (cgn->type & OP_MARK)
- return;
- cgn->type |= OP_MARK;
-
- if (!(cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE))) {
- const char *child, *allsrc;
-
- if (cgn->type & OP_ARCHV)
- child = GNode_VarMember(cgn);
- else
- child = GNode_Path(cgn);
- if (cgn->type & OP_JOIN) {
- allsrc = GNode_VarAllsrc(cgn);
- } else {
- allsrc = child;
+ if (cgn->type & OP_MARK)
+ return;
+ cgn->type |= OP_MARK;
+
+ if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE | OP_INVISIBLE))) {
+ const char *child, *allsrc;
+
+ if (cgn->type & OP_ARCHV)
+ child = GNode_VarMember(cgn);
+ else
+ child = GNode_Path(cgn);
+ if (cgn->type & OP_JOIN) {
+ allsrc = GNode_VarAllsrc(cgn);
+ } else {
+ allsrc = child;
+ }
+ if (allsrc != NULL)
+ Var_Append(ALLSRC, allsrc, pgn);
+ if (pgn->type & OP_JOIN) {
+ if (cgn->made == MADE) {
+ Var_Append(OODATE, child, pgn);
+ }
+ } else if ((pgn->mtime < cgn->mtime) ||
+ (cgn->mtime >= now && cgn->made == MADE)) {
+ /*
+ * It goes in the OODATE variable if the parent is
+ * younger than the child or if the child has been
+ * modified more recently than the start of the make.
+ * This is to keep pmake from getting confused if
+ * something else updates the parent after the make
+ * starts (shouldn't happen, I know, but sometimes it
+ * does). In such a case, if we've updated the child,
+ * the parent is likely to have a modification time
+ * later than that of the child and anything that
+ * relies on the OODATE variable will be hosed.
+ *
+ * XXX: This will cause all made children to go in
+ * the OODATE variable, even if they're not touched,
+ * if RECHECK isn't defined, since cgn->mtime is set
+ * to now in Make_Update. According to some people,
+ * this is good...
+ */
+ Var_Append(OODATE, child, pgn);
+ }
}
- if (allsrc != NULL)
- Var_Append(ALLSRC, allsrc, pgn);
- if (pgn->type & OP_JOIN) {
- if (cgn->made == MADE) {
- Var_Append(OODATE, child, pgn);
- }
- } else if ((pgn->mtime < cgn->mtime) ||
- (cgn->mtime >= now && cgn->made == MADE))
- {
- /*
- * It goes in the OODATE variable if the parent is younger than the
- * child or if the child has been modified more recently than
- * the start of the make. This is to keep pmake from getting
- * confused if something else updates the parent after the
- * make starts (shouldn't happen, I know, but sometimes it
- * does). In such a case, if we've updated the child, the parent
- * is likely to have a modification time later than that of
- * the child and anything that relies on the OODATE variable will
- * be hosed.
- *
- * XXX: This will cause all made children to go in the OODATE
- * variable, even if they're not touched, if RECHECK isn't defined,
- * since cgn->mtime is set to now in Make_Update. According to
- * some people, this is good...
- */
- Var_Append(OODATE, child, pgn);
- }
- }
}
-/* Set up the ALLSRC and OODATE variables. Sad to say, it must be
+/*
+ * Set up the ALLSRC and OODATE variables. Sad to say, it must be
* done separately, rather than while traversing the graph. This is
* because Make defined OODATE to contain all sources whose modification
* times were later than that of the target, *not* those sources that
@@ -823,81 +874,96 @@ MakeAddAllSrc(GNode *cgn, GNode *pgn)
void
Make_DoAllVar(GNode *gn)
{
- GNodeListNode *ln;
+ GNodeListNode *ln;
- if (gn->flags & DONE_ALLSRC)
- return;
+ if (gn->flags & DONE_ALLSRC)
+ return;
- UnmarkChildren(gn);
- for (ln = gn->children->first; ln != NULL; ln = ln->next)
- MakeAddAllSrc(ln->datum, gn);
+ UnmarkChildren(gn);
+ for (ln = gn->children.first; ln != NULL; ln = ln->next)
+ MakeAddAllSrc(ln->datum, gn);
- if (!Var_Exists(OODATE, gn))
- Var_Set(OODATE, "", gn);
- if (!Var_Exists(ALLSRC, gn))
- Var_Set(ALLSRC, "", gn);
+ if (!Var_Exists(OODATE, gn))
+ Var_Set(OODATE, "", gn);
+ if (!Var_Exists(ALLSRC, gn))
+ Var_Set(ALLSRC, "", gn);
- if (gn->type & OP_JOIN)
- Var_Set(TARGET, GNode_VarAllsrc(gn), gn);
- gn->flags |= DONE_ALLSRC;
+ if (gn->type & OP_JOIN)
+ Var_Set(TARGET, GNode_VarAllsrc(gn), gn);
+ gn->flags |= DONE_ALLSRC;
}
static int
-MakeBuildChild(void *v_cn, void *toBeMade_next)
+MakeBuildChild(GNode *cn, GNodeListNode *toBeMadeNext)
{
- GNode *cn = v_cn;
-
- if (DEBUG(MAKE)) {
- debug_printf("MakeBuildChild: inspect %s%s, ",
- cn->name, cn->cohort_num);
- GNode_FprintDetails(opts.debug_file, "", cn, "\n");
- }
- if (cn->made > DEFERRED)
- return 0;
- /* If this node is on the RHS of a .ORDER, check LHSs. */
- if (IsWaitingForOrder(cn)) {
- /* Can't build this (or anything else in this child list) yet */
- cn->made = DEFERRED;
- return 0; /* but keep looking */
- }
-
- DEBUG2(MAKE, "MakeBuildChild: schedule %s%s\n", cn->name, cn->cohort_num);
-
- cn->made = REQUESTED;
- if (toBeMade_next == NULL)
- Lst_Append(toBeMade, cn);
- else
- Lst_InsertBefore(toBeMade, toBeMade_next, cn);
-
- if (cn->unmade_cohorts != 0)
- Lst_ForEachUntil(cn->cohorts, MakeBuildChild, toBeMade_next);
-
- /*
- * If this node is a .WAIT node with unmade children
- * then don't add the next sibling.
- */
- return cn->type & OP_WAIT && cn->unmade > 0;
+ if (DEBUG(MAKE)) {
+ debug_printf("MakeBuildChild: inspect %s%s, ",
+ cn->name, cn->cohort_num);
+ GNode_FprintDetails(opts.debug_file, "", cn, "\n");
+ }
+ if (GNode_IsReady(cn))
+ return 0;
+
+ /* If this node is on the RHS of a .ORDER, check LHSs. */
+ if (IsWaitingForOrder(cn)) {
+ /* Can't build this (or anything else in this child list) yet */
+ cn->made = DEFERRED;
+ return 0; /* but keep looking */
+ }
+
+ DEBUG2(MAKE, "MakeBuildChild: schedule %s%s\n",
+ cn->name, cn->cohort_num);
+
+ cn->made = REQUESTED;
+ if (toBeMadeNext == NULL)
+ Lst_Append(&toBeMade, cn);
+ else
+ Lst_InsertBefore(&toBeMade, toBeMadeNext, cn);
+
+ if (cn->unmade_cohorts != 0) {
+ ListNode *ln;
+
+ for (ln = cn->cohorts.first; ln != NULL; ln = ln->next)
+ if (MakeBuildChild(ln->datum, toBeMadeNext) != 0)
+ break;
+ }
+
+ /*
+ * If this node is a .WAIT node with unmade children
+ * then don't add the next sibling.
+ */
+ return cn->type & OP_WAIT && cn->unmade > 0;
}
/* When a .ORDER LHS node completes, we do this on each RHS. */
static int
-MakeBuildParent(void *v_pn, void *toBeMade_next)
+MakeBuildParent(GNode *pn, GNodeListNode *toBeMadeNext)
{
- GNode *pn = v_pn;
+ if (pn->made != DEFERRED)
+ return 0;
+
+ if (MakeBuildChild(pn, toBeMadeNext) == 0) {
+ /* When this node is built, reschedule its parents. */
+ pn->flags |= DONE_ORDER;
+ }
- if (pn->made != DEFERRED)
return 0;
+}
- if (MakeBuildChild(pn, toBeMade_next) == 0) {
- /* Mark so that when this node is built we reschedule its parents */
- pn->flags |= DONE_ORDER;
- }
+static void
+MakeChildren(GNode *gn)
+{
+ GNodeListNode *toBeMadeNext = toBeMade.first;
+ GNodeListNode *ln;
- return 0;
+ for (ln = gn->children.first; ln != NULL; ln = ln->next)
+ if (MakeBuildChild(ln->datum, toBeMadeNext) != 0)
+ break;
}
-/* Start as many jobs as possible, taking them from the toBeMade queue.
+/*
+ * Start as many jobs as possible, taking them from the toBeMade queue.
*
* If the -q option was given, no job will be started,
* but as soon as an out-of-date target is found, this function
@@ -906,204 +972,229 @@ MakeBuildParent(void *v_pn, void *toBeMade_next)
static Boolean
MakeStartJobs(void)
{
- GNode *gn;
- Boolean have_token = FALSE;
-
- while (!Lst_IsEmpty(toBeMade)) {
- /* Get token now to avoid cycling job-list when we only have 1 token */
- if (!have_token && !Job_TokenWithdraw())
- break;
- have_token = TRUE;
-
- gn = Lst_Dequeue(toBeMade);
- DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num);
-
- if (gn->made != REQUESTED) {
- DEBUG1(MAKE, "state %d\n", gn->made);
-
- make_abort(gn, __LINE__);
- }
-
- if (gn->checked_seqno == checked_seqno) {
- /* We've already looked at this node since a job finished... */
- DEBUG2(MAKE, "already checked %s%s\n", gn->name, gn->cohort_num);
- gn->made = DEFERRED;
- continue;
- }
- gn->checked_seqno = checked_seqno;
-
- if (gn->unmade != 0) {
- /*
- * We can't build this yet, add all unmade children to toBeMade,
- * just before the current first element.
- */
- gn->made = DEFERRED;
- Lst_ForEachUntil(gn->children, MakeBuildChild, toBeMade->first);
- /* and drop this node on the floor */
- DEBUG2(MAKE, "dropped %s%s\n", gn->name, gn->cohort_num);
- continue;
- }
+ GNode *gn;
+ Boolean have_token = FALSE;
- gn->made = BEINGMADE;
- if (GNode_IsOODate(gn)) {
- DEBUG0(MAKE, "out-of-date\n");
- if (opts.queryFlag)
- return TRUE;
- Make_DoAllVar(gn);
- Job_Make(gn);
- have_token = FALSE;
- } else {
- DEBUG0(MAKE, "up-to-date\n");
- gn->made = UPTODATE;
- if (gn->type & OP_JOIN) {
+ while (!Lst_IsEmpty(&toBeMade)) {
/*
- * Even for an up-to-date .JOIN node, we need it to have its
- * context variables so references to it get the correct
- * value for .TARGET when building up the context variables
- * of its parent(s)...
+ * Get token now to avoid cycling job-list when we only
+ * have 1 token
*/
- Make_DoAllVar(gn);
- }
- Make_Update(gn);
+ if (!have_token && !Job_TokenWithdraw())
+ break;
+ have_token = TRUE;
+
+ gn = Lst_Dequeue(&toBeMade);
+ DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num);
+
+ if (gn->made != REQUESTED) {
+ /*
+ * XXX: Replace %d with string representation;
+ * see made_name.
+ */
+ DEBUG1(MAKE, "state %d\n", gn->made);
+
+ make_abort(gn, __LINE__);
+ }
+
+ if (gn->checked_seqno == checked_seqno) {
+ /*
+ * We've already looked at this node since a job
+ * finished...
+ */
+ DEBUG2(MAKE, "already checked %s%s\n", gn->name,
+ gn->cohort_num);
+ gn->made = DEFERRED;
+ continue;
+ }
+ gn->checked_seqno = checked_seqno;
+
+ if (gn->unmade != 0) {
+ /*
+ * We can't build this yet, add all unmade children
+ * to toBeMade, just before the current first element.
+ */
+ gn->made = DEFERRED;
+
+ MakeChildren(gn);
+
+ /* and drop this node on the floor */
+ DEBUG2(MAKE, "dropped %s%s\n", gn->name,
+ gn->cohort_num);
+ continue;
+ }
+
+ gn->made = BEINGMADE;
+ if (GNode_IsOODate(gn)) {
+ DEBUG0(MAKE, "out-of-date\n");
+ if (opts.queryFlag)
+ return TRUE;
+ Make_DoAllVar(gn);
+ Job_Make(gn);
+ have_token = FALSE;
+ } else {
+ DEBUG0(MAKE, "up-to-date\n");
+ gn->made = UPTODATE;
+ if (gn->type & OP_JOIN) {
+ /*
+ * Even for an up-to-date .JOIN node, we
+ * need it to have its context variables so
+ * references to it get the correct value
+ * for .TARGET when building up the context
+ * variables of its parent(s)...
+ */
+ Make_DoAllVar(gn);
+ }
+ Make_Update(gn);
+ }
}
- }
- if (have_token)
- Job_TokenReturn();
+ if (have_token)
+ Job_TokenReturn();
- return FALSE;
+ return FALSE;
}
/* Print the status of a .ORDER node. */
static void
MakePrintStatusOrderNode(GNode *ogn, GNode *gn)
{
- if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED)
- /* not waiting for this one */
- return;
-
- printf(" `%s%s' has .ORDER dependency against %s%s ",
- gn->name, gn->cohort_num, ogn->name, ogn->cohort_num);
- GNode_FprintDetails(stdout, "(", ogn, ")\n");
-
- if (DEBUG(MAKE) && opts.debug_file != stdout) {
- debug_printf(" `%s%s' has .ORDER dependency against %s%s ",
- gn->name, gn->cohort_num, ogn->name, ogn->cohort_num);
- GNode_FprintDetails(opts.debug_file, "(", ogn, ")\n");
- }
+ if (!GNode_IsWaitingFor(ogn))
+ return;
+
+ printf(" `%s%s' has .ORDER dependency against %s%s ",
+ gn->name, gn->cohort_num, ogn->name, ogn->cohort_num);
+ GNode_FprintDetails(stdout, "(", ogn, ")\n");
+
+ if (DEBUG(MAKE) && opts.debug_file != stdout) {
+ debug_printf(" `%s%s' has .ORDER dependency against %s%s ",
+ gn->name, gn->cohort_num, ogn->name, ogn->cohort_num);
+ GNode_FprintDetails(opts.debug_file, "(", ogn, ")\n");
+ }
}
static void
MakePrintStatusOrder(GNode *gn)
{
- GNodeListNode *ln;
- for (ln = gn->order_pred->first; ln != NULL; ln = ln->next)
- MakePrintStatusOrderNode(ln->datum, gn);
+ GNodeListNode *ln;
+ for (ln = gn->order_pred.first; ln != NULL; ln = ln->next)
+ MakePrintStatusOrderNode(ln->datum, gn);
}
static void MakePrintStatusList(GNodeList *, int *);
-/* Print the status of a top-level node, viz. it being up-to-date already
+/*
+ * Print the status of a top-level node, viz. it being up-to-date already
* or not created due to an error in a lower level.
- * Callback function for Make_Run via Lst_ForEachUntil.
*/
static Boolean
MakePrintStatus(GNode *gn, int *errors)
{
- if (gn->flags & DONECYCLE)
- /* We've completely processed this node before, don't do it again. */
- return FALSE;
+ if (gn->flags & DONECYCLE) {
+ /*
+ * We've completely processed this node before, don't do
+ * it again.
+ */
+ return FALSE;
+ }
- if (gn->unmade == 0) {
- gn->flags |= DONECYCLE;
- switch (gn->made) {
- case UPTODATE:
- printf("`%s%s' is up to date.\n", gn->name, gn->cohort_num);
- break;
- case MADE:
- break;
- case UNMADE:
- case DEFERRED:
- case REQUESTED:
- case BEINGMADE:
- (*errors)++;
- printf("`%s%s' was not built", gn->name, gn->cohort_num);
- GNode_FprintDetails(stdout, " (", gn, ")!\n");
- if (DEBUG(MAKE) && opts.debug_file != stdout) {
- debug_printf("`%s%s' was not built", gn->name, gn->cohort_num);
- GNode_FprintDetails(opts.debug_file, " (", gn, ")!\n");
- }
- /* Most likely problem is actually caused by .ORDER */
- MakePrintStatusOrder(gn);
- break;
- default:
- /* Errors - already counted */
- printf("`%s%s' not remade because of errors.\n",
- gn->name, gn->cohort_num);
- if (DEBUG(MAKE) && opts.debug_file != stdout)
- debug_printf("`%s%s' not remade because of errors.\n",
- gn->name, gn->cohort_num);
- break;
+ if (gn->unmade == 0) {
+ gn->flags |= DONECYCLE;
+ switch (gn->made) {
+ case UPTODATE:
+ printf("`%s%s' is up to date.\n", gn->name,
+ gn->cohort_num);
+ break;
+ case MADE:
+ break;
+ case UNMADE:
+ case DEFERRED:
+ case REQUESTED:
+ case BEINGMADE:
+ (*errors)++;
+ printf("`%s%s' was not built", gn->name,
+ gn->cohort_num);
+ GNode_FprintDetails(stdout, " (", gn, ")!\n");
+ if (DEBUG(MAKE) && opts.debug_file != stdout) {
+ debug_printf("`%s%s' was not built", gn->name,
+ gn->cohort_num);
+ GNode_FprintDetails(opts.debug_file, " (", gn,
+ ")!\n");
+ }
+ /* Most likely problem is actually caused by .ORDER */
+ MakePrintStatusOrder(gn);
+ break;
+ default:
+ /* Errors - already counted */
+ printf("`%s%s' not remade because of errors.\n",
+ gn->name, gn->cohort_num);
+ if (DEBUG(MAKE) && opts.debug_file != stdout)
+ debug_printf(
+ "`%s%s' not remade because of errors.\n",
+ gn->name, gn->cohort_num);
+ break;
+ }
+ return FALSE;
}
- return FALSE;
- }
-
- DEBUG3(MAKE, "MakePrintStatus: %s%s has %d unmade children\n",
- gn->name, gn->cohort_num, gn->unmade);
- /*
- * If printing cycles and came to one that has unmade children,
- * print out the cycle by recursing on its children.
- */
- if (!(gn->flags & CYCLE)) {
- /* First time we've seen this node, check all children */
- gn->flags |= CYCLE;
- MakePrintStatusList(gn->children, errors);
- /* Mark that this node needn't be processed again */
+
+ DEBUG3(MAKE, "MakePrintStatus: %s%s has %d unmade children\n",
+ gn->name, gn->cohort_num, gn->unmade);
+ /*
+ * If printing cycles and came to one that has unmade children,
+ * print out the cycle by recursing on its children.
+ */
+ if (!(gn->flags & CYCLE)) {
+ /* First time we've seen this node, check all children */
+ gn->flags |= CYCLE;
+ MakePrintStatusList(&gn->children, errors);
+ /* Mark that this node needn't be processed again */
+ gn->flags |= DONECYCLE;
+ return FALSE;
+ }
+
+ /* Only output the error once per node */
gn->flags |= DONECYCLE;
+ Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num);
+ if ((*errors)++ > 100)
+ /* Abandon the whole error report */
+ return TRUE;
+
+ /* Reporting for our children will give the rest of the loop */
+ MakePrintStatusList(&gn->children, errors);
return FALSE;
- }
-
- /* Only output the error once per node */
- gn->flags |= DONECYCLE;
- Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num);
- if ((*errors)++ > 100)
- /* Abandon the whole error report */
- return TRUE;
-
- /* Reporting for our children will give the rest of the loop */
- MakePrintStatusList(gn->children, errors);
- return FALSE;
}
static void
MakePrintStatusList(GNodeList *gnodes, int *errors)
{
- GNodeListNode *ln;
- for (ln = gnodes->first; ln != NULL; ln = ln->next)
- if (MakePrintStatus(ln->datum, errors))
- break;
+ GNodeListNode *ln;
+
+ for (ln = gnodes->first; ln != NULL; ln = ln->next)
+ if (MakePrintStatus(ln->datum, errors))
+ break;
}
static void
ExamineLater(GNodeList *examine, GNodeList *toBeExamined)
{
- ListNode *ln;
+ ListNode *ln;
- for (ln = toBeExamined->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
+ for (ln = toBeExamined->first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
- if (gn->flags & REMAKE)
- continue;
- if (gn->type & (OP_USE | OP_USEBEFORE))
- continue;
+ if (gn->flags & REMAKE)
+ continue;
+ if (gn->type & (OP_USE | OP_USEBEFORE))
+ continue;
- DEBUG2(MAKE, "ExamineLater: need to examine \"%s%s\"\n",
- gn->name, gn->cohort_num);
- Lst_Enqueue(examine, gn);
- }
+ DEBUG2(MAKE, "ExamineLater: need to examine \"%s%s\"\n",
+ gn->name, gn->cohort_num);
+ Lst_Enqueue(examine, gn);
+ }
}
-/* Expand .USE nodes and create a new targets list.
+/*
+ * Expand .USE nodes and create a new targets list.
*
* Input:
* targs the initial list of targets
@@ -1111,165 +1202,169 @@ ExamineLater(GNodeList *examine, GNodeList *toBeExamined)
void
Make_ExpandUse(GNodeList *targs)
{
- GNodeList *examine = Lst_New(); /* Queue of targets to examine */
- Lst_AppendAll(examine, targs);
-
- /*
- * Make an initial downward pass over the graph, marking nodes to be made
- * as we go down. We call Suff_FindDeps to find where a node is and
- * to get some children for it if it has none and also has no commands.
- * If the node is a leaf, we stick it on the toBeMade queue to
- * be looked at in a minute, otherwise we add its children to our queue
- * and go on about our business.
- */
- while (!Lst_IsEmpty(examine)) {
- GNode *gn = Lst_Dequeue(examine);
-
- if (gn->flags & REMAKE)
- /* We've looked at this one already */
- continue;
- gn->flags |= REMAKE;
- DEBUG2(MAKE, "Make_ExpandUse: examine %s%s\n",
- gn->name, gn->cohort_num);
-
- if (gn->type & OP_DOUBLEDEP)
- Lst_PrependAll(examine, gn->cohorts);
+ GNodeList examine = LST_INIT; /* Queue of targets to examine */
+ Lst_AppendAll(&examine, targs);
/*
- * Apply any .USE rules before looking for implicit dependencies
- * to make sure everything has commands that should...
- * Make sure that the TARGET is set, so that we can make
- * expansions.
+ * Make an initial downward pass over the graph, marking nodes to
+ * be made as we go down.
+ *
+ * We call Suff_FindDeps to find where a node is and to get some
+ * children for it if it has none and also has no commands. If the
+ * node is a leaf, we stick it on the toBeMade queue to be looked
+ * at in a minute, otherwise we add its children to our queue and
+ * go on about our business.
*/
- if (gn->type & OP_ARCHV) {
- char *eoa = strchr(gn->name, '(');
- char *eon = strchr(gn->name, ')');
- if (eoa == NULL || eon == NULL)
- continue;
- *eoa = '\0';
- *eon = '\0';
- Var_Set(MEMBER, eoa + 1, gn);
- Var_Set(ARCHIVE, gn->name, gn);
- *eoa = '(';
- *eon = ')';
- }
+ while (!Lst_IsEmpty(&examine)) {
+ GNode *gn = Lst_Dequeue(&examine);
+
+ if (gn->flags & REMAKE)
+ /* We've looked at this one already */
+ continue;
+ gn->flags |= REMAKE;
+ DEBUG2(MAKE, "Make_ExpandUse: examine %s%s\n",
+ gn->name, gn->cohort_num);
- Dir_UpdateMTime(gn, FALSE);
- Var_Set(TARGET, GNode_Path(gn), gn);
- UnmarkChildren(gn);
- HandleUseNodes(gn);
-
- if (!(gn->type & OP_MADE))
- Suff_FindDeps(gn);
- else {
- PretendAllChildrenAreMade(gn);
- if (gn->unmade != 0)
- printf("Warning: %s%s still has %d unmade children\n",
- gn->name, gn->cohort_num, gn->unmade);
- }
+ if (gn->type & OP_DOUBLEDEP)
+ Lst_PrependAll(&examine, &gn->cohorts);
- if (gn->unmade != 0)
- ExamineLater(examine, gn->children);
- }
+ /*
+ * Apply any .USE rules before looking for implicit
+ * dependencies to make sure everything has commands that
+ * should.
+ *
+ * Make sure that the TARGET is set, so that we can make
+ * expansions.
+ */
+ if (gn->type & OP_ARCHV) {
+ char *eoa = strchr(gn->name, '(');
+ char *eon = strchr(gn->name, ')');
+ if (eoa == NULL || eon == NULL)
+ continue;
+ *eoa = '\0';
+ *eon = '\0';
+ Var_Set(MEMBER, eoa + 1, gn);
+ Var_Set(ARCHIVE, gn->name, gn);
+ *eoa = '(';
+ *eon = ')';
+ }
+
+ Dir_UpdateMTime(gn, FALSE);
+ Var_Set(TARGET, GNode_Path(gn), gn);
+ UnmarkChildren(gn);
+ HandleUseNodes(gn);
+
+ if (!(gn->type & OP_MADE))
+ Suff_FindDeps(gn);
+ else {
+ PretendAllChildrenAreMade(gn);
+ if (gn->unmade != 0) {
+ printf(
+ "Warning: "
+ "%s%s still has %d unmade children\n",
+ gn->name, gn->cohort_num, gn->unmade);
+ }
+ }
+
+ if (gn->unmade != 0)
+ ExamineLater(&examine, &gn->children);
+ }
- Lst_Free(examine);
+ Lst_Done(&examine);
}
/* Make the .WAIT node depend on the previous children */
static void
add_wait_dependency(GNodeListNode *owln, GNode *wn)
{
- GNodeListNode *cln;
- GNode *cn;
-
- for (cln = owln; (cn = cln->datum) != wn; cln = cln->next) {
- DEBUG3(MAKE, ".WAIT: add dependency %s%s -> %s\n",
- cn->name, cn->cohort_num, wn->name);
-
- /* XXX: This pattern should be factored out, it repeats often */
- Lst_Append(wn->children, cn);
- wn->unmade++;
- Lst_Append(cn->parents, wn);
- }
+ GNodeListNode *cln;
+ GNode *cn;
+
+ for (cln = owln; (cn = cln->datum) != wn; cln = cln->next) {
+ DEBUG3(MAKE, ".WAIT: add dependency %s%s -> %s\n",
+ cn->name, cn->cohort_num, wn->name);
+
+ /* XXX: This pattern should be factored out, it repeats often */
+ Lst_Append(&wn->children, cn);
+ wn->unmade++;
+ Lst_Append(&cn->parents, wn);
+ }
}
/* Convert .WAIT nodes into dependencies. */
static void
Make_ProcessWait(GNodeList *targs)
{
- GNode *pgn; /* 'parent' node we are examining */
- GNodeListNode *owln; /* Previous .WAIT node */
- GNodeList *examine; /* List of targets to examine */
-
- /*
- * We need all the nodes to have a common parent in order for the
- * .WAIT and .ORDER scheduling to work.
- * Perhaps this should be done earlier...
- */
-
- pgn = GNode_New(".MAIN");
- pgn->flags = REMAKE;
- pgn->type = OP_PHONY | OP_DEPENDS;
- /* Get it displayed in the diag dumps */
- Lst_Prepend(Targ_List(), pgn);
-
- {
- GNodeListNode *ln;
- for (ln = targs->first; ln != NULL; ln = ln->next) {
- GNode *cgn = ln->datum;
+ GNode *pgn; /* 'parent' node we are examining */
+ GNodeListNode *owln; /* Previous .WAIT node */
+ GNodeList examine; /* List of targets to examine */
+
+ /*
+ * We need all the nodes to have a common parent in order for the
+ * .WAIT and .ORDER scheduling to work.
+ * Perhaps this should be done earlier...
+ */
+
+ pgn = GNode_New(".MAIN");
+ pgn->flags = REMAKE;
+ pgn->type = OP_PHONY | OP_DEPENDS;
+ /* Get it displayed in the diag dumps */
+ Lst_Prepend(Targ_List(), pgn);
- Lst_Append(pgn->children, cgn);
- Lst_Append(cgn->parents, pgn);
- pgn->unmade++;
+ {
+ GNodeListNode *ln;
+ for (ln = targs->first; ln != NULL; ln = ln->next) {
+ GNode *cgn = ln->datum;
+
+ Lst_Append(&pgn->children, cgn);
+ Lst_Append(&cgn->parents, pgn);
+ pgn->unmade++;
+ }
}
- }
- /* Start building with the 'dummy' .MAIN' node */
- MakeBuildChild(pgn, NULL);
+ /* Start building with the 'dummy' .MAIN' node */
+ MakeBuildChild(pgn, NULL);
- examine = Lst_New();
- Lst_Append(examine, pgn);
+ Lst_Init(&examine);
+ Lst_Append(&examine, pgn);
- while (!Lst_IsEmpty(examine)) {
- GNodeListNode *ln;
+ while (!Lst_IsEmpty(&examine)) {
+ GNodeListNode *ln;
+
+ pgn = Lst_Dequeue(&examine);
- pgn = Lst_Dequeue(examine);
-
- /* We only want to process each child-list once */
- if (pgn->flags & DONE_WAIT)
- continue;
- pgn->flags |= DONE_WAIT;
- DEBUG1(MAKE, "Make_ProcessWait: examine %s\n", pgn->name);
-
- if (pgn->type & OP_DOUBLEDEP)
- Lst_PrependAll(examine, pgn->cohorts);
-
- owln = pgn->children->first;
- for (ln = pgn->children->first; ln != NULL; ln = ln->next) {
- GNode *cgn = ln->datum;
- if (cgn->type & OP_WAIT) {
- add_wait_dependency(owln, cgn);
- owln = ln;
- } else {
- Lst_Append(examine, cgn);
- }
+ /* We only want to process each child-list once */
+ if (pgn->flags & DONE_WAIT)
+ continue;
+ pgn->flags |= DONE_WAIT;
+ DEBUG1(MAKE, "Make_ProcessWait: examine %s\n", pgn->name);
+
+ if (pgn->type & OP_DOUBLEDEP)
+ Lst_PrependAll(&examine, &pgn->cohorts);
+
+ owln = pgn->children.first;
+ for (ln = pgn->children.first; ln != NULL; ln = ln->next) {
+ GNode *cgn = ln->datum;
+ if (cgn->type & OP_WAIT) {
+ add_wait_dependency(owln, cgn);
+ owln = ln;
+ } else {
+ Lst_Append(&examine, cgn);
+ }
+ }
}
- }
- Lst_Free(examine);
+ Lst_Done(&examine);
}
-/*-
- *-----------------------------------------------------------------------
- * Make_Run --
- * Initialize the nodes to remake and the list of nodes which are
- * ready to be made by doing a breadth-first traversal of the graph
- * starting from the nodes in the given list. Once this traversal
- * is finished, all the 'leaves' of the graph are in the toBeMade
- * queue.
- * Using this queue and the Job module, work back up the graph,
- * calling on MakeStartJobs to keep the job table as full as
- * possible.
+/*
+ * Initialize the nodes to remake and the list of nodes which are ready to
+ * be made by doing a breadth-first traversal of the graph starting from the
+ * nodes in the given list. Once this traversal is finished, all the 'leaves'
+ * of the graph are in the toBeMade queue.
+ *
+ * Using this queue and the Job module, work back up the graph, calling on
+ * MakeStartJobs to keep the job table as full as possible.
*
* Input:
* targs the initial list of targets
@@ -1281,70 +1376,70 @@ Make_ProcessWait(GNodeList *targs)
* The make field of all nodes involved in the creation of the given
* targets is set to 1. The toBeMade list is set to contain all the
* 'leaves' of these subgraphs.
- *-----------------------------------------------------------------------
*/
Boolean
Make_Run(GNodeList *targs)
{
- int errors; /* Number of errors the Job module reports */
+ int errors; /* Number of errors the Job module reports */
- /* Start trying to make the current targets... */
- toBeMade = Lst_New();
+ /* Start trying to make the current targets... */
+ Lst_Init(&toBeMade);
- Make_ExpandUse(targs);
- Make_ProcessWait(targs);
+ Make_ExpandUse(targs);
+ Make_ProcessWait(targs);
- if (DEBUG(MAKE)) {
- debug_printf("#***# full graph\n");
- Targ_PrintGraph(1);
- }
+ if (DEBUG(MAKE)) {
+ debug_printf("#***# full graph\n");
+ Targ_PrintGraph(1);
+ }
- if (opts.queryFlag) {
+ if (opts.queryFlag) {
+ /*
+ * We wouldn't do any work unless we could start some jobs
+ * in the next loop... (we won't actually start any, of
+ * course, this is just to see if any of the targets was out
+ * of date)
+ */
+ return MakeStartJobs();
+ }
/*
- * We wouldn't do any work unless we could start some jobs in the
- * next loop... (we won't actually start any, of course, this is just
- * to see if any of the targets was out of date)
+ * Initialization. At the moment, no jobs are running and until some
+ * get started, nothing will happen since the remaining upward
+ * traversal of the graph is performed by the routines in job.c upon
+ * the finishing of a job. So we fill the Job table as much as we can
+ * before going into our loop.
*/
- return MakeStartJobs();
- }
- /*
- * Initialization. At the moment, no jobs are running and until some
- * get started, nothing will happen since the remaining upward
- * traversal of the graph is performed by the routines in job.c upon
- * the finishing of a job. So we fill the Job table as much as we can
- * before going into our loop.
- */
- (void)MakeStartJobs();
-
- /*
- * Main Loop: The idea here is that the ending of jobs will take
- * care of the maintenance of data structures and the waiting for output
- * will cause us to be idle most of the time while our children run as
- * much as possible. Because the job table is kept as full as possible,
- * the only time when it will be empty is when all the jobs which need
- * running have been run, so that is the end condition of this loop.
- * Note that the Job module will exit if there were any errors unless the
- * keepgoing flag was given.
- */
- while (!Lst_IsEmpty(toBeMade) || jobTokensRunning > 0) {
- Job_CatchOutput();
(void)MakeStartJobs();
- }
- errors = Job_Finish();
+ /*
+ * Main Loop: The idea here is that the ending of jobs will take
+ * care of the maintenance of data structures and the waiting for
+ * output will cause us to be idle most of the time while our
+ * children run as much as possible. Because the job table is kept
+ * as full as possible, the only time when it will be empty is when
+ * all the jobs which need running have been run, so that is the end
+ * condition of this loop. Note that the Job module will exit if
+ * there were any errors unless the keepgoing flag was given.
+ */
+ while (!Lst_IsEmpty(&toBeMade) || jobTokensRunning > 0) {
+ Job_CatchOutput();
+ (void)MakeStartJobs();
+ }
- /*
- * Print the final status of each target. E.g. if it wasn't made
- * because some inferior reported an error.
- */
- DEBUG1(MAKE, "done: errors %d\n", errors);
- if (errors == 0) {
- MakePrintStatusList(targs, &errors);
- if (DEBUG(MAKE)) {
- debug_printf("done: errors %d\n", errors);
- if (errors > 0)
- Targ_PrintGraph(4);
+ errors = Job_Finish();
+
+ /*
+ * Print the final status of each target. E.g. if it wasn't made
+ * because some inferior reported an error.
+ */
+ DEBUG1(MAKE, "done: errors %d\n", errors);
+ if (errors == 0) {
+ MakePrintStatusList(targs, &errors);
+ if (DEBUG(MAKE)) {
+ debug_printf("done: errors %d\n", errors);
+ if (errors > 0)
+ Targ_PrintGraph(4);
+ }
}
- }
- return errors > 0;
+ return errors > 0;
}
diff --git a/make.h b/make.h
index 2803ceac3f95..230700a8dd9d 100644
--- a/make.h
+++ b/make.h
@@ -1,4 +1,4 @@
-/* $NetBSD: make.h,v 1.210 2020/11/16 21:53:10 rillig Exp $ */
+/* $NetBSD: make.h,v 1.242 2021/01/10 21:20:46 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -107,25 +107,25 @@
#endif
#if defined(__GNUC__)
-#define MAKE_GNUC_PREREQ(x, y) \
+#define MAKE_GNUC_PREREQ(x, y) \
((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \
(__GNUC__ > (x)))
#else /* defined(__GNUC__) */
-#define MAKE_GNUC_PREREQ(x, y) 0
+#define MAKE_GNUC_PREREQ(x, y) 0
#endif /* defined(__GNUC__) */
#if MAKE_GNUC_PREREQ(2, 7)
-#define MAKE_ATTR_UNUSED __attribute__((__unused__))
+#define MAKE_ATTR_UNUSED __attribute__((__unused__))
#else
-#define MAKE_ATTR_UNUSED /* delete */
+#define MAKE_ATTR_UNUSED /* delete */
#endif
#if MAKE_GNUC_PREREQ(2, 5)
-#define MAKE_ATTR_DEAD __attribute__((__noreturn__))
+#define MAKE_ATTR_DEAD __attribute__((__noreturn__))
#elif defined(__GNUC__)
-#define MAKE_ATTR_DEAD __volatile
+#define MAKE_ATTR_DEAD __volatile
#else
-#define MAKE_ATTR_DEAD /* delete */
+#define MAKE_ATTR_DEAD /* delete */
#endif
#if MAKE_GNUC_PREREQ(2, 7)
@@ -141,21 +141,29 @@
* A boolean type is defined as an integer, not an enum, for historic reasons.
* The only allowed values are the constants TRUE and FALSE (1 and 0).
*/
-
-#ifdef USE_DOUBLE_BOOLEAN
+#if defined(lint) || defined(USE_C99_BOOLEAN)
+#include <stdbool.h>
+typedef bool Boolean;
+#define FALSE false
+#define TRUE true
+#elif defined(USE_DOUBLE_BOOLEAN)
/* During development, to find type mismatches in function declarations. */
typedef double Boolean;
#define TRUE 1.0
#define FALSE 0.0
#elif defined(USE_UCHAR_BOOLEAN)
-/* During development, to find code that depends on the exact value of TRUE or
- * that stores other values in Boolean variables. */
+/*
+ * During development, to find code that depends on the exact value of TRUE or
+ * that stores other values in Boolean variables.
+ */
typedef unsigned char Boolean;
#define TRUE ((unsigned char)0xFF)
#define FALSE ((unsigned char)0x00)
#elif defined(USE_CHAR_BOOLEAN)
-/* During development, to find code that uses a boolean as array index, via
- * -Wchar-subscripts. */
+/*
+ * During development, to find code that uses a boolean as array index, via
+ * -Wchar-subscripts.
+ */
typedef char Boolean;
#define TRUE ((char)-1)
#define FALSE ((char)0x00)
@@ -186,134 +194,181 @@ typedef int Boolean;
#endif
#if defined(sun) && (defined(__svr4__) || defined(__SVR4))
-#define POSIX_SIGNALS
+# define POSIX_SIGNALS
#endif
+/*
+ * The typical flow of states is:
+ *
+ * The direct successful path:
+ * UNMADE -> BEINGMADE -> MADE.
+ *
+ * The direct error path:
+ * UNMADE -> BEINGMADE -> ERROR.
+ *
+ * The successful path when dependencies need to be made first:
+ * UNMADE -> DEFERRED -> REQUESTED -> BEINGMADE -> MADE.
+ *
+ * A node that has dependencies, and one of the dependencies cannot be made:
+ * UNMADE -> DEFERRED -> ABORTED.
+ *
+ * A node that turns out to be up-to-date:
+ * UNMADE -> BEINGMADE -> UPTODATE.
+ */
typedef enum GNodeMade {
- UNMADE, /* Not examined yet */
- DEFERRED, /* Examined once (building child) */
- REQUESTED, /* on toBeMade list */
- BEINGMADE, /* Target is already being made.
- * Indicates a cycle in the graph. */
- MADE, /* Was out-of-date and has been made */
- UPTODATE, /* Was already up-to-date */
- ERROR, /* An error occurred while it was being
- * made (used only in compat mode) */
- ABORTED /* The target was aborted due to an error
- * making an inferior (compat). */
+ /* Not examined yet. */
+ UNMADE,
+ /* The node has been examined but is not yet ready since its
+ * dependencies have to be made first. */
+ DEFERRED,
+
+ /* The node is on the toBeMade list. */
+ REQUESTED,
+
+ /* The node is already being made. Trying to build a node in this
+ * state indicates a cycle in the graph. */
+ BEINGMADE,
+
+ /* Was out-of-date and has been made. */
+ MADE,
+ /* Was already up-to-date, does not need to be made. */
+ UPTODATE,
+ /* An error occurred while it was being made.
+ * Used only in compat mode. */
+ ERROR,
+ /* The target was aborted due to an error making a dependency.
+ * Used only in compat mode. */
+ ABORTED
} GNodeMade;
-/* The OP_ constants are used when parsing a dependency line as a way of
+/*
+ * The OP_ constants are used when parsing a dependency line as a way of
* communicating to other parts of the program the way in which a target
* should be made.
*
- * Some of the OP_ constants can be combined, others cannot. */
+ * Some of the OP_ constants can be combined, others cannot.
+ */
typedef enum GNodeType {
- OP_NONE = 0,
-
- /* The dependency operator ':' is the most common one. The commands of
- * this node are executed if any child is out-of-date. */
- OP_DEPENDS = 1 << 0,
- /* The dependency operator '!' always executes its commands, even if
- * its children are up-to-date. */
- OP_FORCE = 1 << 1,
- /* The dependency operator '::' behaves like ':', except that it allows
- * multiple dependency groups to be defined. Each of these groups is
- * executed on its own, independently from the others. Each individual
- * dependency group is called a cohort. */
- OP_DOUBLEDEP = 1 << 2,
-
- /* Matches the dependency operators ':', '!' and '::'. */
- OP_OPMASK = OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP,
-
- /* Don't care if the target doesn't exist and can't be created */
- OP_OPTIONAL = 1 << 3,
- /* Use associated commands for parents */
- OP_USE = 1 << 4,
- /* Target is never out of date, but always execute commands anyway.
- * Its time doesn't matter, so it has none...sort of */
- OP_EXEC = 1 << 5,
- /* Ignore non-zero exit status from shell commands when creating the node */
- OP_IGNORE = 1 << 6,
- /* Don't remove the target when interrupted */
- OP_PRECIOUS = 1 << 7,
- /* Don't echo commands when executed */
- OP_SILENT = 1 << 8,
- /* Target is a recursive make so its commands should always be executed
- * when it is out of date, regardless of the state of the -n or -t flags */
- OP_MAKE = 1 << 9,
- /* Target is out-of-date only if any of its children was out-of-date */
- OP_JOIN = 1 << 10,
- /* Assume the children of the node have been already made */
- OP_MADE = 1 << 11,
- /* Special .BEGIN, .END, .INTERRUPT */
- OP_SPECIAL = 1 << 12,
- /* Like .USE, only prepend commands */
- OP_USEBEFORE = 1 << 13,
- /* The node is invisible to its parents. I.e. it doesn't show up in the
- * parents' local variables (.IMPSRC, .ALLSRC). */
- OP_INVISIBLE = 1 << 14,
- /* The node is exempt from normal 'main target' processing in parse.c */
- OP_NOTMAIN = 1 << 15,
- /* Not a file target; run always */
- OP_PHONY = 1 << 16,
- /* Don't search for file in the path */
- OP_NOPATH = 1 << 17,
- /* In a dependency line "target: source1 .WAIT source2", source1 is made
- * first, including its children. Once that is finished, source2 is made,
- * including its children. The .WAIT keyword may appear more than once in
- * a single dependency declaration. */
- OP_WAIT = 1 << 18,
- /* .NOMETA do not create a .meta file */
- OP_NOMETA = 1 << 19,
- /* .META we _do_ want a .meta file */
- OP_META = 1 << 20,
- /* Do not compare commands in .meta file */
- OP_NOMETA_CMP = 1 << 21,
- /* Possibly a submake node */
- OP_SUBMAKE = 1 << 22,
-
- /* Attributes applied by PMake */
-
- /* The node is a transformation rule, such as ".c.o". */
- OP_TRANSFORM = 1 << 31,
- /* Target is a member of an archive */
- /* XXX: How does this differ from OP_ARCHV? */
- OP_MEMBER = 1 << 30,
- /* The node is a library,
- * its name has the form "-l<libname>" */
- OP_LIB = 1 << 29,
- /* The node is an archive member,
- * its name has the form "archive(member)" */
- /* XXX: How does this differ from OP_MEMBER? */
- OP_ARCHV = 1 << 28,
- /* Target has all the commands it should. Used when parsing to catch
- * multiple command groups for a target. Only applies to the dependency
- * operators ':' and '!', but not to '::'. */
- OP_HAS_COMMANDS = 1 << 27,
- /* The special command "..." has been seen. All further commands from
- * this node will be saved on the .END node instead, to be executed at
- * the very end. */
- OP_SAVE_CMDS = 1 << 26,
- /* Already processed by Suff_FindDeps */
- OP_DEPS_FOUND = 1 << 25,
- /* Node found while expanding .ALLSRC */
- OP_MARK = 1 << 24,
-
- OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM
+ OP_NONE = 0,
+
+ /* The dependency operator ':' is the most common one. The commands
+ * of this node are executed if any child is out-of-date. */
+ OP_DEPENDS = 1 << 0,
+ /* The dependency operator '!' always executes its commands, even if
+ * its children are up-to-date. */
+ OP_FORCE = 1 << 1,
+ /* The dependency operator '::' behaves like ':', except that it
+ * allows multiple dependency groups to be defined. Each of these
+ * groups is executed on its own, independently from the others.
+ * Each individual dependency group is called a cohort. */
+ OP_DOUBLEDEP = 1 << 2,
+
+ /* Matches the dependency operators ':', '!' and '::'. */
+ OP_OPMASK = OP_DEPENDS | OP_FORCE | OP_DOUBLEDEP,
+
+ /* Don't care if the target doesn't exist and can't be created. */
+ OP_OPTIONAL = 1 << 3,
+ /* Use associated commands for parents. */
+ OP_USE = 1 << 4,
+ /* Target is never out of date, but always execute commands anyway.
+ * Its time doesn't matter, so it has none...sort of. */
+ OP_EXEC = 1 << 5,
+ /* Ignore non-zero exit status from shell commands when creating the
+ * node. */
+ OP_IGNORE = 1 << 6,
+ /* Don't remove the target when interrupted. */
+ OP_PRECIOUS = 1 << 7,
+ /* Don't echo commands when executed. */
+ OP_SILENT = 1 << 8,
+ /* Target is a recursive make so its commands should always be
+ * executed when it is out of date, regardless of the state of the
+ * -n or -t flags. */
+ OP_MAKE = 1 << 9,
+ /* Target is out-of-date only if any of its children was out-of-date. */
+ OP_JOIN = 1 << 10,
+ /* Assume the children of the node have been already made. */
+ OP_MADE = 1 << 11,
+ /* Special .BEGIN, .END or .INTERRUPT. */
+ OP_SPECIAL = 1 << 12,
+ /* Like .USE, only prepend commands. */
+ OP_USEBEFORE = 1 << 13,
+ /* The node is invisible to its parents. I.e. it doesn't show up in
+ * the parents' local variables (.IMPSRC, .ALLSRC). */
+ OP_INVISIBLE = 1 << 14,
+ /* The node does not become the main target, even if it is the first
+ * target in the first makefile. */
+ OP_NOTMAIN = 1 << 15,
+ /* Not a file target; run always. */
+ OP_PHONY = 1 << 16,
+ /* Don't search for the file in the path. */
+ OP_NOPATH = 1 << 17,
+ /* In a dependency line "target: source1 .WAIT source2", source1 is
+ * made first, including its children. Once that is finished,
+ * source2 is made, including its children. The .WAIT keyword may
+ * appear more than once in a single dependency declaration. */
+ OP_WAIT = 1 << 18,
+ /* .NOMETA do not create a .meta file */
+ OP_NOMETA = 1 << 19,
+ /* .META we _do_ want a .meta file */
+ OP_META = 1 << 20,
+ /* Do not compare commands in .meta file */
+ OP_NOMETA_CMP = 1 << 21,
+ /* Possibly a submake node */
+ OP_SUBMAKE = 1 << 22,
+
+ /* Attributes applied by PMake */
+
+ /* The node is a transformation rule, such as ".c.o". */
+ OP_TRANSFORM = 1 << 30,
+ /* Target is a member of an archive */
+ /* XXX: How does this differ from OP_ARCHV? */
+ OP_MEMBER = 1 << 29,
+ /* The node is a library,
+ * its name has the form "-l<libname>" */
+ OP_LIB = 1 << 28,
+ /* The node is an archive member,
+ * its name has the form "archive(member)" */
+ /* XXX: How does this differ from OP_MEMBER? */
+ OP_ARCHV = 1 << 27,
+ /* Target has all the commands it should. Used when parsing to catch
+ * multiple command groups for a target. Only applies to the
+ * dependency operators ':' and '!', but not to '::'. */
+ OP_HAS_COMMANDS = 1 << 26,
+ /* The special command "..." has been seen. All further commands from
+ * this node will be saved on the .END node instead, to be executed at
+ * the very end. */
+ OP_SAVE_CMDS = 1 << 25,
+ /* Already processed by Suff_FindDeps, to find dependencies from
+ * suffix transformation rules. */
+ OP_DEPS_FOUND = 1 << 24,
+ /* Node found while expanding .ALLSRC */
+ OP_MARK = 1 << 23,
+
+ OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM
} GNodeType;
typedef enum GNodeFlags {
- REMAKE = 0x0001, /* this target needs to be (re)made */
- CHILDMADE = 0x0002, /* children of this target were made */
- FORCE = 0x0004, /* children don't exist, and we pretend made */
- DONE_WAIT = 0x0008, /* Set by Make_ProcessWait() */
- DONE_ORDER = 0x0010, /* Build requested by .ORDER processing */
- FROM_DEPEND = 0x0020, /* Node created from .depend */
- DONE_ALLSRC = 0x0040, /* We do it once only */
- CYCLE = 0x1000, /* Used by MakePrintStatus */
- DONECYCLE = 0x2000, /* Used by MakePrintStatus */
- INTERNAL = 0x4000 /* Internal use only */
+ GNF_NONE = 0,
+ /* this target needs to be (re)made */
+ REMAKE = 0x0001,
+ /* children of this target were made */
+ CHILDMADE = 0x0002,
+ /* children don't exist, and we pretend made */
+ FORCE = 0x0004,
+ /* Set by Make_ProcessWait() */
+ DONE_WAIT = 0x0008,
+ /* Build requested by .ORDER processing */
+ DONE_ORDER = 0x0010,
+ /* Node created from .depend */
+ FROM_DEPEND = 0x0020,
+ /* We do it once only */
+ DONE_ALLSRC = 0x0040,
+ /* Used by MakePrintStatus */
+ CYCLE = 0x1000,
+ /* Used by MakePrintStatus */
+ DONECYCLE = 0x2000,
+ /* Internal use only */
+ INTERNAL = 0x4000
} GNodeFlags;
typedef struct List StringList;
@@ -324,112 +379,118 @@ typedef struct ListNode GNodeListNode;
typedef struct List /* of CachedDir */ SearchPath;
-/* A graph node represents a target that can possibly be made, including its
- * relation to other targets and a lot of other details. */
+/*
+ * A graph node represents a target that can possibly be made, including its
+ * relation to other targets and a lot of other details.
+ */
typedef struct GNode {
- /* The target's name, such as "clean" or "make.c" */
- char *name;
- /* The unexpanded name of a .USE node */
- char *uname;
- /* The full pathname of the file belonging to the target.
- * XXX: What about .PHONY targets? These don't have an associated path. */
- char *path;
-
- /* The type of operator used to define the sources (see the OP flags below).
- * XXX: This looks like a wild mixture of type and flags. */
- GNodeType type;
- GNodeFlags flags;
-
- /* The state of processing on this node */
- GNodeMade made;
- int unmade; /* The number of unmade children */
-
- /* The modification time; 0 means the node does not have a corresponding
- * file; see GNode_IsOODate. */
- time_t mtime;
- struct GNode *youngestChild;
-
- /* The GNodes for which this node is an implied source. May be empty.
- * For example, when there is an inference rule for .c.o, the node for
- * file.c has the node for file.o in this list. */
- GNodeList *implicitParents;
-
- /* The nodes that depend on this one, or in other words, the nodes for
- * which this is a source. */
- GNodeList *parents;
- /* The nodes on which this one depends. */
- GNodeList *children;
-
- /* .ORDER nodes we need made. The nodes that must be made (if they're
- * made) before this node can be made, but that do not enter into the
- * datedness of this node. */
- GNodeList *order_pred;
- /* .ORDER nodes who need us. The nodes that must be made (if they're made
- * at all) after this node is made, but that do not depend on this node,
- * in the normal sense. */
- GNodeList *order_succ;
-
- /* Other nodes of the same name, for the '::' dependency operator. */
- GNodeList *cohorts;
- /* The "#n" suffix for this cohort, or "" for other nodes */
- char cohort_num[8];
- /* The number of unmade instances on the cohorts list */
- int unmade_cohorts;
- /* Pointer to the first instance of a '::' node; only set when on a
- * cohorts list */
- struct GNode *centurion;
-
- /* Last time (sequence number) we tried to make this node */
- unsigned int checked_seqno;
-
- /* The "local" variables that are specific to this target and this target
- * only, such as $@, $<, $?.
- *
- * Also used for the global variable scopes VAR_GLOBAL, VAR_CMDLINE,
- * VAR_INTERNAL, which contain variables with arbitrary names. */
- HashTable /* of Var pointer */ context;
-
- /* The commands to be given to a shell to create this target. */
- StringList *commands;
-
- /* Suffix for the node (determined by Suff_FindDeps and opaque to everyone
- * but the Suff module) */
- struct Suff *suffix;
-
- /* Filename where the GNode got defined */
- /* XXX: What is the lifetime of this string? */
- const char *fname;
- /* Line number where the GNode got defined */
- int lineno;
+ /* The target's name, such as "clean" or "make.c" */
+ char *name;
+ /* The unexpanded name of a .USE node */
+ char *uname;
+ /* The full pathname of the file belonging to the target.
+ * XXX: What about .PHONY targets? These don't have an associated
+ * path. */
+ char *path;
+
+ /* The type of operator used to define the sources (see the OP flags
+ * below).
+ * XXX: This looks like a wild mixture of type and flags. */
+ GNodeType type;
+ GNodeFlags flags;
+
+ /* The state of processing on this node */
+ GNodeMade made;
+ /* The number of unmade children */
+ int unmade;
+
+ /* The modification time; 0 means the node does not have a
+ * corresponding file; see GNode_IsOODate. */
+ time_t mtime;
+ struct GNode *youngestChild;
+
+ /* The GNodes for which this node is an implied source. May be empty.
+ * For example, when there is an inference rule for .c.o, the node for
+ * file.c has the node for file.o in this list. */
+ GNodeList implicitParents;
+
+ /* The nodes that depend on this one, or in other words, the nodes for
+ * which this is a source. */
+ GNodeList parents;
+ /* The nodes on which this one depends. */
+ GNodeList children;
+
+ /* .ORDER nodes we need made. The nodes that must be made (if they're
+ * made) before this node can be made, but that do not enter into the
+ * datedness of this node. */
+ GNodeList order_pred;
+ /* .ORDER nodes who need us. The nodes that must be made (if they're
+ * made at all) after this node is made, but that do not depend on
+ * this node, in the normal sense. */
+ GNodeList order_succ;
+
+ /* Other nodes of the same name, for the '::' dependency operator. */
+ GNodeList cohorts;
+ /* The "#n" suffix for this cohort, or "" for other nodes */
+ char cohort_num[8];
+ /* The number of unmade instances on the cohorts list */
+ int unmade_cohorts;
+ /* Pointer to the first instance of a '::' node; only set when on a
+ * cohorts list */
+ struct GNode *centurion;
+
+ /* Last time (sequence number) we tried to make this node */
+ unsigned int checked_seqno;
+
+ /* The "local" variables that are specific to this target and this
+ * target only, such as $@, $<, $?.
+ *
+ * Also used for the global variable scopes VAR_GLOBAL, VAR_CMDLINE,
+ * VAR_INTERNAL, which contain variables with arbitrary names. */
+ HashTable /* of Var pointer */ vars;
+
+ /* The commands to be given to a shell to create this target. */
+ StringList commands;
+
+ /* Suffix for the node (determined by Suff_FindDeps and opaque to
+ * everyone but the Suff module) */
+ struct Suffix *suffix;
+
+ /* Filename where the GNode got defined */
+ /* XXX: What is the lifetime of this string? */
+ const char *fname;
+ /* Line number where the GNode got defined */
+ int lineno;
} GNode;
/* Error levels for diagnostics during parsing. */
typedef enum ParseErrorLevel {
- /* Exit when the current top-level makefile has been parsed completely. */
- PARSE_FATAL = 1,
- /* Print "warning"; may be upgraded to fatal by the -w option. */
- PARSE_WARNING,
- /* Informational, mainly used during development of makefiles. */
- PARSE_INFO
+ /* Exit when the current top-level makefile has been parsed
+ * completely. */
+ PARSE_FATAL = 1,
+ /* Print "warning"; may be upgraded to fatal by the -w option. */
+ PARSE_WARNING,
+ /* Informational, mainly used during development of makefiles. */
+ PARSE_INFO
} ParseErrorLevel;
/*
* Values returned by Cond_EvalLine and Cond_EvalCondition.
*/
typedef enum CondEvalResult {
- COND_PARSE, /* Parse the next lines */
- COND_SKIP, /* Skip the next lines */
- COND_INVALID /* Not a conditional statement */
+ COND_PARSE, /* Parse the next lines */
+ COND_SKIP, /* Skip the next lines */
+ COND_INVALID /* Not a conditional statement */
} CondEvalResult;
/* Names of the variables that are "local" to a specific target. */
-#define TARGET "@" /* Target of dependency */
-#define OODATE "?" /* All out-of-date sources */
-#define ALLSRC ">" /* All sources */
-#define IMPSRC "<" /* Source implied by transformation */
-#define PREFIX "*" /* Common prefix */
-#define ARCHIVE "!" /* Archive in "archive(member)" syntax */
-#define MEMBER "%" /* Member in "archive(member)" syntax */
+#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
@@ -444,43 +505,36 @@ extern Boolean doing_depend;
/* .DEFAULT rule */
extern GNode *defaultNode;
-/* Variables defined internally by make which should not override those set
- * by makefiles. */
+/*
+ * Variables defined internally by make which should not override those set
+ * by makefiles.
+ */
extern GNode *VAR_INTERNAL;
/* Variables defined in a global context, e.g in the Makefile itself. */
extern GNode *VAR_GLOBAL;
/* Variables defined on the command line. */
extern GNode *VAR_CMDLINE;
-/* Value returned by Var_Parse when an error is encountered. It actually
- * points to an empty string, so naive callers needn't worry about it. */
+/*
+ * Value returned by Var_Parse when an error is encountered. It actually
+ * points to an empty string, so naive callers needn't worry about it.
+ */
extern char var_Error[];
/* The time at the start of this whole process */
extern time_t now;
/*
- * If FALSE (the default behavior), undefined subexpressions in a variable
- * expression are discarded. If TRUE (only during variable assignments using
- * the ':=' assignment operator, no matter how deeply nested), they are
- * preserved and possibly expanded later when the variable from the
- * subexpression has been defined.
- *
- * 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)".
+ * The list of directories to search when looking for targets (set by the
+ * special target .PATH).
*/
-extern Boolean preserveUndefined;
-
-/* The list of directories to search when looking for targets (set by the
- * special target .PATH). */
-extern SearchPath *dirSearchPath;
+extern SearchPath dirSearchPath;
/* Used for .include "...". */
extern SearchPath *parseIncPath;
-/* Used for .include <...>, for the built-in sys.mk and makefiles from the
- * command line arguments. */
+/*
+ * Used for .include <...>, for the built-in sys.mk and makefiles from the
+ * command line arguments.
+ */
extern SearchPath *sysIncPath;
/* The default for sysIncPath. */
extern SearchPath *defSysIncPath;
@@ -488,7 +542,7 @@ extern SearchPath *defSysIncPath;
/* Startup directory */
extern char curdir[];
/* The basename of the program name, suffixed with [n] for sub-makes. */
-extern char *progname;
+extern const char *progname;
/* Name of the .depend makefile */
extern char *makeDependfile;
/* If we replaced environ, this will be non-NULL. */
@@ -503,156 +557,152 @@ extern int makelevel;
#define vFork() ((getpid() == myPid) ? vfork() : fork())
extern pid_t myPid;
-#define MAKEFLAGS ".MAKEFLAGS"
-#define MAKEOVERRIDES ".MAKEOVERRIDES"
-#define MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX" /* prefix for job target output */
-#define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */
-#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all makefiles already loaded */
-#define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */
+#define MAKEFLAGS ".MAKEFLAGS"
+#define MAKEOVERRIDES ".MAKEOVERRIDES"
+/* prefix when printing the target of a job */
+#define MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX"
+#define MAKE_EXPORTED ".MAKE.EXPORTED" /* exported variables */
+#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all loaded makefiles */
+#define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */
#define MAKE_MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE"
-#define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */
+#define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */
#define MAKE_MODE ".MAKE.MODE"
#ifndef MAKE_LEVEL_ENV
# define MAKE_LEVEL_ENV "MAKELEVEL"
#endif
typedef enum DebugFlags {
- DEBUG_NONE = 0,
- DEBUG_ARCH = 1 << 0,
- DEBUG_COND = 1 << 1,
- DEBUG_CWD = 1 << 2,
- DEBUG_DIR = 1 << 3,
- DEBUG_ERROR = 1 << 4,
- DEBUG_FOR = 1 << 5,
- DEBUG_GRAPH1 = 1 << 6,
- DEBUG_GRAPH2 = 1 << 7,
- DEBUG_GRAPH3 = 1 << 8,
- DEBUG_HASH = 1 << 9,
- DEBUG_JOB = 1 << 10,
- DEBUG_LOUD = 1 << 11,
- DEBUG_MAKE = 1 << 12,
- DEBUG_META = 1 << 13,
- DEBUG_PARSE = 1 << 14,
- DEBUG_SCRIPT = 1 << 15,
- DEBUG_SHELL = 1 << 16,
- DEBUG_SUFF = 1 << 17,
- DEBUG_TARG = 1 << 18,
- DEBUG_VAR = 1 << 19,
- DEBUG_ALL = (1 << 20) - 1
+ DEBUG_NONE = 0,
+ DEBUG_ARCH = 1 << 0,
+ DEBUG_COND = 1 << 1,
+ DEBUG_CWD = 1 << 2,
+ DEBUG_DIR = 1 << 3,
+ DEBUG_ERROR = 1 << 4,
+ DEBUG_FOR = 1 << 5,
+ DEBUG_GRAPH1 = 1 << 6,
+ DEBUG_GRAPH2 = 1 << 7,
+ DEBUG_GRAPH3 = 1 << 8,
+ DEBUG_HASH = 1 << 9,
+ DEBUG_JOB = 1 << 10,
+ DEBUG_LOUD = 1 << 11,
+ DEBUG_MAKE = 1 << 12,
+ DEBUG_META = 1 << 13,
+ DEBUG_PARSE = 1 << 14,
+ DEBUG_SCRIPT = 1 << 15,
+ DEBUG_SHELL = 1 << 16,
+ DEBUG_SUFF = 1 << 17,
+ DEBUG_TARG = 1 << 18,
+ DEBUG_VAR = 1 << 19,
+ DEBUG_ALL = (1 << 20) - 1
} DebugFlags;
-#define CONCAT(a,b) a##b
+#define CONCAT(a, b) a##b
-#define DEBUG(module) (opts.debug & CONCAT(DEBUG_,module))
+#define DEBUG(module) ((opts.debug & CONCAT(DEBUG_, module)) != 0)
void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
-#define DEBUG0(module, text) \
- if (!DEBUG(module)) (void)0; \
- else debug_printf("%s", text)
+#define DEBUG_IMPL(module, args) \
+ do { \
+ if (DEBUG(module)) \
+ debug_printf args; \
+ } while (/*CONSTCOND*/ 0)
+#define DEBUG0(module, text) \
+ DEBUG_IMPL(module, ("%s", text))
#define DEBUG1(module, fmt, arg1) \
- if (!DEBUG(module)) (void)0; \
- else debug_printf(fmt, arg1)
-
+ DEBUG_IMPL(module, (fmt, arg1))
#define DEBUG2(module, fmt, arg1, arg2) \
- if (!DEBUG(module)) (void)0; \
- else debug_printf(fmt, arg1, arg2)
-
+ DEBUG_IMPL(module, (fmt, arg1, arg2))
#define DEBUG3(module, fmt, arg1, arg2, arg3) \
- if (!DEBUG(module)) (void)0; \
- else debug_printf(fmt, arg1, arg2, arg3)
-
+ DEBUG_IMPL(module, (fmt, arg1, arg2, arg3))
#define DEBUG4(module, fmt, arg1, arg2, arg3, arg4) \
- if (!DEBUG(module)) (void)0; \
- else debug_printf(fmt, arg1, arg2, arg3, arg4)
-
+ DEBUG_IMPL(module, (fmt, arg1, arg2, arg3, arg4))
#define DEBUG5(module, fmt, arg1, arg2, arg3, arg4, arg5) \
- if (!DEBUG(module)) (void)0; \
- else debug_printf(fmt, arg1, arg2, arg3, arg4, arg5)
+ DEBUG_IMPL(module, (fmt, arg1, arg2, arg3, arg4, arg5))
typedef enum PrintVarsMode {
- PVM_NONE,
- PVM_UNEXPANDED,
- PVM_EXPANDED
+ PVM_NONE,
+ PVM_UNEXPANDED,
+ PVM_EXPANDED
} PrintVarsMode;
/* Command line options */
typedef struct CmdOpts {
- /* -B: whether we are make compatible */
- Boolean compatMake;
+ /* -B: whether we are make compatible */
+ Boolean compatMake;
- /* -d: debug control: There is one bit per module. It is up to the
- * module what debug information to print. */
- DebugFlags debug;
+ /* -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;
+ /* -df: debug output is written here - default stderr */
+ FILE *debug_file;
- /* -dL: lint mode
- *
- * Runs make in strict mode, with additional checks and better error
- * handling. */
- Boolean lint;
+ /* -dL: lint mode
+ *
+ * Runs make in strict mode, with additional checks and better error
+ * handling. */
+ Boolean strict;
- /* -dV: for the -V option, print unexpanded variable values */
- Boolean debugVflag;
+ /* -dV: for the -V option, print unexpanded variable values */
+ Boolean debugVflag;
- /* -e: check environment variables before global variables */
- Boolean checkEnvFirst;
+ /* -e: check environment variables before global variables */
+ Boolean checkEnvFirst;
- /* -f: the makefiles to read */
- StringList *makefiles;
+ /* -f: the makefiles to read */
+ StringList makefiles;
- /* -i: if true, ignore all errors from shell commands */
- Boolean ignoreErrors;
+ /* -i: if true, ignore all errors from shell commands */
+ Boolean ignoreErrors;
- /* -j: the maximum number of jobs that can run in parallel;
- * this is coordinated with the submakes */
- int maxJobs;
+ /* -j: the maximum number of jobs that can run in parallel;
+ * this is coordinated with the submakes */
+ int maxJobs;
- /* -k: if true, continue on unaffected portions of the graph when an
- * error occurs in one portion */
- Boolean keepgoing;
+ /* -k: if true and an error occurs while making a node, continue
+ * making nodes that do not depend on the erroneous node */
+ Boolean keepgoing;
- /* -N: execute no commands from the targets */
- Boolean noRecursiveExecute;
+ /* -N: execute no commands from the targets */
+ Boolean noRecursiveExecute;
- /* -n: execute almost no commands from the targets */
- Boolean noExecute;
+ /* -n: execute almost no commands from the targets */
+ Boolean noExecute;
- /* -q: if true, we aren't supposed to really make anything, just see if
- * the targets are out-of-date */
- Boolean queryFlag;
+ /* -q: if true, we aren't supposed to really make anything, just see
+ * if the targets are out-of-date */
+ Boolean queryFlag;
- /* -r: raw mode, without loading the builtin rules. */
- Boolean noBuiltins;
+ /* -r: raw mode, without loading the builtin rules. */
+ Boolean noBuiltins;
- /* -s: don't echo the shell commands before executing them */
- Boolean beSilent;
+ /* -s: don't echo the shell commands before executing them */
+ Boolean beSilent;
- /* -t: touch the targets if they are out-of-date, but don't actually
- * make them */
- Boolean touchFlag;
+ /* -t: touch the targets if they are out-of-date, but don't actually
+ * make them */
+ Boolean touchFlag;
- /* -[Vv]: print expanded or unexpanded selected variables */
- PrintVarsMode printVars;
- /* -[Vv]: the variables to print */
- StringList *variables;
+ /* -[Vv]: print expanded or unexpanded selected variables */
+ PrintVarsMode printVars;
+ /* -[Vv]: the variables to print */
+ StringList variables;
- /* -W: if true, makefile parsing warnings are treated as errors */
- Boolean parseWarnFatal;
+ /* -W: if true, makefile parsing warnings are treated as errors */
+ Boolean parseWarnFatal;
- /* -w: print Entering and Leaving for submakes */
- Boolean enterFlag;
+ /* -w: print Entering and Leaving for submakes */
+ Boolean enterFlag;
- /* -X: if true, do not export variables set on the command line to the
- * environment. */
- Boolean varNoExportEnv;
+ /* -X: if true, do not export variables set on the command line to the
+ * environment. */
+ Boolean varNoExportEnv;
- /* The target names specified on the command line.
- * Used to resolve .if make(...) statements. */
- StringList *create;
+ /* The target names specified on the command line.
+ * Used to resolve .if make(...) statements. */
+ StringList create;
} CmdOpts;
@@ -681,13 +731,37 @@ Boolean GNode_ShouldExecute(GNode *gn);
MAKE_INLINE Boolean
GNode_IsTarget(const GNode *gn)
{
- return (gn->type & OP_OPMASK) != 0;
+ return (gn->type & OP_OPMASK) != 0;
}
MAKE_INLINE const char *
GNode_Path(const GNode *gn)
{
- return gn->path != NULL ? gn->path : gn->name;
+ return gn->path != NULL ? gn->path : gn->name;
+}
+
+MAKE_INLINE Boolean
+GNode_IsWaitingFor(const GNode *gn)
+{
+ return (gn->flags & REMAKE) && gn->made <= REQUESTED;
+}
+
+MAKE_INLINE Boolean
+GNode_IsReady(const GNode *gn)
+{
+ return gn->made > DEFERRED;
+}
+
+MAKE_INLINE Boolean
+GNode_IsDone(const GNode *gn)
+{
+ return gn->made >= MADE;
+}
+
+MAKE_INLINE Boolean
+GNode_IsError(const GNode *gn)
+{
+ return gn->made == ERROR || gn->made == ABORTED;
}
MAKE_INLINE const char *
@@ -728,9 +802,9 @@ GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); }
#endif
#if defined(SYSV)
-#define KILLPG(pid, sig) kill(-(pid), (sig))
+#define KILLPG(pid, sig) kill(-(pid), (sig))
#else
-#define KILLPG(pid, sig) killpg((pid), (sig))
+#define KILLPG(pid, sig) killpg((pid), (sig))
#endif
MAKE_INLINE Boolean
@@ -751,36 +825,42 @@ ch_toupper(char ch) { return (char)toupper((unsigned char)ch); }
MAKE_INLINE void
cpp_skip_whitespace(const char **pp)
{
- while (ch_isspace(**pp))
- (*pp)++;
+ while (ch_isspace(**pp))
+ (*pp)++;
}
MAKE_INLINE void
cpp_skip_hspace(const char **pp)
{
- while (**pp == ' ' || **pp == '\t')
- (*pp)++;
+ while (**pp == ' ' || **pp == '\t')
+ (*pp)++;
}
MAKE_INLINE void
pp_skip_whitespace(char **pp)
{
- while (ch_isspace(**pp))
- (*pp)++;
+ while (ch_isspace(**pp))
+ (*pp)++;
}
MAKE_INLINE void
pp_skip_hspace(char **pp)
{
- while (**pp == ' ' || **pp == '\t')
- (*pp)++;
+ while (**pp == ' ' || **pp == '\t')
+ (*pp)++;
}
-#ifdef MAKE_NATIVE
+#if defined(lint)
+# define MAKE_RCSID(id) extern void do_not_define_rcsid(void)
+#elif defined(MAKE_NATIVE)
# include <sys/cdefs.h>
-# ifndef lint
-# define MAKE_RCSID(id) __RCSID(id)
-# endif
+# define MAKE_RCSID(id) __RCSID(id)
+#elif defined(MAKE_ALL_IN_ONE) && defined(__COUNTER__)
+# define MAKE_RCSID_CONCAT(x, y) CONCAT(x, y)
+# define MAKE_RCSID(id) static volatile char \
+ MAKE_RCSID_CONCAT(rcsid_, __COUNTER__)[] = id
+#elif defined(MAKE_ALL_IN_ONE)
+# define MAKE_RCSID(id) extern void do_not_define_rcsid(void)
#else
# define MAKE_RCSID(id) static volatile char rcsid[] = id
#endif
diff --git a/make_malloc.c b/make_malloc.c
index 9a8570b6a2b0..62cbb0a5cc07 100644
--- a/make_malloc.c
+++ b/make_malloc.c
@@ -1,4 +1,4 @@
-/* $NetBSD: make_malloc.c,v 1.23 2020/10/05 19:27:47 rillig Exp $ */
+/* $NetBSD: make_malloc.c,v 1.24 2020/12/07 22:37:18 rillig Exp $ */
/*-
* Copyright (c) 2009 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
#include "make.h"
-MAKE_RCSID("$NetBSD: make_malloc.c,v 1.23 2020/10/05 19:27:47 rillig Exp $");
+MAKE_RCSID("$NetBSD: make_malloc.c,v 1.24 2020/12/07 22:37:18 rillig Exp $");
#ifndef USE_EMALLOC
@@ -61,8 +61,7 @@ bmake_strdup(const char *str)
char *p;
len = strlen(str) + 1;
- if ((p = malloc(len)) == NULL)
- enomem();
+ p = bmake_malloc(len);
return memcpy(p, str, len);
}
diff --git a/make_malloc.h b/make_malloc.h
index 551495fb6266..4dacc924c40f 100644
--- a/make_malloc.h
+++ b/make_malloc.h
@@ -1,4 +1,4 @@
-/* $NetBSD: make_malloc.h,v 1.13 2020/11/10 00:32:12 rillig Exp $ */
+/* $NetBSD: make_malloc.h,v 1.15 2020/12/30 10:03:16 rillig Exp $ */
/*-
* Copyright (c) 2009 The NetBSD Foundation, Inc.
@@ -41,14 +41,16 @@ char *bmake_strldup(const char *, size_t);
char *bmake_strsedup(const char *, const char *);
-/* Thin wrapper around free(3) to avoid the extra function call in case
+/*
+ * 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. */
+ * since only environment variables need to be freed, but not others.
+ */
MAKE_INLINE void
bmake_free(void *p)
{
- if (p != NULL)
- free(p);
+ if (p != NULL)
+ free(p);
}
diff --git a/meta.c b/meta.c
index e77fa3d73a7b..feff26846e4d 100644
--- a/meta.c
+++ b/meta.c
@@ -1,4 +1,4 @@
-/* $NetBSD: meta.c,v 1.144 2020/11/15 12:02:44 rillig Exp $ */
+/* $NetBSD: meta.c,v 1.168 2021/01/10 21:20:46 rillig Exp $ */
/*
* Implement 'meta' mode.
@@ -55,9 +55,9 @@ char * dirname(char *);
#endif
static BuildMon Mybm; /* for compat */
-static StringList *metaBailiwick; /* our scope of control */
+static StringList metaBailiwick = LST_INIT; /* our scope of control */
static char *metaBailiwickStr; /* string storage for the list */
-static StringList *metaIgnorePaths; /* paths we deliberately ignore */
+static StringList metaIgnorePaths = LST_INIT; /* paths we deliberately ignore */
static char *metaIgnorePathsStr; /* string storage for the list */
#ifndef MAKE_META_IGNORE_PATHS
@@ -79,14 +79,14 @@ static Boolean metaEnv = FALSE; /* don't save env unless asked */
static Boolean metaVerbose = FALSE;
static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */
static Boolean metaIgnorePatterns = FALSE; /* do we need to do pattern matches */
-static Boolean metaIgnoreFilter = FALSE; /* do we have more complex filtering? */
+static Boolean metaIgnoreFilter = FALSE; /* do we have more complex filtering? */
static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */
static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */
extern Boolean forceJobs;
extern char **environ;
-#define MAKE_META_PREFIX ".MAKE.META.PREFIX"
+#define MAKE_META_PREFIX ".MAKE.META.PREFIX"
#ifndef N2U
# define N2U(n, u) (((n) + ((u) - 1)) / (u))
@@ -128,7 +128,7 @@ meta_open_filemon(BuildMon *pbm)
pbm->mon_fd = -1;
pbm->filemon = NULL;
- if (!useFilemon || !pbm->mfp)
+ if (!useFilemon || pbm->mfp == NULL)
return;
pbm->filemon = filemon_open();
@@ -222,7 +222,7 @@ eat_dots(char *buf, size_t bufsz, int dots)
do {
cp = strstr(buf, eat);
- if (cp) {
+ if (cp != NULL) {
cp2 = cp + eatlen;
if (dots == 2 && cp > buf) {
do {
@@ -235,7 +235,7 @@ eat_dots(char *buf, size_t bufsz, int dots)
return; /* can't happen? */
}
}
- } while (cp);
+ } while (cp != NULL);
}
static char *
@@ -258,9 +258,9 @@ meta_name(char *mname, size_t mnamelen,
* So we use realpath() just to get the dirname, and leave the
* basename as given to us.
*/
- if ((cp = strrchr(tname, '/'))) {
- if (cached_realpath(tname, buf)) {
- if ((rp = strrchr(buf, '/'))) {
+ if ((cp = strrchr(tname, '/')) != NULL) {
+ if (cached_realpath(tname, buf) != NULL) {
+ if ((rp = strrchr(buf, '/')) != NULL) {
rp++;
cp++;
if (strcmp(cp, rp) != 0)
@@ -316,25 +316,22 @@ meta_name(char *mname, size_t mnamelen,
* Return true if running ${.MAKE}
* Bypassed if target is flagged .MAKE
*/
-static int
-is_submake(void *cmdp, void *gnp)
+static Boolean
+is_submake(const char *cmd, GNode *gn)
{
static const char *p_make = NULL;
static size_t p_len;
- char *cmd = cmdp;
- GNode *gn = gnp;
char *mp = NULL;
char *cp;
char *cp2;
- int rc = 0; /* keep looking */
+ Boolean rc = FALSE;
if (p_make == NULL) {
- void *dontFreeIt;
- p_make = Var_Value(".MAKE", gn, &dontFreeIt);
+ p_make = Var_Value(".MAKE", gn).str;
p_len = strlen(p_make);
}
cp = strchr(cmd, '$');
- if ((cp)) {
+ if (cp != NULL) {
(void)Var_Subst(cmd, gn, VARE_WANTRES, &mp);
/* TODO: handle errors */
cmd = mp;
@@ -346,17 +343,17 @@ is_submake(void *cmdp, void *gnp)
case ' ':
case '\t':
case '\n':
- rc = 1;
+ rc = TRUE;
break;
}
- if (cp2 > cmd && rc > 0) {
+ if (cp2 > cmd && rc) {
switch (cp2[-1]) {
case ' ':
case '\t':
case '\n':
break;
default:
- rc = 0; /* no match */
+ rc = FALSE; /* no match */
break;
}
}
@@ -365,32 +362,38 @@ is_submake(void *cmdp, void *gnp)
return rc;
}
-typedef struct meta_file_s {
- FILE *fp;
- GNode *gn;
-} meta_file_t;
+static Boolean
+any_is_submake(GNode *gn)
+{
+ StringListNode *ln;
+
+ for (ln = gn->commands.first; ln != NULL; ln = ln->next)
+ if (is_submake(ln->datum, gn))
+ return TRUE;
+ return FALSE;
+}
static void
-printCMD(const char *cmd, meta_file_t *mfp)
+printCMD(const char *cmd, FILE *fp, GNode *gn)
{
char *cmd_freeIt = NULL;
- if (strchr(cmd, '$')) {
- (void)Var_Subst(cmd, mfp->gn, VARE_WANTRES, &cmd_freeIt);
+ if (strchr(cmd, '$') != NULL) {
+ (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmd_freeIt);
/* TODO: handle errors */
cmd = cmd_freeIt;
}
- fprintf(mfp->fp, "CMD %s\n", cmd);
+ fprintf(fp, "CMD %s\n", cmd);
free(cmd_freeIt);
}
static void
-printCMDs(GNode *gn, meta_file_t *mf)
+printCMDs(GNode *gn, FILE *fp)
{
- GNodeListNode *ln;
+ StringListNode *ln;
- for (ln = gn->commands->first; ln != NULL; ln = ln->next)
- printCMD(ln->datum, mf);
+ for (ln = gn->commands.first; ln != NULL; ln = ln->next)
+ printCMD(ln->datum, fp, gn);
}
/*
@@ -404,7 +407,7 @@ printCMDs(GNode *gn, meta_file_t *mf)
} \
return FALSE; \
} \
-} while (0)
+} while (/*CONSTCOND*/0)
/*
@@ -412,7 +415,7 @@ printCMDs(GNode *gn, meta_file_t *mf)
*/
static Boolean
meta_needed(GNode *gn, const char *dname,
- char *objdir, int verbose)
+ char *objdir_realpath, Boolean verbose)
{
struct cached_stat cst;
@@ -431,14 +434,14 @@ meta_needed(GNode *gn, const char *dname,
}
/* Check if there are no commands to execute. */
- if (Lst_IsEmpty(gn->commands)) {
+ if (Lst_IsEmpty(&gn->commands)) {
if (verbose)
debug_printf("Skipping meta for %s: no commands\n", gn->name);
return FALSE;
}
if ((gn->type & (OP_META|OP_SUBMAKE)) == OP_SUBMAKE) {
/* OP_SUBMAKE is a bit too aggressive */
- if (Lst_ForEachUntil(gn->commands, is_submake, gn)) {
+ if (any_is_submake(gn)) {
DEBUG1(META, "Skipping meta for %s: .SUBMAKE\n", gn->name);
return FALSE;
}
@@ -452,8 +455,8 @@ meta_needed(GNode *gn, const char *dname,
}
/* make sure these are canonical */
- if (cached_realpath(dname, objdir))
- dname = objdir;
+ if (cached_realpath(dname, objdir_realpath) != NULL)
+ dname = objdir_realpath;
/* If we aren't in the object directory, don't create a meta file. */
if (!metaCurdirOk && strcmp(curdir, dname) == 0) {
@@ -469,25 +472,24 @@ meta_needed(GNode *gn, const char *dname,
static FILE *
meta_create(BuildMon *pbm, GNode *gn)
{
- meta_file_t mf;
+ FILE *fp;
char buf[MAXPATHLEN];
- char objdir[MAXPATHLEN];
+ char objdir_realpath[MAXPATHLEN];
char **ptr;
- const char *dname;
+ FStr dname;
const char *tname;
char *fname;
const char *cp;
- void *objdir_freeIt;
- mf.fp = NULL;
+ fp = NULL;
- dname = Var_Value(".OBJDIR", gn, &objdir_freeIt);
+ dname = Var_Value(".OBJDIR", gn);
tname = GNode_VarTarget(gn);
- /* if this succeeds objdir is realpath of dname */
- if (!meta_needed(gn, dname, objdir, TRUE))
+ /* if this succeeds objdir_realpath is realpath of dname */
+ if (!meta_needed(gn, dname.str, objdir_realpath, TRUE))
goto out;
- dname = objdir;
+ dname.str = objdir_realpath;
if (metaVerbose) {
char *mp;
@@ -495,16 +497,12 @@ meta_create(BuildMon *pbm, GNode *gn)
/* Describe the target we are building */
(void)Var_Subst("${" MAKE_META_PREFIX "}", gn, VARE_WANTRES, &mp);
/* TODO: handle errors */
- if (*mp)
+ if (mp[0] != '\0')
fprintf(stdout, "%s\n", mp);
free(mp);
}
/* Get the basename of the target */
- if ((cp = strrchr(tname, '/')) == NULL) {
- cp = tname;
- } else {
- cp++;
- }
+ cp = str_basename(tname);
fflush(stdout);
@@ -513,34 +511,32 @@ meta_create(BuildMon *pbm, GNode *gn)
goto out;
fname = meta_name(pbm->meta_fname, sizeof pbm->meta_fname,
- dname, tname, objdir);
+ dname.str, tname, objdir_realpath);
#ifdef DEBUG_META_MODE
DEBUG1(META, "meta_create: %s\n", fname);
#endif
- if ((mf.fp = fopen(fname, "w")) == NULL)
+ if ((fp = fopen(fname, "w")) == NULL)
err(1, "Could not open meta file '%s'", fname);
- fprintf(mf.fp, "# Meta data file %s\n", fname);
+ fprintf(fp, "# Meta data file %s\n", fname);
- mf.gn = gn;
+ printCMDs(gn, fp);
- printCMDs(gn, &mf);
-
- fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof buf));
- fprintf(mf.fp, "TARGET %s\n", tname);
+ fprintf(fp, "CWD %s\n", getcwd(buf, sizeof buf));
+ fprintf(fp, "TARGET %s\n", tname);
cp = GNode_VarOodate(gn);
- if (cp && *cp) {
- fprintf(mf.fp, "OODATE %s\n", cp);
+ if (cp != NULL && *cp != '\0') {
+ fprintf(fp, "OODATE %s\n", cp);
}
if (metaEnv) {
for (ptr = environ; *ptr != NULL; ptr++)
- fprintf(mf.fp, "ENV %s\n", *ptr);
+ fprintf(fp, "ENV %s\n", *ptr);
}
- fprintf(mf.fp, "-- command output --\n");
- fflush(mf.fp);
+ fprintf(fp, "-- command output --\n");
+ fflush(fp);
Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL);
@@ -550,9 +546,9 @@ meta_create(BuildMon *pbm, GNode *gn)
gn->type |= OP_SILENT;
}
out:
- bmake_free(objdir_freeIt);
+ FStr_Done(&dname);
- return mf.fp;
+ return fp;
}
static Boolean
@@ -583,7 +579,7 @@ meta_init(void)
#define get_mode_bf(bf, token) \
- if ((cp = strstr(make_mode, token))) \
+ if ((cp = strstr(make_mode, token)) != NULL) \
bf = boolValue(cp + sizeof (token) - 1)
/*
@@ -592,24 +588,24 @@ meta_init(void)
void
meta_mode_init(const char *make_mode)
{
- static int once = 0;
+ static Boolean once = FALSE;
char *cp;
- void *freeIt;
+ FStr value;
useMeta = TRUE;
useFilemon = TRUE;
writeMeta = TRUE;
- if (make_mode) {
- if (strstr(make_mode, "env"))
+ if (make_mode != NULL) {
+ if (strstr(make_mode, "env") != NULL)
metaEnv = TRUE;
- if (strstr(make_mode, "verb"))
+ if (strstr(make_mode, "verb") != NULL)
metaVerbose = TRUE;
- if (strstr(make_mode, "read"))
+ if (strstr(make_mode, "read") != NULL)
writeMeta = FALSE;
- if (strstr(make_mode, "nofilemon"))
+ if (strstr(make_mode, "nofilemon") != NULL)
useFilemon = FALSE;
- if (strstr(make_mode, "ignore-cmd"))
+ if (strstr(make_mode, "ignore-cmd") != NULL)
metaIgnoreCMDs = TRUE;
if (useFilemon)
get_mode_bf(filemonMissing, "missing-filemon=");
@@ -628,39 +624,37 @@ meta_mode_init(const char *make_mode)
}
if (once)
return;
- once = 1;
+ once = TRUE;
memset(&Mybm, 0, sizeof Mybm);
/*
* We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
*/
- metaBailiwick = Lst_New();
(void)Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}",
VAR_GLOBAL, VARE_WANTRES, &metaBailiwickStr);
/* TODO: handle errors */
- str2Lst_Append(metaBailiwick, metaBailiwickStr);
+ str2Lst_Append(&metaBailiwick, metaBailiwickStr);
/*
* We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS}
*/
- metaIgnorePaths = Lst_New();
Var_Append(MAKE_META_IGNORE_PATHS,
"/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL);
(void)Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}",
VAR_GLOBAL, VARE_WANTRES, &metaIgnorePathsStr);
/* TODO: handle errors */
- str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr);
+ str2Lst_Append(&metaIgnorePaths, metaIgnorePathsStr);
/*
* We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS}
*/
- freeIt = NULL;
- if (Var_Value(MAKE_META_IGNORE_PATTERNS, VAR_GLOBAL, &freeIt)) {
+ value = Var_Value(MAKE_META_IGNORE_PATTERNS, VAR_GLOBAL);
+ if (value.str != NULL) {
metaIgnorePatterns = TRUE;
- bmake_free(freeIt);
+ FStr_Done(&value);
}
- freeIt = NULL;
- if (Var_Value(MAKE_META_IGNORE_FILTER, VAR_GLOBAL, &freeIt)) {
+ value = Var_Value(MAKE_META_IGNORE_FILTER, VAR_GLOBAL);
+ if (value.str != NULL) {
metaIgnoreFilter = TRUE;
- bmake_free(freeIt);
+ FStr_Done(&value);
}
}
@@ -710,7 +704,7 @@ meta_job_child(Job *job)
}
if (pbm->mfp != NULL) {
close(fileno(pbm->mfp));
- if (useFilemon && pbm->filemon) {
+ if (useFilemon && pbm->filemon != NULL) {
pid_t pid;
pid = getpid();
@@ -733,7 +727,7 @@ meta_job_parent(Job *job, pid_t pid)
} else {
pbm = &Mybm;
}
- if (useFilemon && pbm->filemon) {
+ if (useFilemon && pbm->filemon != NULL) {
filemon_setpid_parent(pbm->filemon, pid);
}
#endif
@@ -750,7 +744,7 @@ meta_job_fd(Job *job)
} else {
pbm = &Mybm;
}
- if (useFilemon && pbm->filemon) {
+ if (useFilemon && pbm->filemon != NULL) {
return filemon_readfd(pbm->filemon);
}
#endif
@@ -768,7 +762,7 @@ meta_job_event(Job *job)
} else {
pbm = &Mybm;
}
- if (useFilemon && pbm->filemon) {
+ if (useFilemon && pbm->filemon != NULL) {
return filemon_process(pbm->filemon);
}
#endif
@@ -776,7 +770,7 @@ meta_job_event(Job *job)
}
void
-meta_job_error(Job *job, GNode *gn, int flags, int status)
+meta_job_error(Job *job, GNode *gn, Boolean ignerr, int status)
{
char cwd[MAXPATHLEN];
BuildMon *pbm;
@@ -790,11 +784,9 @@ meta_job_error(Job *job, GNode *gn, int flags, int status)
}
if (pbm->mfp != NULL) {
fprintf(pbm->mfp, "\n*** Error code %d%s\n",
- status,
- (flags & JOB_IGNERR) ?
- "(ignored)" : "");
+ status, ignerr ? "(ignored)" : "");
}
- if (gn) {
+ if (gn != NULL) {
Var_Set(".ERROR_TARGET", GNode_Path(gn), VAR_GLOBAL);
}
getcwd(cwd, sizeof cwd);
@@ -826,15 +818,16 @@ meta_job_output(Job *job, char *cp, const char *nl)
(void)Var_Subst("${" MAKE_META_PREFIX "}",
VAR_GLOBAL, VARE_WANTRES, &meta_prefix);
/* TODO: handle errors */
- if ((cp2 = strchr(meta_prefix, '$')))
+ if ((cp2 = strchr(meta_prefix, '$')) != NULL)
meta_prefix_len = (size_t)(cp2 - meta_prefix);
else
meta_prefix_len = strlen(meta_prefix);
}
if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) {
- cp = strchr(cp+1, '\n');
- if (!cp++)
+ cp = strchr(cp + 1, '\n');
+ if (cp == NULL)
return;
+ cp++;
}
}
fprintf(pbm->mfp, "%s%s", cp, nl);
@@ -854,7 +847,7 @@ meta_cmd_finish(void *pbmp)
pbm = &Mybm;
#ifdef USE_FILEMON
- if (pbm->filemon) {
+ if (pbm->filemon != NULL) {
while (filemon_process(pbm->filemon) > 0)
continue;
if (filemon_close(pbm->filemon) == -1)
@@ -898,11 +891,9 @@ meta_job_finish(Job *job)
void
meta_finish(void)
{
- if (metaBailiwick != NULL)
- Lst_Free(metaBailiwick);
+ Lst_Done(&metaBailiwick);
free(metaBailiwickStr);
- if (metaIgnorePaths != NULL)
- Lst_Free(metaIgnorePaths);
+ Lst_Done(&metaIgnorePaths);
free(metaIgnorePathsStr);
}
@@ -936,32 +927,39 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
newsz = ROUNDUP((size_t)fs.st_size, BUFSIZ);
if (newsz <= bufsz)
return x; /* truncated */
- DEBUG2(META, "growing buffer %zu -> %zu\n", bufsz, newsz);
+ DEBUG2(META, "growing buffer %u -> %u\n",
+ (unsigned)bufsz, (unsigned)newsz);
p = bmake_realloc(buf, newsz);
- if (p) {
- *bufp = buf = p;
- *szp = bufsz = newsz;
- /* fetch the rest */
- if (fgets(&buf[x], (int)bufsz - x, fp) == NULL)
- return x; /* truncated! */
- goto check_newline;
- }
+ *bufp = buf = p;
+ *szp = bufsz = newsz;
+ /* fetch the rest */
+ if (fgets(&buf[x], (int)bufsz - x, fp) == NULL)
+ return x; /* truncated! */
+ goto check_newline;
}
}
return 0;
}
-/* Lst_ForEachUntil wants 1 to stop search */
-static int
-prefix_match(void *p, void *q)
+static Boolean
+prefix_match(const char *prefix, const char *path)
{
- const char *prefix = p;
- const char *path = q;
size_t n = strlen(prefix);
return strncmp(path, prefix, n) == 0;
}
+static Boolean
+has_any_prefix(const char *path, StringList *prefixes)
+{
+ StringListNode *ln;
+
+ for (ln = prefixes->first; ln != NULL; ln = ln->next)
+ if (prefix_match(ln->datum, path))
+ return TRUE;
+ return FALSE;
+}
+
/* See if the path equals prefix or starts with "prefix/". */
static Boolean
path_starts_with(const char *path, const char *prefix)
@@ -973,7 +971,7 @@ path_starts_with(const char *path, const char *prefix)
return path[n] == '\0' || path[n] == '/';
}
-static int
+static Boolean
meta_ignore(GNode *gn, const char *p)
{
char fname[MAXPATHLEN];
@@ -983,7 +981,7 @@ meta_ignore(GNode *gn, const char *p)
if (*p == '/') {
cached_realpath(p, fname); /* clean it up */
- if (Lst_ForEachUntil(metaIgnorePaths, prefix_match, fname)) {
+ if (has_any_prefix(fname, &metaIgnorePaths)) {
#ifdef DEBUG_META_MODE
DEBUG1(META, "meta_oodate: ignoring path: %s\n", p);
#endif
@@ -999,7 +997,7 @@ meta_ignore(GNode *gn, const char *p)
expr = "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}";
(void)Var_Subst(expr, gn, VARE_WANTRES, &pm);
/* TODO: handle errors */
- if (*pm) {
+ if (pm[0] != '\0') {
#ifdef DEBUG_META_MODE
DEBUG1(META, "meta_oodate: ignoring pattern: %s\n", p);
#endif
@@ -1043,7 +1041,7 @@ meta_ignore(GNode *gn, const char *p)
* if we detect this we want to reproduce it.
* Setting oodate TRUE will have that effect.
*/
-#define CHECK_VALID_META(p) if (!(p && *p)) { \
+#define CHECK_VALID_META(p) if (!(p != NULL && *p != '\0')) { \
warnx("%s: %d: malformed", fname, lineno); \
oodate = TRUE; \
continue; \
@@ -1052,7 +1050,7 @@ meta_ignore(GNode *gn, const char *p)
#define DEQUOTE(p) if (*p == '\'') { \
char *ep; \
p++; \
- if ((ep = strchr(p, '\''))) \
+ if ((ep = strchr(p, '\'')) != NULL) \
*ep = '\0'; \
}
@@ -1080,32 +1078,30 @@ meta_oodate(GNode *gn, Boolean oodate)
char fname1[MAXPATHLEN];
char fname2[MAXPATHLEN];
char fname3[MAXPATHLEN];
- const char *dname;
+ FStr dname;
const char *tname;
char *p;
- char *cp;
char *link_src;
char *move_target;
static size_t cwdlen = 0;
static size_t tmplen = 0;
FILE *fp;
Boolean needOODATE = FALSE;
- StringList *missingFiles;
+ StringList missingFiles;
Boolean have_filemon = FALSE;
- void *objdir_freeIt;
if (oodate)
return oodate; /* we're done */
- dname = Var_Value(".OBJDIR", gn, &objdir_freeIt);
+ dname = Var_Value(".OBJDIR", gn);
tname = GNode_VarTarget(gn);
/* if this succeeds fname3 is realpath of dname */
- if (!meta_needed(gn, dname, fname3, FALSE))
+ if (!meta_needed(gn, dname.str, fname3, FALSE))
goto oodate_out;
- dname = fname3;
+ dname.str = fname3;
- missingFiles = Lst_New();
+ Lst_Init(&missingFiles);
/*
* We need to check if the target is out-of-date. This includes
@@ -1115,7 +1111,7 @@ meta_oodate(GNode *gn, Boolean oodate)
*/
Make_DoAllVar(gn);
- meta_name(fname, sizeof fname, dname, tname, dname);
+ meta_name(fname, sizeof fname, dname.str, tname, dname.str);
#ifdef DEBUG_META_MODE
DEBUG1(META, "meta_oodate: %s\n", fname);
@@ -1152,7 +1148,7 @@ meta_oodate(GNode *gn, Boolean oodate)
/* we want to track all the .meta we read */
Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
- cmdNode = gn->commands->first;
+ cmdNode = gn->commands.first;
while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) {
lineno++;
if (buf[x - 1] == '\n')
@@ -1221,8 +1217,7 @@ meta_oodate(GNode *gn, Boolean oodate)
CHECK_VALID_META(p);
pid = atoi(p);
if (pid > 0 && pid != lastpid) {
- const char *ldir;
- void *tp;
+ FStr ldir;
if (lastpid > 0) {
/* We need to remember these. */
@@ -1232,15 +1227,15 @@ meta_oodate(GNode *gn, Boolean oodate)
snprintf(lcwd_vname, sizeof lcwd_vname, LCWD_VNAME_FMT, pid);
snprintf(ldir_vname, sizeof ldir_vname, LDIR_VNAME_FMT, pid);
lastpid = pid;
- ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp);
- if (ldir) {
- strlcpy(latestdir, ldir, sizeof latestdir);
- bmake_free(tp);
+ ldir = Var_Value(ldir_vname, VAR_GLOBAL);
+ if (ldir.str != NULL) {
+ strlcpy(latestdir, ldir.str, sizeof latestdir);
+ FStr_Done(&ldir);
}
- ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp);
- if (ldir) {
- strlcpy(lcwd, ldir, sizeof lcwd);
- bmake_free(tp);
+ ldir = Var_Value(lcwd_vname, VAR_GLOBAL);
+ if (ldir.str != NULL) {
+ strlcpy(lcwd, ldir.str, sizeof lcwd);
+ FStr_Done(&ldir);
}
}
/* Skip past the pid. */
@@ -1305,13 +1300,15 @@ meta_oodate(GNode *gn, Boolean oodate)
* the src as for 'R'ead
* and the target as for 'W'rite.
*/
- cp = p; /* save this for a second */
- /* now get target */
- if (strsep(&p, " ") == NULL)
- continue;
- CHECK_VALID_META(p);
- move_target = p;
- p = cp;
+ {
+ char *cp = p; /* save this for a second */
+ /* now get target */
+ if (strsep(&p, " ") == NULL)
+ continue;
+ CHECK_VALID_META(p);
+ move_target = p;
+ p = cp;
+ }
/* 'L' and 'M' put single quotes around the args */
DEQUOTE(p);
DEQUOTE(move_target);
@@ -1319,12 +1316,12 @@ meta_oodate(GNode *gn, Boolean oodate)
case 'D': /* unlink */
if (*p == '/') {
/* remove any missingFiles entries that match p */
- StringListNode *ln = missingFiles->first;
+ StringListNode *ln = missingFiles.first;
while (ln != NULL) {
StringListNode *next = ln->next;
if (path_starts_with(ln->datum, p)) {
free(ln->datum);
- Lst_Remove(missingFiles, ln);
+ Lst_Remove(&missingFiles, ln);
}
ln = next;
}
@@ -1368,14 +1365,14 @@ meta_oodate(GNode *gn, Boolean oodate)
if (*p != '/')
break;
- if (Lst_IsEmpty(metaBailiwick))
+ if (Lst_IsEmpty(&metaBailiwick))
break;
/* ignore cwd - normal dependencies handle those */
if (strncmp(p, cwd, cwdlen) == 0)
break;
- if (!Lst_ForEachUntil(metaBailiwick, prefix_match, p))
+ if (!has_any_prefix(p, &metaBailiwick))
break;
/* tmpdir might be within */
@@ -1384,13 +1381,13 @@ meta_oodate(GNode *gn, Boolean oodate)
/* ignore anything containing the string "tmp" */
/* XXX: The arguments to strstr must be swapped. */
- if ((strstr("tmp", p)))
+ if (strstr("tmp", p) != NULL)
break;
if ((link_src != NULL && cached_lstat(p, &cst) < 0) ||
(link_src == NULL && cached_stat(p, &cst) < 0)) {
if (!meta_ignore(gn, p))
- append_if_new(missingFiles, p);
+ append_if_new(&missingFiles, p);
}
break;
check_link_src:
@@ -1418,13 +1415,13 @@ meta_oodate(GNode *gn, Boolean oodate)
char *sdirs[4];
char **sdp;
int sdx = 0;
- int found = 0;
+ Boolean found = FALSE;
if (*p == '/') {
sdirs[sdx++] = p; /* done */
} else {
if (strcmp(".", p) == 0)
- continue; /* no point */
+ continue; /* no point */
/* Check vs latestdir */
snprintf(fname1, sizeof fname1, "%s/%s", latestdir, p);
@@ -1443,13 +1440,13 @@ meta_oodate(GNode *gn, Boolean oodate)
}
sdirs[sdx++] = NULL;
- for (sdp = sdirs; *sdp && !found; sdp++) {
+ for (sdp = sdirs; *sdp != NULL && !found; sdp++) {
#ifdef DEBUG_META_MODE
DEBUG3(META, "%s: %d: looking for: %s\n",
fname, lineno, *sdp);
#endif
if (cached_stat(*sdp, &cst) == 0) {
- found = 1;
+ found = TRUE;
p = *sdp;
}
}
@@ -1473,7 +1470,7 @@ meta_oodate(GNode *gn, Boolean oodate)
* A referenced file outside of CWD is missing.
* We cannot catch every eventuality here...
*/
- append_if_new(missingFiles, p);
+ append_if_new(&missingFiles, p);
}
}
if (buf[0] == 'E') {
@@ -1496,12 +1493,13 @@ meta_oodate(GNode *gn, Boolean oodate)
fname, lineno);
oodate = TRUE;
} else {
+ const char *cp;
char *cmd = cmdNode->datum;
Boolean hasOODATE = FALSE;
- if (strstr(cmd, "$?"))
+ if (strstr(cmd, "$?") != NULL)
hasOODATE = TRUE;
- else if ((cp = strstr(cmd, ".OODATE"))) {
+ else if ((cp = strstr(cmd, ".OODATE")) != NULL) {
/* check for $[{(].OODATE[:)}] */
if (cp > cmd + 2 && cp[-2] == '$')
hasOODATE = TRUE;
@@ -1514,7 +1512,7 @@ meta_oodate(GNode *gn, Boolean oodate)
(void)Var_Subst(cmd, gn, VARE_WANTRES|VARE_UNDEFERR, &cmd);
/* TODO: handle errors */
- if ((cp = strchr(cmd, '\n'))) {
+ if ((cp = strchr(cmd, '\n')) != NULL) {
int n;
/*
@@ -1534,8 +1532,8 @@ meta_oodate(GNode *gn, Boolean oodate)
warnx("%s: %d: line truncated at %u", fname, lineno, x);
break;
}
- cp = strchr(++cp, '\n');
- } while (cp);
+ cp = strchr(cp + 1, '\n');
+ } while (cp != NULL);
if (buf[x - 1] == '\n')
buf[x - 1] = '\0';
}
@@ -1571,9 +1569,9 @@ meta_oodate(GNode *gn, Boolean oodate)
}
fclose(fp);
- if (!Lst_IsEmpty(missingFiles)) {
+ if (!Lst_IsEmpty(&missingFiles)) {
DEBUG2(META, "%s: missing files: %s...\n",
- fname, (char *)missingFiles->first->datum);
+ fname, (char *)missingFiles.first->datum);
oodate = TRUE;
}
if (!oodate && !have_filemon && filemonMissing) {
@@ -1582,10 +1580,11 @@ meta_oodate(GNode *gn, Boolean oodate)
}
} else {
if (writeMeta && (metaMissing || (gn->type & OP_META))) {
- cp = NULL;
+ const char *cp = NULL;
/* if target is in .CURDIR we do not need a meta file */
- if (gn->path && (cp = strrchr(gn->path, '/')) && cp > gn->path) {
+ if (gn->path != NULL && (cp = strrchr(gn->path, '/')) != NULL &&
+ (cp > gn->path)) {
if (strncmp(curdir, gn->path, (size_t)(cp - gn->path)) != 0) {
cp = NULL; /* not in .CURDIR */
}
@@ -1598,7 +1597,7 @@ meta_oodate(GNode *gn, Boolean oodate)
}
}
- Lst_Destroy(missingFiles, free);
+ Lst_DoneCall(&missingFiles, free);
if (oodate && needOODATE) {
/*
@@ -1611,7 +1610,7 @@ meta_oodate(GNode *gn, Boolean oodate)
}
oodate_out:
- bmake_free(objdir_freeIt);
+ FStr_Done(&dname);
return oodate;
}
@@ -1661,7 +1660,7 @@ meta_compat_parent(pid_t child)
close(childPipe[1]); /* child side */
outfd = childPipe[0];
#ifdef USE_FILEMON
- metafd = Mybm.filemon ? filemon_readfd(Mybm.filemon) : -1;
+ metafd = Mybm.filemon != NULL ? filemon_readfd(Mybm.filemon) : -1;
#else
metafd = -1;
#endif
@@ -1686,7 +1685,7 @@ meta_compat_parent(pid_t child)
err(1, "select");
}
- if (outfd != -1 && FD_ISSET(outfd, &readfds)) do {
+ if (outfd != -1 && FD_ISSET(outfd, &readfds) != 0) do {
/* XXX this is not line-buffered */
ssize_t nread = read(outfd, buf, sizeof buf - 1);
if (nread == -1)
@@ -1700,12 +1699,12 @@ meta_compat_parent(pid_t child)
fflush(stdout);
buf[nread] = '\0';
meta_job_output(NULL, buf, "");
- } while (0);
- if (metafd != -1 && FD_ISSET(metafd, &readfds)) {
+ } while (/*CONSTCOND*/0);
+ if (metafd != -1 && FD_ISSET(metafd, &readfds) != 0) {
if (meta_job_event(NULL) <= 0)
metafd = -1;
}
}
}
-#endif /* USE_META */
+#endif /* USE_META */
diff --git a/meta.h b/meta.h
index a4feb9e2d324..1fc8910d3b65 100644
--- a/meta.h
+++ b/meta.h
@@ -1,4 +1,4 @@
-/* $NetBSD: meta.h,v 1.8 2020/10/19 23:43:55 rillig Exp $ */
+/* $NetBSD: meta.h,v 1.9 2020/12/10 20:49:11 rillig Exp $ */
/*
* Things needed for 'meta' mode.
@@ -48,7 +48,7 @@ void meta_job_child(struct Job *);
void meta_job_parent(struct Job *, pid_t);
int meta_job_fd(struct Job *);
int meta_job_event(struct Job *);
-void meta_job_error(struct Job *, GNode *, int, int);
+void meta_job_error(struct Job *, GNode *, Boolean, int);
void meta_job_output(struct Job *, char *, const char *);
int meta_cmd_finish(void *);
int meta_job_finish(struct Job *);
diff --git a/metachar.h b/metachar.h
index ced0648fa19e..3c0780a31b8b 100644
--- a/metachar.h
+++ b/metachar.h
@@ -1,4 +1,4 @@
-/* $NetBSD: metachar.h,v 1.12 2020/11/10 00:32:12 rillig Exp $ */
+/* $NetBSD: metachar.h,v 1.13 2021/01/10 21:20:46 rillig Exp $ */
/*-
* Copyright (c) 2015 The NetBSD Foundation, Inc.
@@ -35,7 +35,7 @@
extern unsigned char _metachar[];
-#define is_shell_metachar(c) _metachar[(c) & 0x7f]
+#define is_shell_metachar(c) (_metachar[(c) & 0x7f] != 0)
MAKE_INLINE int
needshell(const char *cmd)
diff --git a/mk/ChangeLog b/mk/ChangeLog
index 80abde7ea93f..4c74e668faf1 100644
--- a/mk/ChangeLog
+++ b/mk/ChangeLog
@@ -1,3 +1,42 @@
+2021-01-06 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * install-mk (MK_VERSION): 20210101
+
+ * dirdeps.mk: first time we are read, just use TARGET_SPEC for
+ _DEP_TARGET_SPEC
+
+2020-12-22 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * sys.mk (MAKE_SHELL): use ${.SHELL:Ush}
+ and use := when setting SHELL
+
+2020-12-21 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * install-mk (MK_VERSION): 20201221
+
+ * dirdeps-options.mk: latest bmake allows only one arg to .undef
+
+2020-12-11 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * dirdeps-targets.mk: allow for "." in DIRDEPS_TARGETS_DIRS
+ so that any directory can be treated as a target.
+
+2020-11-26 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * install-mk (MK_VERSION): 20201126
+
+ * own.mk: use .MAKE.{UID,GID} if available.
+
+ * init.mk: suppress _SKIP_BUILD warning if doing -V
+
+2020-11-20 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * install-mk (MK_VERSION): 20201120
+
+ * init.mk: rename LEVEL0_TARGETS to DIRDEPS_BUILD_LEVEL0_TARGETS
+
+ * dirdeps-targets.mk: fix typo in comment
+
2020-11-06 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20201106
diff --git a/mk/dirdeps-options.mk b/mk/dirdeps-options.mk
index 74f54a4cf665..4e907e66141e 100644
--- a/mk/dirdeps-options.mk
+++ b/mk/dirdeps-options.mk
@@ -1,4 +1,4 @@
-# $Id: dirdeps-options.mk,v 1.17 2020/08/07 01:57:38 sjg Exp $
+# $Id: dirdeps-options.mk,v 1.18 2020/12/22 18:10:34 sjg Exp $
#
# @(#) Copyright (c) 2018-2020, Simon J. Gerraty
#
@@ -59,7 +59,8 @@ DIRDEPS_OPTIONS_QUALIFIER_LIST ?= \
# note that we need to include $o in the variable _o$o
# to ensure correct evaluation.
.for o in ${DIRDEPS_OPTIONS}
-.undef _o$o _v$o
+.undef _o$o
+.undef _v$o
.for x in ${DIRDEPS_OPTIONS_QUALIFIER_LIST}
.if defined(MK_$o.$x)
_o$o ?= MK_$o.$x
diff --git a/mk/dirdeps-targets.mk b/mk/dirdeps-targets.mk
index 73dcf3639d3b..6201efe1e402 100644
--- a/mk/dirdeps-targets.mk
+++ b/mk/dirdeps-targets.mk
@@ -1,5 +1,5 @@
# RCSid:
-# $Id: dirdeps-targets.mk,v 1.22 2020/08/15 18:00:11 sjg Exp $
+# $Id: dirdeps-targets.mk,v 1.24 2020/12/11 18:15:43 sjg Exp $
#
# @(#) Copyright (c) 2019-2020 Simon J. Gerraty
#
@@ -41,6 +41,7 @@
.-include <local.dirdeps-targets.mk>
# for DIRDEPS_BUILD this is how we prime the pump
+# include . to allow any directory to work as a target
DIRDEPS_TARGETS_DIRS ?= targets targets/pseudo
# these prefixes can modify how we behave
# they need to be stripped when looking for target dirs
@@ -76,7 +77,7 @@ DIRDEPS_TARGETS_MACHINE_LIST += \
DIRDEPS_TARGETS_MACHINE_LIST := ${DIRDEPS_TARGETS_MACHINE_LIST:O:u}
# raw Makefile.depend* list
-tdeps != 'cd' ${SRCTOP} && 'ls' -1 ${tdirs:O:u:@d@$d/${.MAKE.DEPENDFILE_PREFIX}*@} 2> /dev/null; echo
+tdeps != 'cd' ${SRCTOP} && 'ls' -1 ${tdirs:O:u:@d@$d/${.MAKE.DEPENDFILE_PREFIX}*@:S,^./,,} 2> /dev/null; echo
.if ${DEBUG_DIRDEPS_TARGETS:U:Mdep*} != ""
.info tdeps=${tdeps}
.endif
@@ -135,7 +136,7 @@ DIRDEPS := ${DIRDEPS:O:u}
# if we got DIRDEPS get to work
.if !empty(DIRDEPS)
DIRDEPS.dirs := ${DIRDEPS:S,^,${SRCTOP}/,:@d@${exists($d):?$d:${d:R}}@}
-# some targets what to tweak options we might want to process now
+# some targets want to tweak options we might want to process now
.for m in ${DIRDEPS.dirs:S,$,/Makefile.dirdeps.options,}
.-include <$m>
.endfor
diff --git a/mk/dirdeps.mk b/mk/dirdeps.mk
index 237d4c7e74aa..c8f03df01c9c 100644
--- a/mk/dirdeps.mk
+++ b/mk/dirdeps.mk
@@ -1,6 +1,6 @@
-# $Id: dirdeps.mk,v 1.130 2020/11/02 00:34:30 sjg Exp $
+# $Id: dirdeps.mk,v 1.131 2021/01/07 00:57:51 sjg Exp $
-# Copyright (c) 2010-2020, Simon J. Gerraty
+# Copyright (c) 2010-2021, Simon J. Gerraty
# Copyright (c) 2010-2018, Juniper Networks, Inc.
# All rights reserved.
#
@@ -265,24 +265,9 @@ N_notmachine := ${.MAKE.DEPENDFILE_PREFERENCE:E:N*${MACHINE}*:${M_ListToSkip}}
# if we were included recursively _DEP_TARGET_SPEC should be valid.
.if empty(_DEP_TARGET_SPEC)
-# we may or may not have included a dependfile yet
-.if defined(.INCLUDEDFROMFILE)
-_last_dependfile := ${.INCLUDEDFROMFILE:M${.MAKE.DEPENDFILE_PREFIX}*}
-.else
-_last_dependfile := ${.MAKE.MAKEFILES:M*/${.MAKE.DEPENDFILE_PREFIX}*:[-1]}
-.endif
-.if ${_debug_reldir:U0}
-.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _last_dependfile='${_last_dependfile}'
-.endif
-
-.if empty(_last_dependfile) || ${_last_dependfile:E:${N_notmachine}} == ""
-# this is all we have to work with
-DEP_MACHINE = ${TARGET_MACHINE:U${MACHINE}}
-_DEP_TARGET_SPEC := ${DEP_TARGET_SPEC}
-.else
-_DEP_TARGET_SPEC = ${_last_dependfile:${M_dep_qual_fixes:ts:}:E}
-.endif
-.if !empty(_last_dependfile)
+# if not, just use TARGET_SPEC
+_DEP_TARGET_SPEC := ${TARGET_SPEC}
+.if ${.INCLUDEDFROMFILE:U:M${.MAKE.DEPENDFILE_PREFIX}*} != ""
# record that we've read dependfile for this
_dirdeps_checked.${_CURDIR}.${TARGET_SPEC}:
.endif
diff --git a/mk/init.mk b/mk/init.mk
index 0dae997beb54..8f6f8f2fbaf4 100644
--- a/mk/init.mk
+++ b/mk/init.mk
@@ -1,4 +1,4 @@
-# $Id: init.mk,v 1.21 2020/08/19 17:51:53 sjg Exp $
+# $Id: init.mk,v 1.25 2020/11/27 17:59:46 sjg Exp $
#
# @(#) Copyright (c) 2002, Simon J. Gerraty
#
@@ -65,14 +65,15 @@ CC_PIC?= -DPIC
CXX_PIC?= ${CC_PIC}
PROFFLAGS?= -DGPROF -DPROF
-# targets that are ok at level 0
-LEVEL0_TARGETS += clean* destory*
-M_ListToSkip= O:u:S,^,N,:ts:
-
-.if ${.MAKE.LEVEL:U1} == 0 && ${MK_DIRDEPS_BUILD:Uno} == "yes" && ${.TARGETS:Uall:${LEVEL0_TARGETS:${M_ListToSkip}}} != ""
+.if ${.MAKE.LEVEL:U1} == 0 && ${MK_DIRDEPS_BUILD:Uno} == "yes"
+# 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
+.endif
.if !defined(.PARSEDIR)
# no-op is the best we can do if not bmake.
@@ -80,13 +81,15 @@ _SKIP_BUILD = not building at level 0
.endif
# define this once for consistency
-.if empty(_SKIP_BUILD)
+.if !defined(_SKIP_BUILD)
# beforebuild is a hook for things that must be done early
all: beforebuild .WAIT realbuild
.else
all: .PHONY
+.if !empty(_SKIP_BUILD) && ${.MAKEFLAGS:M-V} == ""
.warning ${_SKIP_BUILD}
.endif
+.endif
beforebuild:
realbuild:
diff --git a/mk/install-mk b/mk/install-mk
index cd396f7d5331..aa3f360b43ae 100644..100755
--- a/mk/install-mk
+++ b/mk/install-mk
@@ -55,7 +55,7 @@
# Simon J. Gerraty <sjg@crufty.net>
# RCSid:
-# $Id: install-mk,v 1.184 2020/11/08 05:47:56 sjg Exp $
+# $Id: install-mk,v 1.190 2021/01/07 00:58:42 sjg Exp $
#
# @(#) Copyright (c) 1994 Simon J. Gerraty
#
@@ -70,7 +70,7 @@
# sjg@crufty.net
#
-MK_VERSION=20201106
+MK_VERSION=20210101
OWNER=
GROUP=
MODE=444
diff --git a/mk/meta.subdir.mk b/mk/meta.subdir.mk
index 39cf875d6b77..d9caae4edbcc 100644
--- a/mk/meta.subdir.mk
+++ b/mk/meta.subdir.mk
@@ -1,4 +1,4 @@
-# $Id: meta.subdir.mk,v 1.12 2020/08/19 17:51:53 sjg Exp $
+# $Id: meta.subdir.mk,v 1.13 2021/01/05 22:24:37 sjg Exp $
#
# @(#) Copyright (c) 2010, Simon J. Gerraty
@@ -17,7 +17,7 @@
.if !defined(NO_SUBDIR) && !empty(SUBDIR)
.if make(destroy*) || make(clean*)
.MAKE.MODE = compat
-.if !commands(destroy)
+.if !commands(obj)
.-include <bsd.obj.mk>
.endif
.elif ${.MAKE.LEVEL} == 0
diff --git a/mk/mkopt.sh b/mk/mkopt.sh
index 4a42c0ddf122..4a42c0ddf122 100755..100644
--- a/mk/mkopt.sh
+++ b/mk/mkopt.sh
diff --git a/mk/own.mk b/mk/own.mk
index b20b9e5e2c35..63322297420b 100644
--- a/mk/own.mk
+++ b/mk/own.mk
@@ -1,4 +1,4 @@
-# $Id: own.mk,v 1.41 2020/08/19 17:51:53 sjg Exp $
+# $Id: own.mk,v 1.42 2020/11/27 18:00:08 sjg Exp $
.if !target(__${.PARSEFILE}__)
__${.PARSEFILE}__:
@@ -125,10 +125,10 @@ OPTIONS_DEFAULT_DEPENDENT+= \
.if ${MK_INSTALL_AS_USER} == "yes"
# We ignore this if user is root.
-_uid!= id -u
+_uid:= ${.MAKE.UID:U${id -u:L:sh}}
.if ${_uid} != 0
.if !defined(USERGRP)
-USERGRP!= id -g
+USERGRP:= ${.MAKE.GID:U${id -g:L:sh}}
.export USERGRP
.endif
.for x in BIN CONF DOC INC INFO FILES KMOD LIB MAN NLS PROG SHARE
diff --git a/mk/sys.mk b/mk/sys.mk
index b21dfa8b4ebf..7ef8f724ef10 100644
--- a/mk/sys.mk
+++ b/mk/sys.mk
@@ -1,4 +1,4 @@
-# $Id: sys.mk,v 1.51 2020/08/19 17:51:53 sjg Exp $
+# $Id: sys.mk,v 1.52 2020/12/22 20:44:24 sjg Exp $
#
# @(#) Copyright (c) 2003-2009, Simon J. Gerraty
#
@@ -118,8 +118,8 @@ ROOT_GROUP != sed -n /:0:/s/:.*//p /etc/group
unix ?= We run ${_HOST_OSNAME}.
# We need a Bourne/POSIX shell
-MAKE_SHELL ?= sh
-SHELL ?= ${MAKE_SHELL}
+MAKE_SHELL ?= ${.SHELL:Ush}
+SHELL := ${MAKE_SHELL}
# A race condition in mkdir, means that it can bail if another
# process made a dir that mkdir expected to.
diff --git a/nonints.h b/nonints.h
index f089b39ca50e..41194f104f13 100644
--- a/nonints.h
+++ b/nonints.h
@@ -1,4 +1,4 @@
-/* $NetBSD: nonints.h,v 1.162 2020/11/16 21:48:18 rillig Exp $ */
+/* $NetBSD: nonints.h,v 1.186 2020/12/28 00:46:24 rillig Exp $ */
/*-
* Copyright (c) 1988, 1989, 1990, 1993
@@ -86,7 +86,7 @@ Boolean Arch_LibOODate(GNode *);
Boolean Arch_IsLib(GNode *);
/* compat.c */
-int Compat_RunCommand(const char *, GNode *);
+int Compat_RunCommand(const char *, GNode *, StringListNode *);
void Compat_Run(GNodeList *);
void Compat_Make(GNode *, GNode *);
@@ -96,6 +96,21 @@ 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)
+{ return Lst_New(); }
+
+void SearchPath_Free(SearchPath *);
+
/* for.c */
int For_Eval(const char *);
Boolean For_Accum(const char *);
@@ -109,7 +124,6 @@ void JobReapChild(pid_t, WAIT_T, Boolean);
/* main.c */
Boolean GetBooleanVar(const char *, Boolean);
void Main_ParseArgLine(const char *);
-void MakeMode(const char *);
char *Cmd_Exec(const char *, const char **);
void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
@@ -127,42 +141,96 @@ void Parse_Init(void);
void Parse_End(void);
typedef enum VarAssignOp {
- VAR_NORMAL, /* = */
- VAR_SUBST, /* := */
- VAR_SHELL, /* != or :sh= */
- VAR_APPEND, /* += */
- VAR_DEFAULT /* ?= */
+ 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 */
+ char *varname; /* unexpanded */
+ VarAssignOp op;
+ const char *value; /* unexpanded */
} VarAssign;
-typedef char *(*NextBufProc)(void *, size_t *);
+typedef char *(*ReadMoreProc)(void *, size_t *);
void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
Boolean Parse_IsVar(const char *, VarAssign *out_var);
void Parse_DoVar(VarAssign *, GNode *);
void Parse_AddIncludeDir(const char *);
void Parse_File(const char *, int);
-void Parse_SetInput(const char *, int, int, NextBufProc, void *);
-GNodeList *Parse_MainName(void);
+void Parse_SetInput(const char *, int, int, ReadMoreProc, void *);
+void Parse_MainName(GNodeList *);
int Parse_GetFatals(void);
/* str.c */
+
+/* A read-only string that may need to be freed after use. */
+typedef struct FStr {
+ const char *str;
+ void *freeIt;
+} FStr;
+
+/* A modifiable string that may need to be freed after use. */
+typedef struct MFStr {
+ char *str;
+ void *freeIt;
+} MFStr;
+
typedef struct Words {
- char **words;
- size_t len;
- void *freeIt;
+ char **words;
+ size_t len;
+ void *freeIt;
} Words;
+/* Return a string that is the sole owner of str. */
+MAKE_INLINE FStr
+FStr_InitOwn(char *str)
+{
+ return (FStr){ str, str };
+}
+
+/* Return a string that refers to the shared str. */
+MAKE_INLINE FStr
+FStr_InitRefer(const char *str)
+{
+ return (FStr){ str, NULL };
+}
+
+MAKE_INLINE void
+FStr_Done(FStr *fstr)
+{
+ free(fstr->freeIt);
+}
+
+/* Return a string that is the sole owner of str. */
+MAKE_INLINE MFStr
+MFStr_InitOwn(char *str)
+{
+ return (MFStr){ str, str };
+}
+
+/* Return a string that refers to the shared str. */
+MAKE_INLINE MFStr
+MFStr_InitRefer(char *str)
+{
+ return (MFStr){ str, NULL };
+}
+
+MAKE_INLINE void
+MFStr_Done(MFStr *mfstr)
+{
+ free(mfstr->freeIt);
+}
+
Words Str_Words(const char *, Boolean);
MAKE_INLINE void
-Words_Free(Words w) {
- free(w.words);
- free(w.freeIt);
+Words_Free(Words w)
+{
+ free(w.words);
+ free(w.freeIt);
}
char *str_concat2(const char *, const char *);
@@ -204,15 +272,13 @@ GNode *Targ_FindNode(const char *);
GNode *Targ_GetNode(const char *);
GNode *Targ_NewInternalNode(const char *);
GNode *Targ_GetEndNode(void);
-GNodeList *Targ_FindList(StringList *);
-Boolean Targ_Ignore(const GNode *);
-Boolean Targ_Silent(const GNode *);
+void Targ_FindList(GNodeList *, StringList *);
Boolean Targ_Precious(const GNode *);
void Targ_SetMain(GNode *);
void Targ_PrintCmds(GNode *);
void Targ_PrintNode(GNode *, int);
void Targ_PrintNodes(GNodeList *, int);
-char *Targ_FmtTime(time_t);
+const char *Targ_FmtTime(time_t);
void Targ_PrintType(int);
void Targ_PrintGraph(int);
void Targ_Propagate(void);
@@ -222,112 +288,104 @@ void Var_Init(void);
void Var_End(void);
typedef enum VarEvalFlags {
- VARE_NONE = 0,
-
- /* Expand and evaluate variables during parsing.
- *
- * TODO: Document what Var_Parse and Var_Subst return when this flag
- * is not set. */
- VARE_WANTRES = 1 << 0,
-
- /* Treat undefined variables as errors.
- * Must only be used in combination with VARE_WANTRES. */
- VARE_UNDEFERR = 1 << 1,
-
- /* Keep '$$' as '$$' instead of reducing it to a single '$'.
- *
- * Used in variable assignments using the ':=' operator. It allows
- * multiple such assignments to be chained without accidentally expanding
- * '$$file' to '$file' in the first assignment and interpreting it as
- * '${f}' followed by 'ile' in the next assignment.
- *
- * See also preserveUndefined, which preserves subexpressions that are
- * based on undefined variables; maybe that can be converted to a flag
- * as well. */
- VARE_KEEP_DOLLAR = 1 << 2
+ VARE_NONE = 0,
+
+ /* Expand and evaluate variables during parsing.
+ *
+ * TODO: Document what Var_Parse and Var_Subst return when this flag
+ * is not set. */
+ VARE_WANTRES = 1 << 0,
+
+ /* Treat undefined variables as errors.
+ * Must only be used in combination with VARE_WANTRES. */
+ VARE_UNDEFERR = 1 << 1,
+
+ /* Keep '$$' as '$$' instead of reducing it to a single '$'.
+ *
+ * Used in variable assignments using the ':=' operator. It allows
+ * multiple such assignments to be chained without accidentally
+ * expanding '$$file' to '$file' in the first assignment and
+ * interpreting it as '${f}' followed by 'ile' in the next assignment.
+ *
+ * See also preserveUndefined, which preserves subexpressions that are
+ * based on undefined variables; maybe that can be converted to a flag
+ * as well. */
+ VARE_KEEP_DOLLAR = 1 << 2,
+
+ /*
+ * Keep undefined variables as-is instead of expanding them to an
+ * empty string.
+ *
+ * Example for a ':=' assignment:
+ * CFLAGS = $(.INCLUDES)
+ * CFLAGS := -I.. $(CFLAGS)
+ * # If .INCLUDES (an undocumented special variable, by the
+ * # way) is still undefined, the updated CFLAGS becomes
+ * # "-I.. $(.INCLUDES)".
+ */
+ VARE_KEEP_UNDEF = 1 << 3
} VarEvalFlags;
typedef enum VarSetFlags {
- VAR_SET_NONE = 0,
+ VAR_SET_NONE = 0,
- /* do not export */
- VAR_SET_NO_EXPORT = 1 << 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
+ /* 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.
- *
- * As of 2020-09-13, this bitset looks quite bloated,
- * with all the constants doubled.
- *
- * Its purpose is to first document the existing behavior,
- * and then migrate away from the SILENT constants, step by step,
- * as these are not suited for reliable, consistent error handling
- * and reporting. */
+/* The state of error handling returned by Var_Parse. */
typedef enum VarParseResult {
- /* Both parsing and evaluation succeeded. */
- VPR_OK = 0x0000,
-
- /* See if a message has already been printed for this error. */
- VPR_ANY_MSG = 0x0001,
-
- /* Parsing failed.
- * No error message has been printed yet.
- * Deprecated, migrate to VPR_PARSE_MSG instead. */
- VPR_PARSE_SILENT = 0x0002,
-
- /* Parsing failed.
- * An error message has already been printed. */
- VPR_PARSE_MSG = VPR_PARSE_SILENT | VPR_ANY_MSG,
-
- /* Parsing succeeded.
- * During evaluation, VARE_UNDEFERR was set and there was an undefined
- * variable.
- * No error message has been printed yet.
- * Deprecated, migrate to VPR_UNDEF_MSG instead. */
- VPR_UNDEF_SILENT = 0x0004,
-
- /* Parsing succeeded.
- * During evaluation, VARE_UNDEFERR was set and there was an undefined
- * variable.
- * An error message has already been printed. */
- VPR_UNDEF_MSG = VPR_UNDEF_SILENT | VPR_ANY_MSG,
-
- /* Parsing succeeded.
- * Evaluation failed.
- * No error message has been printed yet.
- * Deprecated, migrate to VPR_EVAL_MSG instead. */
- VPR_EVAL_SILENT = 0x0006,
-
- /* Parsing succeeded.
- * Evaluation failed.
- * An error message has already been printed. */
- VPR_EVAL_MSG = VPR_EVAL_SILENT | VPR_ANY_MSG,
-
- /* The exact error handling status is not known yet.
- * Deprecated, migrate to VPR_OK or any VPE_*_MSG instead. */
- VPR_UNKNOWN = 0x0008
+ /* Both parsing and evaluation succeeded. */
+ VPR_OK,
+
+ /* Parsing or evaluating failed, with an error message. */
+ VPR_ERR,
+
+ /*
+ * Parsing succeeded, undefined expressions are allowed and the
+ * expression was still undefined after applying all modifiers.
+ * No error message is printed in this case.
+ *
+ * Some callers handle this case differently, so return this
+ * information to them, for now.
+ *
+ * TODO: Replace this with a new flag VARE_KEEP_UNDEFINED.
+ */
+ 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_DeleteVar(const char *, GNode *);
void Var_Delete(const char *, GNode *);
+void Var_Undef(const char *);
void Var_Set(const char *, const char *, GNode *);
void Var_SetWithFlags(const char *, const char *, GNode *, VarSetFlags);
void Var_Append(const char *, const char *, GNode *);
Boolean Var_Exists(const char *, GNode *);
-const char *Var_Value(const char *, GNode *, void **);
+FStr Var_Value(const char *, GNode *);
const char *Var_ValueDirect(const char *, GNode *);
-VarParseResult Var_Parse(const char **, GNode *, VarEvalFlags,
- const char **, void **);
+VarParseResult Var_Parse(const char **, GNode *, VarEvalFlags, FStr *);
VarParseResult Var_Subst(const char *, GNode *, VarEvalFlags, char **);
void Var_Stats(void);
void Var_Dump(GNode *);
-void Var_ExportVars(void);
-void Var_Export(const char *, Boolean);
-void Var_UnExport(const char *);
+void Var_ReexportVars(void);
+void Var_Export(VarExportMode, const char *);
+void Var_ExportVars(const char *);
+void Var_UnExport(Boolean, const char *);
/* util.c */
typedef void (*SignalProc)(int);
diff --git a/os.sh b/os.sh
index 7e6823b240c3..7e6823b240c3 100755..100644
--- a/os.sh
+++ b/os.sh
diff --git a/parse.c b/parse.c
index d7bd65645aca..aca1015ab30d 100644
--- a/parse.c
+++ b/parse.c
@@ -1,4 +1,4 @@
-/* $NetBSD: parse.c,v 1.443 2020/11/16 21:39:22 rillig Exp $ */
+/* $NetBSD: parse.c,v 1.526 2021/01/10 21:20:46 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -124,7 +124,7 @@
#include "pathnames.h"
/* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: parse.c,v 1.443 2020/11/16 21:39:22 rillig Exp $");
+MAKE_RCSID("$NetBSD: parse.c,v 1.526 2021/01/10 21:20:46 rillig Exp $");
/* types and constants */
@@ -132,62 +132,64 @@ MAKE_RCSID("$NetBSD: parse.c,v 1.443 2020/11/16 21:39:22 rillig Exp $");
* Structure for a file being read ("included file")
*/
typedef struct IFile {
- char *fname; /* name of file (relative? absolute?) */
- Boolean fromForLoop; /* simulated .include by the .for loop */
- int lineno; /* current line number in file */
- int first_lineno; /* line number of start of text */
- unsigned int cond_depth; /* 'if' nesting when file opened */
- Boolean depending; /* state of doing_depend on EOF */
-
- /* The buffer from which the file's content is read. */
- char *buf_freeIt;
- char *buf_ptr; /* next char to be read */
- char *buf_end;
-
- char *(*nextbuf)(void *, size_t *); /* Function to get more data */
- void *nextbuf_arg; /* Opaque arg for nextbuf() */
- struct loadedfile *lf; /* loadedfile object, if any */
+ char *fname; /* name of file (relative? absolute?) */
+ Boolean fromForLoop; /* simulated .include by the .for loop */
+ int lineno; /* current line number in file */
+ int first_lineno; /* line number of start of text */
+ unsigned int cond_depth; /* 'if' nesting when file opened */
+ Boolean depending; /* state of doing_depend on EOF */
+
+ /* The buffer from which the file's content is read. */
+ char *buf_freeIt;
+ char *buf_ptr; /* next char to be read */
+ char *buf_end;
+
+ /* Function to read more data, with a single opaque argument. */
+ ReadMoreProc readMore;
+ void *readMoreArg;
+
+ struct loadedfile *lf; /* loadedfile object, if any */
} IFile;
/*
* Tokens for target attributes
*/
typedef enum ParseSpecial {
- SP_ATTRIBUTE, /* Generic attribute */
- SP_BEGIN, /* .BEGIN */
- SP_DEFAULT, /* .DEFAULT */
- SP_DELETE_ON_ERROR, /* .DELETE_ON_ERROR */
- SP_END, /* .END */
- SP_ERROR, /* .ERROR */
- SP_IGNORE, /* .IGNORE */
- SP_INCLUDES, /* .INCLUDES; not mentioned in the manual page */
- SP_INTERRUPT, /* .INTERRUPT */
- SP_LIBS, /* .LIBS; not mentioned in the manual page */
- SP_MAIN, /* .MAIN and we don't have anything user-specified to
- * make */
- SP_META, /* .META */
- SP_MFLAGS, /* .MFLAGS or .MAKEFLAGS */
- SP_NOMETA, /* .NOMETA */
- SP_NOMETA_CMP, /* .NOMETA_CMP */
- SP_NOPATH, /* .NOPATH */
- SP_NOT, /* Not special */
- SP_NOTPARALLEL, /* .NOTPARALLEL or .NO_PARALLEL */
- SP_NULL, /* .NULL; not mentioned in the manual page */
- SP_OBJDIR, /* .OBJDIR */
- SP_ORDER, /* .ORDER */
- SP_PARALLEL, /* .PARALLEL; not mentioned in the manual page */
- SP_PATH, /* .PATH or .PATH.suffix */
- SP_PHONY, /* .PHONY */
+ SP_ATTRIBUTE, /* Generic attribute */
+ SP_BEGIN, /* .BEGIN */
+ SP_DEFAULT, /* .DEFAULT */
+ SP_DELETE_ON_ERROR, /* .DELETE_ON_ERROR */
+ SP_END, /* .END */
+ SP_ERROR, /* .ERROR */
+ SP_IGNORE, /* .IGNORE */
+ SP_INCLUDES, /* .INCLUDES; not mentioned in the manual page */
+ SP_INTERRUPT, /* .INTERRUPT */
+ SP_LIBS, /* .LIBS; not mentioned in the manual page */
+ /* .MAIN and we don't have anything user-specified to make */
+ SP_MAIN,
+ SP_META, /* .META */
+ SP_MFLAGS, /* .MFLAGS or .MAKEFLAGS */
+ SP_NOMETA, /* .NOMETA */
+ SP_NOMETA_CMP, /* .NOMETA_CMP */
+ SP_NOPATH, /* .NOPATH */
+ SP_NOT, /* Not special */
+ SP_NOTPARALLEL, /* .NOTPARALLEL or .NO_PARALLEL */
+ SP_NULL, /* .NULL; not mentioned in the manual page */
+ SP_OBJDIR, /* .OBJDIR */
+ SP_ORDER, /* .ORDER */
+ SP_PARALLEL, /* .PARALLEL; not mentioned in the manual page */
+ SP_PATH, /* .PATH or .PATH.suffix */
+ SP_PHONY, /* .PHONY */
#ifdef POSIX
- SP_POSIX, /* .POSIX; not mentioned in the manual page */
+ SP_POSIX, /* .POSIX; not mentioned in the manual page */
#endif
- SP_PRECIOUS, /* .PRECIOUS */
- SP_SHELL, /* .SHELL */
- SP_SILENT, /* .SILENT */
- SP_SINGLESHELL, /* .SINGLESHELL; not mentioned in the manual page */
- SP_STALE, /* .STALE */
- SP_SUFFIXES, /* .SUFFIXES */
- SP_WAIT /* .WAIT */
+ SP_PRECIOUS, /* .PRECIOUS */
+ SP_SHELL, /* .SHELL */
+ SP_SILENT, /* .SILENT */
+ SP_SINGLESHELL, /* .SINGLESHELL; not mentioned in the manual page */
+ SP_STALE, /* .STALE */
+ SP_SUFFIXES, /* .SUFFIXES */
+ SP_WAIT /* .WAIT */
} ParseSpecial;
typedef List SearchPathList;
@@ -203,19 +205,23 @@ static GNode *mainNode;
/* eval state */
-/* During parsing, the targets from the left-hand side of the currently
+/*
+ * During parsing, the targets from the left-hand side of the currently
* active dependency line, or NULL if the current line does not belong to a
* dependency line, for example because it is a variable assignment.
*
- * See unit-tests/deptgt.mk, keyword "parse.c:targets". */
+ * See unit-tests/deptgt.mk, keyword "parse.c:targets".
+ */
static GNodeList *targets;
#ifdef CLEANUP
-/* All shell commands for all targets, in no particular order and possibly
+/*
+ * All shell commands for all targets, in no particular order and possibly
* with duplicates. Kept in a separate list since the commands from .USE or
* .USEBEFORE nodes are shared with other GNodes, thereby giving up the
- * easily understandable ownership over the allocated strings. */
-static StringList *targCmds;
+ * easily understandable ownership over the allocated strings.
+ */
+static StringList targCmds = LST_INIT;
#endif
/*
@@ -233,7 +239,8 @@ static int fatals = 0;
* Variables for doing includes
*/
-/* The include chain of makefiles. At the bottom is the top-level makefile
+/*
+ * The include chain of makefiles. At the bottom is the top-level makefile
* from the command line, and on top of that, there are the included files or
* .for loops, up to and including the current file.
*
@@ -277,14 +284,14 @@ static Vector /* of IFile */ includes;
static IFile *
GetInclude(size_t i)
{
- return Vector_Get(&includes, i);
+ return Vector_Get(&includes, i);
}
/* The file that is currently being read. */
static IFile *
CurFile(void)
{
- return GetInclude(includes.len - 1);
+ return GetInclude(includes.len - 1);
}
/* include paths */
@@ -302,78 +309,76 @@ 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; /* Name of keyword */
- ParseSpecial spec; /* Type when used as a target */
- GNodeType op; /* Operator when used as a source */
+ const char *name; /* Name of keyword */
+ ParseSpecial spec; /* Type when used as a target */
+ GNodeType op; /* Operator when used as a source */
} parseKeywords[] = {
- { ".BEGIN", SP_BEGIN, 0 },
- { ".DEFAULT", SP_DEFAULT, 0 },
- { ".DELETE_ON_ERROR", SP_DELETE_ON_ERROR, 0 },
- { ".END", SP_END, 0 },
- { ".ERROR", SP_ERROR, 0 },
+ { ".BEGIN", SP_BEGIN, OP_NONE },
+ { ".DEFAULT", SP_DEFAULT, OP_NONE },
+ { ".DELETE_ON_ERROR", SP_DELETE_ON_ERROR, OP_NONE },
+ { ".END", SP_END, OP_NONE },
+ { ".ERROR", SP_ERROR, OP_NONE },
{ ".EXEC", SP_ATTRIBUTE, OP_EXEC },
{ ".IGNORE", SP_IGNORE, OP_IGNORE },
- { ".INCLUDES", SP_INCLUDES, 0 },
- { ".INTERRUPT", SP_INTERRUPT, 0 },
+ { ".INCLUDES", SP_INCLUDES, OP_NONE },
+ { ".INTERRUPT", SP_INTERRUPT, OP_NONE },
{ ".INVISIBLE", SP_ATTRIBUTE, OP_INVISIBLE },
{ ".JOIN", SP_ATTRIBUTE, OP_JOIN },
- { ".LIBS", SP_LIBS, 0 },
+ { ".LIBS", SP_LIBS, OP_NONE },
{ ".MADE", SP_ATTRIBUTE, OP_MADE },
- { ".MAIN", SP_MAIN, 0 },
+ { ".MAIN", SP_MAIN, OP_NONE },
{ ".MAKE", SP_ATTRIBUTE, OP_MAKE },
- { ".MAKEFLAGS", SP_MFLAGS, 0 },
+ { ".MAKEFLAGS", SP_MFLAGS, OP_NONE },
{ ".META", SP_META, OP_META },
- { ".MFLAGS", SP_MFLAGS, 0 },
+ { ".MFLAGS", SP_MFLAGS, OP_NONE },
{ ".NOMETA", SP_NOMETA, OP_NOMETA },
{ ".NOMETA_CMP", SP_NOMETA_CMP, OP_NOMETA_CMP },
{ ".NOPATH", SP_NOPATH, OP_NOPATH },
{ ".NOTMAIN", SP_ATTRIBUTE, OP_NOTMAIN },
- { ".NOTPARALLEL", SP_NOTPARALLEL, 0 },
- { ".NO_PARALLEL", SP_NOTPARALLEL, 0 },
- { ".NULL", SP_NULL, 0 },
- { ".OBJDIR", SP_OBJDIR, 0 },
+ { ".NOTPARALLEL", SP_NOTPARALLEL, OP_NONE },
+ { ".NO_PARALLEL", SP_NOTPARALLEL, OP_NONE },
+ { ".NULL", SP_NULL, OP_NONE },
+ { ".OBJDIR", SP_OBJDIR, OP_NONE },
{ ".OPTIONAL", SP_ATTRIBUTE, OP_OPTIONAL },
- { ".ORDER", SP_ORDER, 0 },
- { ".PARALLEL", SP_PARALLEL, 0 },
- { ".PATH", SP_PATH, 0 },
+ { ".ORDER", SP_ORDER, OP_NONE },
+ { ".PARALLEL", SP_PARALLEL, OP_NONE },
+ { ".PATH", SP_PATH, OP_NONE },
{ ".PHONY", SP_PHONY, OP_PHONY },
#ifdef POSIX
- { ".POSIX", SP_POSIX, 0 },
+ { ".POSIX", SP_POSIX, OP_NONE },
#endif
{ ".PRECIOUS", SP_PRECIOUS, OP_PRECIOUS },
{ ".RECURSIVE", SP_ATTRIBUTE, OP_MAKE },
- { ".SHELL", SP_SHELL, 0 },
+ { ".SHELL", SP_SHELL, OP_NONE },
{ ".SILENT", SP_SILENT, OP_SILENT },
- { ".SINGLESHELL", SP_SINGLESHELL, 0 },
- { ".STALE", SP_STALE, 0 },
- { ".SUFFIXES", SP_SUFFIXES, 0 },
+ { ".SINGLESHELL", SP_SINGLESHELL, OP_NONE },
+ { ".STALE", SP_STALE, OP_NONE },
+ { ".SUFFIXES", SP_SUFFIXES, OP_NONE },
{ ".USE", SP_ATTRIBUTE, OP_USE },
{ ".USEBEFORE", SP_ATTRIBUTE, OP_USEBEFORE },
- { ".WAIT", SP_WAIT, 0 },
+ { ".WAIT", SP_WAIT, OP_NONE },
};
/* file loader */
struct loadedfile {
/* XXX: What is the lifetime of this path? Who manages the memory? */
- const char *path; /* name, for error reports */
- char *buf; /* contents buffer */
- size_t len; /* length of contents */
- size_t maplen; /* length of mmap area, or 0 */
- Boolean used; /* XXX: have we used the data yet */
+ const char *path; /* name, for error reports */
+ char *buf; /* contents buffer */
+ size_t len; /* length of contents */
+ Boolean used; /* XXX: have we used the data yet */
};
/* XXX: What is the lifetime of the path? Who manages the memory? */
static struct loadedfile *
-loadedfile_create(const char *path)
+loadedfile_create(const char *path, char *buf, size_t buflen)
{
struct loadedfile *lf;
lf = bmake_malloc(sizeof *lf);
lf->path = path == NULL ? "(stdin)" : path;
- lf->buf = NULL;
- lf->len = 0;
- lf->maplen = 0;
+ lf->buf = buf;
+ lf->len = buflen;
lf->used = FALSE;
return lf;
}
@@ -381,23 +386,16 @@ loadedfile_create(const char *path)
static void
loadedfile_destroy(struct loadedfile *lf)
{
- if (lf->buf != NULL) {
-#ifdef HAVE_MMAP
- if (lf->maplen > 0)
- munmap(lf->buf, lf->maplen);
- else
-#endif
- free(lf->buf);
- }
+ free(lf->buf);
free(lf);
}
/*
- * nextbuf() operation for loadedfile, as needed by the weird and twisted
- * logic below. Once that's cleaned up, we can get rid of lf->used...
+ * 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_nextbuf(void *x, size_t *len)
+loadedfile_readMore(void *x, size_t *len)
{
struct loadedfile *lf = x;
@@ -427,68 +425,18 @@ load_getsize(int fd, size_t *ret)
* st_size is an off_t, which is 64 bits signed; *ret is
* size_t, which might be 32 bits unsigned or 64 bits
* unsigned. Rather than being elaborate, just punt on
- * files that are more than 2^31 bytes. We should never
- * see a makefile that size in practice...
+ * 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 > 0x7fffffff)
+ if (st.st_size < 0 || st.st_size > 0x3fffffff)
return FALSE;
*ret = (size_t)st.st_size;
return TRUE;
}
-#ifdef HAVE_MMAP
-static Boolean
-loadedfile_mmap(struct loadedfile *lf, int fd)
-{
- static unsigned long pagesize = 0;
-
- if (!load_getsize(fd, &lf->len))
- return FALSE;
-
- /* found a size, try mmap */
-#ifdef _SC_PAGESIZE
- if (pagesize == 0)
- pagesize = (unsigned long)sysconf(_SC_PAGESIZE);
-#endif
- if (pagesize == 0 || pagesize == (unsigned long)-1)
- pagesize = 0x1000;
-
- /* round size up to a page */
- lf->maplen = pagesize * ((lf->len + pagesize - 1) / pagesize);
-
- /*
- * XXX hack for dealing with empty files; remove when
- * we're no longer limited by interfacing to the old
- * logic elsewhere in this file.
- */
- if (lf->maplen == 0)
- lf->maplen = pagesize;
-
- /*
- * FUTURE: remove PROT_WRITE when the parser no longer
- * needs to scribble on the input.
- */
- lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE,
- MAP_FILE|MAP_COPY, fd, 0);
- if (lf->buf == MAP_FAILED)
- return FALSE;
-
- if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') {
- char *b = bmake_malloc(lf->len + 1);
- b[lf->len] = '\n';
- memcpy(b, lf->buf, lf->len++);
- munmap(lf->buf, lf->maplen);
- lf->maplen = 0;
- lf->buf = b;
- }
-
- return TRUE;
-}
-#endif
-
/*
* Read in a file.
*
@@ -501,79 +449,63 @@ loadedfile_mmap(struct loadedfile *lf, int fd)
static struct loadedfile *
loadfile(const char *path, int fd)
{
- struct loadedfile *lf;
- ssize_t result;
- size_t bufpos;
+ ssize_t n;
+ Buffer buf;
+ size_t filesize;
- lf = loadedfile_create(path);
if (path == NULL) {
assert(fd == -1);
fd = STDIN_FILENO;
- } else {
-#if 0 /* notyet */
- fd = open(path, O_RDONLY);
- if (fd < 0) {
- ...
- Error("%s: %s", path, strerror(errno));
- exit(1);
- }
-#endif
}
-#ifdef HAVE_MMAP
- if (loadedfile_mmap(lf, fd))
- goto done;
-#endif
-
- /* cannot mmap; load the traditional way */
-
- lf->maplen = 0;
- lf->len = 1024;
- lf->buf = bmake_malloc(lf->len);
+ 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);
- bufpos = 0;
for (;;) {
- assert(bufpos <= lf->len);
- if (bufpos == lf->len) {
- if (lf->len > SIZE_MAX/2) {
+ assert(buf.len <= buf.cap);
+ if (buf.len == buf.cap) {
+ if (buf.cap > 0x1fffffff) {
errno = EFBIG;
Error("%s: file too large", path);
- exit(1);
+ exit(2); /* Not 1 so -q can distinguish error */
}
- lf->len *= 2;
- lf->buf = bmake_realloc(lf->buf, lf->len);
+ Buf_Expand(&buf);
}
- assert(bufpos < lf->len);
- result = read(fd, lf->buf + bufpos, lf->len - bufpos);
- if (result < 0) {
+ assert(buf.len < buf.cap);
+ n = read(fd, buf.data + buf.len, buf.cap - buf.len);
+ if (n < 0) {
Error("%s: read error: %s", path, strerror(errno));
- exit(1);
+ exit(2); /* Not 1 so -q can distinguish error */
}
- if (result == 0)
+ if (n == 0)
break;
- bufpos += (size_t)result;
+ buf.len += (size_t)n;
}
- assert(bufpos <= lf->len);
- lf->len = bufpos;
+ assert(buf.len <= buf.cap);
- /* truncate malloc region to actual length (maybe not useful) */
- if (lf->len > 0) {
- /* as for mmap case, ensure trailing \n */
- if (lf->buf[lf->len - 1] != '\n')
- lf->len++;
- lf->buf = bmake_realloc(lf->buf, lf->len);
- lf->buf[lf->len - 1] = '\n';
- }
+ if (!Buf_EndsWith(&buf, '\n'))
+ Buf_AddByte(&buf, '\n');
-#ifdef HAVE_MMAP
-done:
-#endif
if (path != NULL)
close(fd);
- return lf;
+ {
+ struct loadedfile *lf = loadedfile_create(path,
+ buf.data, buf.len);
+ Buf_Destroy(&buf, FALSE);
+ return lf;
+ }
}
/* old code */
@@ -582,79 +514,82 @@ done:
static Boolean
ParseIsEscaped(const char *line, const char *c)
{
- Boolean active = FALSE;
- for (;;) {
- if (line == c)
- return active;
- if (*--c != '\\')
- return active;
- active = !active;
- }
+ Boolean active = FALSE;
+ for (;;) {
+ if (line == c)
+ return active;
+ if (*--c != '\\')
+ return active;
+ active = !active;
+ }
}
-/* Add the filename and lineno to the GNode so that we remember where it
- * was first defined. */
+/*
+ * Add the filename and lineno to the GNode so that we remember where it
+ * was first defined.
+ */
static void
ParseMark(GNode *gn)
{
- IFile *curFile = CurFile();
- gn->fname = curFile->fname;
- gn->lineno = curFile->lineno;
+ IFile *curFile = CurFile();
+ gn->fname = curFile->fname;
+ gn->lineno = curFile->lineno;
}
-/* Look in the table of keywords for one matching the given string.
- * Return the index of the keyword, or -1 if it isn't there. */
+/*
+ * Look in the table of keywords for one matching the given string.
+ * Return the index of the keyword, or -1 if it isn't there.
+ */
static int
ParseFindKeyword(const char *str)
{
- int start = 0;
- int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1;
+ int start = 0;
+ int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1;
- do {
- int cur = start + (end - start) / 2;
- int diff = strcmp(str, parseKeywords[cur].name);
+ do {
+ int curr = start + (end - start) / 2;
+ int diff = strcmp(str, parseKeywords[curr].name);
- if (diff == 0)
- return cur;
- if (diff < 0)
- end = cur - 1;
- else
- start = cur + 1;
- } while (start <= end);
+ if (diff == 0)
+ return curr;
+ if (diff < 0)
+ end = curr - 1;
+ else
+ start = curr + 1;
+ } while (start <= end);
- return -1;
+ return -1;
}
static void
PrintLocation(FILE *f, const char *fname, size_t lineno)
{
- char dirbuf[MAXPATHLEN+1];
- const char *dir, *base;
- void *dir_freeIt, *base_freeIt;
+ char dirbuf[MAXPATHLEN + 1];
+ FStr dir, base;
if (*fname == '/' || strcmp(fname, "(stdin)") == 0) {
- (void)fprintf(f, "\"%s\" line %zu: ", fname, lineno);
+ (void)fprintf(f, "\"%s\" line %u: ", fname, (unsigned)lineno);
return;
}
/* Find out which makefile is the culprit.
* We try ${.PARSEDIR} and apply realpath(3) if not absolute. */
- dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &dir_freeIt);
- if (dir == NULL)
- dir = ".";
- if (*dir != '/')
- dir = realpath(dir, dirbuf);
+ dir = Var_Value(".PARSEDIR", VAR_GLOBAL);
+ if (dir.str == NULL)
+ dir.str = ".";
+ if (dir.str[0] != '/')
+ dir.str = realpath(dir.str, dirbuf);
- base = Var_Value(".PARSEFILE", VAR_GLOBAL, &base_freeIt);
- if (base == NULL) {
- const char *slash = strrchr(fname, '/');
- base = slash != NULL ? slash + 1 : fname;
- }
+ base = Var_Value(".PARSEFILE", VAR_GLOBAL);
+ if (base.str == NULL)
+ base.str = str_basename(fname);
- (void)fprintf(f, "\"%s/%s\" line %zu: ", dir, base, lineno);
- bmake_free(base_freeIt);
- bmake_free(dir_freeIt);
+ (void)fprintf(f, "\"%s/%s\" line %u: ",
+ dir.str, base.str, (unsigned)lineno);
+
+ FStr_Done(&base);
+ FStr_Done(&dir);
}
static void
@@ -697,17 +632,19 @@ ParseErrorInternal(const char *fname, size_t lineno,
if (opts.debug_file != stderr && opts.debug_file != stdout) {
va_start(ap, fmt);
ParseVErrorInternal(opts.debug_file, fname, lineno, type,
- fmt, ap);
+ fmt, ap);
va_end(ap);
}
}
-/* Print a parse error message, including location information.
+/*
+ * Print a parse error message, including location information.
*
* If the level is PARSE_FATAL, continue parsing until the end of the
* current top-level makefile, then exit (see Parse_File).
*
- * Fmt is given without a trailing newline. */
+ * Fmt is given without a trailing newline.
+ */
void
Parse_Error(ParseErrorLevel type, const char *fmt, ...)
{
@@ -732,260 +669,278 @@ Parse_Error(ParseErrorLevel type, const char *fmt, ...)
if (opts.debug_file != stderr && opts.debug_file != stdout) {
va_start(ap, fmt);
ParseVErrorInternal(opts.debug_file, fname, lineno, type,
- fmt, ap);
+ fmt, ap);
va_end(ap);
}
}
-/* Parse and handle a .info, .warning or .error directive.
- * For an .error directive, immediately exit. */
-static Boolean
-ParseMessage(const char *directive)
+/*
+ * Parse and handle a .info, .warning or .error directive.
+ * For an .error directive, immediately exit.
+ */
+static void
+ParseMessage(ParseErrorLevel level, const char *levelName, const char *umsg)
{
- const char *p = directive;
- int mtype = *p == 'i' ? PARSE_INFO :
- *p == 'w' ? PARSE_WARNING : PARSE_FATAL;
- char *arg;
+ char *xmsg;
- while (ch_isalpha(*p))
- p++;
- if (!ch_isspace(*p))
- return FALSE; /* missing argument */
+ if (umsg[0] == '\0') {
+ Parse_Error(PARSE_FATAL, "Missing argument for \".%s\"",
+ levelName);
+ return;
+ }
- cpp_skip_whitespace(&p);
- (void)Var_Subst(p, VAR_CMDLINE, VARE_WANTRES, &arg);
- /* TODO: handle errors */
+ (void)Var_Subst(umsg, VAR_CMDLINE, VARE_WANTRES, &xmsg);
+ /* TODO: handle errors */
- Parse_Error(mtype, "%s", arg);
- free(arg);
+ Parse_Error(level, "%s", xmsg);
+ free(xmsg);
- if (mtype == PARSE_FATAL) {
- PrintOnError(NULL, NULL);
- exit(1);
- }
- return TRUE;
+ if (level == PARSE_FATAL) {
+ PrintOnError(NULL, NULL);
+ exit(1);
+ }
}
-/* Add the child to the parent's children.
+/*
+ * 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. */
+ * which does not need to be informed once the child target has been made.
+ */
static void
LinkSource(GNode *pgn, GNode *cgn, Boolean isSpecial)
{
- if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(pgn->cohorts))
- pgn = pgn->cohorts->last->datum;
+ if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(&pgn->cohorts))
+ pgn = pgn->cohorts.last->datum;
- Lst_Append(pgn->children, cgn);
- pgn->unmade++;
+ Lst_Append(&pgn->children, cgn);
+ pgn->unmade++;
- /* Special targets like .END don't need any children. */
- if (!isSpecial)
- Lst_Append(cgn->parents, pgn);
+ /* Special targets like .END don't need any children. */
+ if (!isSpecial)
+ Lst_Append(&cgn->parents, pgn);
- if (DEBUG(PARSE)) {
- debug_printf("# %s: added child %s - %s\n",
- __func__, pgn->name, cgn->name);
- Targ_PrintNode(pgn, 0);
- Targ_PrintNode(cgn, 0);
- }
+ if (DEBUG(PARSE)) {
+ debug_printf("# %s: added child %s - %s\n",
+ __func__, pgn->name, cgn->name);
+ Targ_PrintNode(pgn, 0);
+ Targ_PrintNode(cgn, 0);
+ }
}
/* Add the node to each target from the current dependency group. */
static void
LinkToTargets(GNode *gn, Boolean isSpecial)
{
- GNodeListNode *ln;
- for (ln = targets->first; ln != NULL; ln = ln->next)
- LinkSource(ln->datum, gn, isSpecial);
+ GNodeListNode *ln;
+
+ for (ln = targets->first; ln != NULL; ln = ln->next)
+ LinkSource(ln->datum, gn, isSpecial);
}
static Boolean
TryApplyDependencyOperator(GNode *gn, GNodeType op)
{
- /*
- * If the node occurred on the left-hand side of a dependency and the
- * operator also defines a dependency, they must match.
- */
- if ((op & OP_OPMASK) && (gn->type & OP_OPMASK) &&
- ((op & OP_OPMASK) != (gn->type & OP_OPMASK)))
- {
- Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name);
- return FALSE;
- }
-
- if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) {
/*
- * If the node was of the left-hand side of a '::' operator, we need
- * to create a new instance of it for the children and commands on
- * this dependency line since each of these dependency groups has its
- * own attributes and commands, separate from the others.
- *
- * The new instance is placed on the 'cohorts' list of the
- * initial one (note the initial one is not on its own cohorts list)
- * and the new instance is linked to all parents of the initial
- * instance.
+ * If the node occurred on the left-hand side of a dependency and the
+ * operator also defines a dependency, they must match.
*/
- GNode *cohort;
+ if ((op & OP_OPMASK) && (gn->type & OP_OPMASK) &&
+ ((op & OP_OPMASK) != (gn->type & OP_OPMASK))) {
+ Parse_Error(PARSE_FATAL, "Inconsistent operator for %s",
+ gn->name);
+ return FALSE;
+ }
- /*
- * Propagate copied bits to the initial node. They'll be propagated
- * back to the rest of the cohorts later.
- */
- gn->type |= op & ~OP_OPMASK;
+ if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) {
+ /*
+ * If the node was of the left-hand side of a '::' operator,
+ * we need to create a new instance of it for the children
+ * and commands on this dependency line since each of these
+ * dependency groups has its own attributes and commands,
+ * separate from the others.
+ *
+ * The new instance is placed on the 'cohorts' list of the
+ * initial one (note the initial one is not on its own
+ * cohorts list) and the new instance is linked to all
+ * parents of the initial instance.
+ */
+ GNode *cohort;
- cohort = Targ_NewInternalNode(gn->name);
- if (doing_depend)
- ParseMark(cohort);
- /*
- * Make the cohort invisible as well to avoid duplicating it into
- * other variables. True, parents of this target won't tend to do
- * anything with their local variables, but better safe than
- * sorry. (I think this is pointless now, since the relevant list
- * traversals will no longer see this node anyway. -mycroft)
- */
- cohort->type = op | OP_INVISIBLE;
- Lst_Append(gn->cohorts, cohort);
- cohort->centurion = gn;
- gn->unmade_cohorts++;
- snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d",
- (unsigned int)gn->unmade_cohorts % 1000000);
- } else {
- /*
- * We don't want to nuke any previous flags (whatever they were) so we
- * just OR the new operator into the old.
- */
- gn->type |= op;
- }
+ /*
+ * Propagate copied bits to the initial node. They'll be
+ * propagated back to the rest of the cohorts later.
+ */
+ gn->type |= op & ~OP_OPMASK;
+
+ cohort = Targ_NewInternalNode(gn->name);
+ if (doing_depend)
+ ParseMark(cohort);
+ /*
+ * Make the cohort invisible as well to avoid duplicating it
+ * into other variables. True, parents of this target won't
+ * tend to do anything with their local variables, but better
+ * safe than sorry.
+ *
+ * (I think this is pointless now, since the relevant list
+ * traversals will no longer see this node anyway. -mycroft)
+ */
+ cohort->type = op | OP_INVISIBLE;
+ Lst_Append(&gn->cohorts, cohort);
+ cohort->centurion = gn;
+ gn->unmade_cohorts++;
+ snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d",
+ (unsigned int)gn->unmade_cohorts % 1000000);
+ } else {
+ /*
+ * We don't want to nuke any previous flags (whatever they
+ * were) so we just OR the new operator into the old.
+ */
+ gn->type |= op;
+ }
- return TRUE;
+ return TRUE;
}
static void
ApplyDependencyOperator(GNodeType op)
{
- GNodeListNode *ln;
- for (ln = targets->first; ln != NULL; ln = ln->next)
- if (!TryApplyDependencyOperator(ln->datum, op))
- break;
+ GNodeListNode *ln;
+
+ for (ln = targets->first; ln != NULL; ln = ln->next)
+ if (!TryApplyDependencyOperator(ln->datum, op))
+ break;
+}
+
+/*
+ * We add a .WAIT node in the dependency list. After any dynamic dependencies
+ * (and filename globbing) have happened, it is given a dependency on each
+ * previous child, back until the previous .WAIT node. The next child won't
+ * be scheduled until the .WAIT node is built.
+ *
+ * We give each .WAIT node a unique name (mainly for diagnostics).
+ */
+static void
+ParseDependencySourceWait(Boolean isSpecial)
+{
+ static int wait_number = 0;
+ char wait_src[16];
+ GNode *gn;
+
+ snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number);
+ gn = Targ_NewInternalNode(wait_src);
+ if (doing_depend)
+ ParseMark(gn);
+ gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN;
+ LinkToTargets(gn, isSpecial);
+
}
static Boolean
-ParseDoSrcKeyword(const char *src, ParseSpecial specType)
+ParseDependencySourceKeyword(const char *src, ParseSpecial specType)
{
- static int wait_number = 0;
- char wait_src[16];
- GNode *gn;
+ int keywd;
+ GNodeType op;
- if (*src == '.' && ch_isupper(src[1])) {
- int keywd = ParseFindKeyword(src);
- if (keywd != -1) {
- GNodeType op = parseKeywords[keywd].op;
- if (op != 0) {
+ if (*src != '.' || !ch_isupper(src[1]))
+ return FALSE;
+
+ keywd = ParseFindKeyword(src);
+ if (keywd == -1)
+ return FALSE;
+
+ op = parseKeywords[keywd].op;
+ if (op != OP_NONE) {
ApplyDependencyOperator(op);
return TRUE;
- }
- if (parseKeywords[keywd].spec == SP_WAIT) {
- /*
- * We add a .WAIT node in the dependency list.
- * After any dynamic dependencies (and filename globbing)
- * have happened, it is given a dependency on each
- * previous child, back until the previous .WAIT node.
- * The next child won't be scheduled until the .WAIT node
- * is built.
- * We give each .WAIT node a unique name (mainly for
- * diagnostics).
- */
- snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number);
- gn = Targ_NewInternalNode(wait_src);
- if (doing_depend)
- ParseMark(gn);
- gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN;
- LinkToTargets(gn, specType != SP_NOT);
+ }
+ if (parseKeywords[keywd].spec == SP_WAIT) {
+ ParseDependencySourceWait(specType != SP_NOT);
return TRUE;
- }
}
- }
- return FALSE;
+ return FALSE;
}
static void
-ParseDoSrcMain(const char *src)
-{
- /*
- * In a line like ".MAIN: source1 source2", it means we need to add
- * the sources of said target to the list of things to create.
- *
- * Note that this will only be invoked if the user didn't specify a
- * target on the command line. This is to allow .ifmake to succeed.
- *
- * XXX: Double-check all of the above comment.
- */
- Lst_Append(opts.create, bmake_strdup(src));
- /*
- * Add the name to the .TARGETS variable as well, so the user can
- * employ that, if desired.
- */
- Var_Append(".TARGETS", src, VAR_GLOBAL);
+ParseDependencySourceMain(const char *src)
+{
+ /*
+ * In a line like ".MAIN: source1 source2", it means we need to add
+ * the sources of said target to the list of things to create.
+ *
+ * Note that this will only be invoked if the user didn't specify a
+ * target on the command line and the .MAIN occurs for the first time.
+ *
+ * See ParseDoDependencyTargetSpecial, branch SP_MAIN.
+ * See unit-tests/cond-func-make-main.mk.
+ */
+ Lst_Append(&opts.create, bmake_strdup(src));
+ /*
+ * Add the name to the .TARGETS variable as well, so the user can
+ * employ that, if desired.
+ */
+ Var_Append(".TARGETS", src, VAR_GLOBAL);
}
static void
-ParseDoSrcOrder(const char *src)
-{
- GNode *gn;
- /*
- * Create proper predecessor/successor links between the previous
- * source and the current one.
- */
- gn = Targ_GetNode(src);
- if (doing_depend)
- ParseMark(gn);
- if (order_pred != NULL) {
- Lst_Append(order_pred->order_succ, gn);
- Lst_Append(gn->order_pred, order_pred);
- if (DEBUG(PARSE)) {
- debug_printf("# %s: added Order dependency %s - %s\n",
- __func__, order_pred->name, gn->name);
- Targ_PrintNode(order_pred, 0);
- Targ_PrintNode(gn, 0);
+ParseDependencySourceOrder(const char *src)
+{
+ GNode *gn;
+ /*
+ * Create proper predecessor/successor links between the previous
+ * source and the current one.
+ */
+ gn = Targ_GetNode(src);
+ if (doing_depend)
+ ParseMark(gn);
+ if (order_pred != NULL) {
+ Lst_Append(&order_pred->order_succ, gn);
+ Lst_Append(&gn->order_pred, order_pred);
+ if (DEBUG(PARSE)) {
+ debug_printf("# %s: added Order dependency %s - %s\n",
+ __func__, order_pred->name, gn->name);
+ Targ_PrintNode(order_pred, 0);
+ Targ_PrintNode(gn, 0);
+ }
}
- }
- /*
- * The current source now becomes the predecessor for the next one.
- */
- order_pred = gn;
+ /*
+ * The current source now becomes the predecessor for the next one.
+ */
+ order_pred = gn;
}
static void
-ParseDoSrcOther(const char *src, GNodeType tOp, ParseSpecial specType)
-{
- GNode *gn;
-
- /*
- * If the source is not an attribute, we need to find/create
- * a node for it. After that we can apply any operator to it
- * from a special target or link it to its parents, as
- * appropriate.
- *
- * In the case of a source that was the object of a :: operator,
- * the attribute is applied to all of its instances (as kept in
- * the 'cohorts' list of the node) or all the cohorts are linked
- * to all the targets.
- */
-
- /* Find/create the 'src' node and attach to all targets */
- gn = Targ_GetNode(src);
- if (doing_depend)
- ParseMark(gn);
- if (tOp != OP_NONE)
- gn->type |= tOp;
- else
- LinkToTargets(gn, specType != SP_NOT);
-}
-
-/* Given the name of a source in a dependency line, figure out if it is an
+ParseDependencySourceOther(const char *src, GNodeType tOp,
+ ParseSpecial specType)
+{
+ GNode *gn;
+
+ /*
+ * If the source is not an attribute, we need to find/create
+ * a node for it. After that we can apply any operator to it
+ * from a special target or link it to its parents, as
+ * appropriate.
+ *
+ * In the case of a source that was the object of a :: operator,
+ * the attribute is applied to all of its instances (as kept in
+ * the 'cohorts' list of the node) or all the cohorts are linked
+ * to all the targets.
+ */
+
+ /* Find/create the 'src' node and attach to all targets */
+ gn = Targ_GetNode(src);
+ if (doing_depend)
+ ParseMark(gn);
+ if (tOp != OP_NONE)
+ gn->type |= tOp;
+ else
+ LinkToTargets(gn, specType != SP_NOT);
+}
+
+/*
+ * Given the name of a source in a dependency line, figure out if it is an
* attribute (such as .SILENT) and apply it to the targets if it is. Else
* decide if there is some attribute which should be applied *to* the source
* because of some special target (such as .PHONY) and apply it if so.
@@ -996,38 +951,41 @@ ParseDoSrcOther(const char *src, GNodeType tOp, ParseSpecial specType)
* src name of the source to handle
*/
static void
-ParseDoSrc(GNodeType tOp, const char *src, ParseSpecial specType)
+ParseDependencySource(GNodeType tOp, const char *src, ParseSpecial specType)
{
- if (ParseDoSrcKeyword(src, specType))
- return;
+ if (ParseDependencySourceKeyword(src, specType))
+ return;
- if (specType == SP_MAIN)
- ParseDoSrcMain(src);
- else if (specType == SP_ORDER)
- ParseDoSrcOrder(src);
- else
- ParseDoSrcOther(src, tOp, specType);
+ if (specType == SP_MAIN)
+ ParseDependencySourceMain(src);
+ else if (specType == SP_ORDER)
+ ParseDependencySourceOrder(src);
+ else
+ ParseDependencySourceOther(src, tOp, specType);
}
-/* If we have yet to decide on a main target to make, in the absence of any
+/*
+ * If we have yet to decide on a main target to make, in the absence of any
* user input, we want the first target on the first dependency line that is
- * actually a real target (i.e. isn't a .USE or .EXEC rule) to be made. */
+ * actually a real target (i.e. isn't a .USE or .EXEC rule) to be made.
+ */
static void
FindMainTarget(void)
{
- GNodeListNode *ln;
+ GNodeListNode *ln;
- if (mainNode != NULL)
- return;
+ if (mainNode != NULL)
+ return;
- for (ln = targets->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
- if (!(gn->type & OP_NOTARGET)) {
- mainNode = gn;
- Targ_SetMain(gn);
- return;
+ for (ln = targets->first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
+ if (!(gn->type & OP_NOTARGET)) {
+ DEBUG1(MAKE, "Setting main node to \"%s\"\n", gn->name);
+ mainNode = gn;
+ Targ_SetMain(gn);
+ return;
+ }
}
- }
}
/*
@@ -1040,114 +998,121 @@ FindMainTarget(void)
static void
ParseErrorNoDependency(const char *lstart)
{
- if ((strncmp(lstart, "<<<<<<", 6) == 0) ||
- (strncmp(lstart, "======", 6) == 0) ||
- (strncmp(lstart, ">>>>>>", 6) == 0))
- Parse_Error(PARSE_FATAL,
+ if ((strncmp(lstart, "<<<<<<", 6) == 0) ||
+ (strncmp(lstart, "======", 6) == 0) ||
+ (strncmp(lstart, ">>>>>>", 6) == 0))
+ Parse_Error(PARSE_FATAL,
"Makefile appears to contain unresolved cvs/rcs/??? merge conflicts");
- else if (lstart[0] == '.') {
- const char *dirstart = lstart + 1;
- const char *dirend;
- cpp_skip_whitespace(&dirstart);
- dirend = dirstart;
- while (ch_isalnum(*dirend) || *dirend == '-')
- dirend++;
- Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"",
+ else if (lstart[0] == '.') {
+ const char *dirstart = lstart + 1;
+ const char *dirend;
+ cpp_skip_whitespace(&dirstart);
+ dirend = dirstart;
+ while (ch_isalnum(*dirend) || *dirend == '-')
+ dirend++;
+ Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"",
(int)(dirend - dirstart), dirstart);
- } else
- Parse_Error(PARSE_FATAL, "Need an operator");
+ } else
+ Parse_Error(PARSE_FATAL, "Need an operator");
}
static void
-ParseDependencyTargetWord(/*const*/ char **pp, const char *lstart)
-{
- /*const*/ char *cp = *pp;
-
- while (*cp != '\0') {
- if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' || *cp == '(') &&
- !ParseIsEscaped(lstart, cp))
- break;
-
- if (*cp == '$') {
- /*
- * Must be a dynamic source (would have been expanded
- * otherwise), so call the Var module to parse the puppy
- * so we can safely advance beyond it...There should be
- * no errors in this, as they would have been discovered
- * in the initial Var_Subst and we wouldn't be here.
- */
- const char *nested_p = cp;
- const char *nested_val;
- void *freeIt;
-
- (void)Var_Parse(&nested_p, VAR_CMDLINE,
- VARE_WANTRES | VARE_UNDEFERR, &nested_val, &freeIt);
- /* TODO: handle errors */
- free(freeIt);
- cp += nested_p - cp;
- } else
- cp++;
- }
+ParseDependencyTargetWord(const char **pp, const char *lstart)
+{
+ const char *cp = *pp;
- *pp = cp;
+ while (*cp != '\0') {
+ if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' ||
+ *cp == '(') &&
+ !ParseIsEscaped(lstart, cp))
+ break;
+
+ if (*cp == '$') {
+ /*
+ * Must be a dynamic source (would have been expanded
+ * otherwise), so call the Var module to parse the
+ * puppy so we can safely advance beyond it.
+ *
+ * There should be no errors in this, as they would
+ * have been discovered in the initial Var_Subst and
+ * we wouldn't be here.
+ */
+ const char *nested_p = cp;
+ FStr nested_val;
+
+ (void)Var_Parse(&nested_p, VAR_CMDLINE, VARE_NONE,
+ &nested_val);
+ /* TODO: handle errors */
+ FStr_Done(&nested_val);
+ cp += nested_p - cp;
+ } else
+ cp++;
+ }
+
+ *pp = cp;
}
/* Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. */
static void
ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType,
- const char *line,
+ const char *line, /* XXX: bad name */
SearchPathList **inout_paths)
{
- switch (*inout_specType) {
- case SP_PATH:
- if (*inout_paths == NULL)
- *inout_paths = Lst_New();
- Lst_Append(*inout_paths, dirSearchPath);
- break;
- case SP_MAIN:
- /* Allow targets from the command line to override the .MAIN node. */
- if (!Lst_IsEmpty(opts.create))
- *inout_specType = SP_NOT;
- break;
- case SP_BEGIN:
- case SP_END:
- case SP_STALE:
- case SP_ERROR:
- case SP_INTERRUPT: {
- GNode *gn = Targ_GetNode(line);
- if (doing_depend)
- ParseMark(gn);
- gn->type |= OP_NOTMAIN|OP_SPECIAL;
- Lst_Append(targets, gn);
- break;
- }
- case SP_DEFAULT: {
- /* Need to create a node to hang commands on, but we don't want it
- * in the graph, nor do we want it to be the Main Target. We claim
- * the node is a transformation rule to make life easier later,
- * when we'll use Make_HandleUse to actually apply the .DEFAULT
- * commands. */
- GNode *gn = GNode_New(".DEFAULT");
- gn->type |= OP_NOTMAIN|OP_TRANSFORM;
- Lst_Append(targets, gn);
- defaultNode = gn;
- break;
- }
- case SP_DELETE_ON_ERROR:
- deleteOnError = TRUE;
- break;
- case SP_NOTPARALLEL:
- opts.maxJobs = 1;
- break;
- case SP_SINGLESHELL:
- opts.compatMake = TRUE;
- break;
- case SP_ORDER:
- order_pred = NULL;
- break;
- default:
- break;
- }
+ switch (*inout_specType) {
+ case SP_PATH:
+ if (*inout_paths == NULL)
+ *inout_paths = Lst_New();
+ Lst_Append(*inout_paths, &dirSearchPath);
+ break;
+ case SP_MAIN:
+ /*
+ * Allow targets from the command line to override the
+ * .MAIN node.
+ */
+ if (!Lst_IsEmpty(&opts.create))
+ *inout_specType = SP_NOT;
+ break;
+ case SP_BEGIN:
+ case SP_END:
+ case SP_STALE:
+ case SP_ERROR:
+ case SP_INTERRUPT: {
+ GNode *gn = Targ_GetNode(line);
+ if (doing_depend)
+ ParseMark(gn);
+ gn->type |= OP_NOTMAIN | OP_SPECIAL;
+ Lst_Append(targets, gn);
+ break;
+ }
+ case SP_DEFAULT: {
+ /*
+ * Need to create a node to hang commands on, but we don't
+ * want it in the graph, nor do we want it to be the Main
+ * Target. We claim the node is a transformation rule to make
+ * life easier later, when we'll use Make_HandleUse to
+ * actually apply the .DEFAULT commands.
+ */
+ GNode *gn = GNode_New(".DEFAULT");
+ gn->type |= OP_NOTMAIN | OP_TRANSFORM;
+ Lst_Append(targets, gn);
+ defaultNode = gn;
+ break;
+ }
+ case SP_DELETE_ON_ERROR:
+ deleteOnError = TRUE;
+ break;
+ case SP_NOTPARALLEL:
+ opts.maxJobs = 1;
+ break;
+ case SP_SINGLESHELL:
+ opts.compatMake = TRUE;
+ break;
+ case SP_ORDER:
+ order_pred = NULL;
+ break;
+ default:
+ break;
+ }
}
/*
@@ -1155,221 +1120,225 @@ ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType,
* Call on the suffix module to give us a path to modify.
*/
static Boolean
-ParseDoDependencyTargetPath(const char *line, SearchPathList **inout_paths)
+ParseDoDependencyTargetPath(const char *line, /* XXX: bad name */
+ SearchPathList **inout_paths)
{
- SearchPath *path;
+ SearchPath *path;
- path = Suff_GetPath(&line[5]);
- if (path == NULL) {
- Parse_Error(PARSE_FATAL,
- "Suffix '%s' not defined (yet)",
- &line[5]);
- return FALSE;
- }
+ path = Suff_GetPath(&line[5]);
+ if (path == NULL) {
+ Parse_Error(PARSE_FATAL,
+ "Suffix '%s' not defined (yet)", &line[5]);
+ return FALSE;
+ }
- if (*inout_paths == NULL)
- *inout_paths = Lst_New();
- Lst_Append(*inout_paths, path);
+ if (*inout_paths == NULL)
+ *inout_paths = Lst_New();
+ Lst_Append(*inout_paths, path);
- return TRUE;
+ return TRUE;
}
/*
* See if it's a special target and if so set specType to match it.
*/
static Boolean
-ParseDoDependencyTarget(const char *line, ParseSpecial *inout_specType,
+ParseDoDependencyTarget(const char *line, /* XXX: bad name */
+ ParseSpecial *inout_specType,
GNodeType *out_tOp, SearchPathList **inout_paths)
{
- int keywd;
+ int keywd;
- if (!(*line == '.' && ch_isupper(line[1])))
- return TRUE;
+ if (!(line[0] == '.' && ch_isupper(line[1])))
+ return TRUE;
- /*
- * See if the target is a special target that must have it
- * or its sources handled specially.
- */
- keywd = ParseFindKeyword(line);
- if (keywd != -1) {
- if (*inout_specType == SP_PATH && parseKeywords[keywd].spec != SP_PATH) {
- Parse_Error(PARSE_FATAL, "Mismatched special targets");
- return FALSE;
- }
+ /*
+ * See if the target is a special target that must have it
+ * or its sources handled specially.
+ */
+ keywd = ParseFindKeyword(line);
+ if (keywd != -1) {
+ if (*inout_specType == SP_PATH &&
+ parseKeywords[keywd].spec != SP_PATH) {
+ Parse_Error(PARSE_FATAL, "Mismatched special targets");
+ return FALSE;
+ }
- *inout_specType = parseKeywords[keywd].spec;
- *out_tOp = parseKeywords[keywd].op;
+ *inout_specType = parseKeywords[keywd].spec;
+ *out_tOp = parseKeywords[keywd].op;
- ParseDoDependencyTargetSpecial(inout_specType, line, inout_paths);
+ ParseDoDependencyTargetSpecial(inout_specType, line,
+ inout_paths);
- } else if (strncmp(line, ".PATH", 5) == 0) {
- *inout_specType = SP_PATH;
- if (!ParseDoDependencyTargetPath(line, inout_paths))
- return FALSE;
- }
- return TRUE;
+ } else if (strncmp(line, ".PATH", 5) == 0) {
+ *inout_specType = SP_PATH;
+ if (!ParseDoDependencyTargetPath(line, inout_paths))
+ return FALSE;
+ }
+ return TRUE;
}
static void
-ParseDoDependencyTargetMundane(char *line, StringList *curTargs)
+ParseDoDependencyTargetMundane(char *line, /* XXX: bad name */
+ StringList *curTargs)
{
- if (Dir_HasWildcards(line)) {
- /*
- * Targets are to be sought only in the current directory,
- * so create an empty path for the thing. Note we need to
- * use Dir_Destroy in the destruction of the path as the
- * Dir module could have added a directory to the path...
- */
- SearchPath *emptyPath = Lst_New();
+ if (Dir_HasWildcards(line)) {
+ /*
+ * Targets are to be sought only in the current directory,
+ * so create an empty path for the thing. Note we need to
+ * use Dir_Destroy in the destruction of the path as the
+ * Dir module could have added a directory to the path...
+ */
+ SearchPath *emptyPath = SearchPath_New();
- Dir_Expand(line, emptyPath, curTargs);
+ Dir_Expand(line, emptyPath, curTargs);
- Lst_Destroy(emptyPath, Dir_Destroy);
- } else {
- /*
- * No wildcards, but we want to avoid code duplication,
- * so create a list with the word on it.
- */
- Lst_Append(curTargs, line);
- }
+ 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, line);
+ }
- /* Apply the targets. */
+ /* Apply the targets. */
- while (!Lst_IsEmpty(curTargs)) {
- char *targName = Lst_Dequeue(curTargs);
- GNode *gn = Suff_IsTransform(targName)
+ while (!Lst_IsEmpty(curTargs)) {
+ char *targName = Lst_Dequeue(curTargs);
+ GNode *gn = Suff_IsTransform(targName)
? Suff_AddTransform(targName)
: Targ_GetNode(targName);
- if (doing_depend)
- ParseMark(gn);
+ if (doing_depend)
+ ParseMark(gn);
- Lst_Append(targets, gn);
- }
+ Lst_Append(targets, gn);
+ }
}
static void
ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart)
{
- Boolean warning = FALSE;
- char *cp = *pp;
+ Boolean warning = FALSE;
+ char *cp = *pp;
- while (*cp != '\0') {
- if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':'))
- break;
- if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t'))
- warning = TRUE;
- cp++;
- }
- if (warning)
- Parse_Error(PARSE_WARNING, "Extra target ignored");
+ while (*cp != '\0') {
+ if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':'))
+ break;
+ if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t'))
+ warning = TRUE;
+ cp++;
+ }
+ if (warning)
+ Parse_Error(PARSE_WARNING, "Extra target ignored");
- *pp = cp;
+ *pp = cp;
}
static void
ParseDoDependencyCheckSpec(ParseSpecial specType)
{
- switch (specType) {
- default:
- Parse_Error(PARSE_WARNING,
+ switch (specType) {
+ default:
+ Parse_Error(PARSE_WARNING,
"Special and mundane targets don't mix. "
"Mundane ones ignored");
- break;
- case SP_DEFAULT:
- case SP_STALE:
- case SP_BEGIN:
- case SP_END:
- case SP_ERROR:
- case SP_INTERRUPT:
- /*
- * These create nodes on which to hang commands, so targets
- * shouldn't be empty...
- */
- case SP_NOT:
- /* Nothing special here -- targets can be empty if it wants. */
- break;
- }
+ break;
+ case SP_DEFAULT:
+ case SP_STALE:
+ case SP_BEGIN:
+ case SP_END:
+ case SP_ERROR:
+ case SP_INTERRUPT:
+ /*
+ * These create nodes on which to hang commands, so targets
+ * shouldn't be empty.
+ */
+ case SP_NOT:
+ /* Nothing special here -- targets can be empty if it wants. */
+ break;
+ }
}
static Boolean
ParseDoDependencyParseOp(char **pp, const char *lstart, GNodeType *out_op)
{
- const char *cp = *pp;
+ const char *cp = *pp;
- if (*cp == '!') {
- *out_op = OP_FORCE;
- (*pp)++;
- return TRUE;
- }
+ 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)++;
+ if (*cp == ':') {
+ if (cp[1] == ':') {
+ *out_op = OP_DOUBLEDEP;
+ (*pp) += 2;
+ } else {
+ *out_op = OP_DEPENDS;
+ (*pp)++;
+ }
+ return TRUE;
}
- return TRUE;
- }
- {
- const char *msg = lstart[0] == '.' ? "Unknown directive"
- : "Missing dependency operator";
- Parse_Error(PARSE_FATAL, "%s", msg);
- return FALSE;
- }
+ {
+ const char *msg = lstart[0] == '.'
+ ? "Unknown directive" : "Missing dependency operator";
+ Parse_Error(PARSE_FATAL, "%s", msg);
+ return FALSE;
+ }
}
static void
ClearPaths(SearchPathList *paths)
{
- if (paths != NULL) {
- SearchPathListNode *ln;
- for (ln = paths->first; ln != NULL; ln = ln->next)
- Dir_ClearPath(ln->datum);
- }
+ if (paths != NULL) {
+ SearchPathListNode *ln;
+ for (ln = paths->first; ln != NULL; ln = ln->next)
+ SearchPath_Clear(ln->datum);
+ }
- Dir_SetPATH();
+ Dir_SetPATH();
}
static void
ParseDoDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths)
{
- switch (specType) {
- case SP_SUFFIXES:
- Suff_ClearSuffixes();
- break;
- case SP_PRECIOUS:
- allPrecious = TRUE;
- break;
- case SP_IGNORE:
- opts.ignoreErrors = TRUE;
- break;
- case SP_SILENT:
- opts.beSilent = TRUE;
- break;
- case SP_PATH:
- ClearPaths(paths);
- break;
+ switch (specType) {
+ case SP_SUFFIXES:
+ Suff_ClearSuffixes();
+ break;
+ case SP_PRECIOUS:
+ allPrecious = TRUE;
+ break;
+ case SP_IGNORE:
+ opts.ignoreErrors = TRUE;
+ break;
+ case SP_SILENT:
+ opts.beSilent = TRUE;
+ break;
+ case SP_PATH:
+ ClearPaths(paths);
+ break;
#ifdef POSIX
- case SP_POSIX:
- Var_Set("%POSIX", "1003.2", VAR_GLOBAL);
- break;
+ case SP_POSIX:
+ Var_Set("%POSIX", "1003.2", VAR_GLOBAL);
+ break;
#endif
- default:
- break;
- }
+ default:
+ break;
+ }
}
static void
AddToPaths(const char *dir, SearchPathList *paths)
{
- if (paths != NULL) {
- SearchPathListNode *ln;
- for (ln = paths->first; ln != NULL; ln = ln->next)
- (void)Dir_AddDir(ln->datum, dir);
- }
+ if (paths != NULL) {
+ SearchPathListNode *ln;
+ for (ln = paths->first; ln != NULL; ln = ln->next)
+ (void)Dir_AddDir(ln->datum, dir);
+ }
}
/*
@@ -1403,28 +1372,28 @@ static void
ParseDoDependencySourceSpecial(ParseSpecial specType, char *word,
SearchPathList *paths)
{
- switch (specType) {
- case SP_SUFFIXES:
- Suff_AddSuffix(word, &mainNode);
- break;
- case SP_PATH:
- AddToPaths(word, paths);
- break;
- case SP_INCLUDES:
- Suff_AddInclude(word);
- break;
- case SP_LIBS:
- Suff_AddLib(word);
- break;
- case SP_NULL:
- Suff_SetNull(word);
- break;
- case SP_OBJDIR:
- Main_SetObjdir(FALSE, "%s", word);
- break;
- default:
- break;
- }
+ switch (specType) {
+ case SP_SUFFIXES:
+ Suff_AddSuffix(word, &mainNode);
+ break;
+ case SP_PATH:
+ AddToPaths(word, paths);
+ break;
+ case SP_INCLUDES:
+ Suff_AddInclude(word);
+ break;
+ case SP_LIBS:
+ Suff_AddLib(word);
+ break;
+ case SP_NULL:
+ Suff_SetNull(word);
+ break;
+ case SP_OBJDIR:
+ Main_SetObjdir(FALSE, "%s", word);
+ break;
+ default:
+ break;
+ }
}
static Boolean
@@ -1436,163 +1405,175 @@ ParseDoDependencyTargets(char **inout_cp,
SearchPathList **inout_paths,
StringList *curTargs)
{
- char *cp = *inout_cp;
- char *line = *inout_line;
- char savec;
+ char *cp;
+ char *tgt = *inout_line;
+ char savec;
+ const char *p;
- for (;;) {
- /*
- * Here LINE points to the beginning of the next word, and
- * LSTART points to the actual beginning of the line.
- */
+ for (;;) {
+ /*
+ * Here LINE points to the beginning of the next word, and
+ * LSTART points to the actual beginning of the line.
+ */
- /* Find the end of the next word. */
- cp = line;
- ParseDependencyTargetWord(&cp, lstart);
+ /* Find the end of the next word. */
+ cp = tgt;
+ p = cp;
+ ParseDependencyTargetWord(&p, lstart);
+ cp += p - cp;
- /*
- * If the word is followed by a left parenthesis, it's the
- * name of an object file inside an archive (ar file).
- */
- if (!ParseIsEscaped(lstart, cp) && *cp == '(') {
- /*
- * Archives must be handled specially to make sure the OP_ARCHV
- * flag is set in their 'type' field, for one thing, and because
- * things like "archive(file1.o file2.o file3.o)" are permissible.
- * Arch_ParseArchive will set 'line' to be the first non-blank
- * after the archive-spec. It creates/finds nodes for the members
- * and places them on the given list, returning TRUE if all
- * went well and FALSE if there was an error in the
- * specification. On error, line should remain untouched.
- */
- if (!Arch_ParseArchive(&line, targets, VAR_CMDLINE)) {
- Parse_Error(PARSE_FATAL,
- "Error in archive specification: \"%s\"", line);
- return FALSE;
- } else {
- /* Done with this word; on to the next. */
- cp = line;
- continue;
- }
- }
+ /*
+ * If the word is followed by a left parenthesis, it's the
+ * name of an object file inside an archive (ar file).
+ */
+ if (!ParseIsEscaped(lstart, cp) && *cp == '(') {
+ /*
+ * Archives must be handled specially to make sure the
+ * OP_ARCHV flag is set in their 'type' field, for one
+ * thing, and because things like "archive(file1.o
+ * file2.o file3.o)" are permissible.
+ *
+ * Arch_ParseArchive will set 'line' to be the first
+ * non-blank after the archive-spec. It creates/finds
+ * nodes for the members and places them on the given
+ * list, returning TRUE if all went well and FALSE if
+ * there was an error in the specification. On error,
+ * line should remain untouched.
+ */
+ if (!Arch_ParseArchive(&tgt, targets, VAR_CMDLINE)) {
+ Parse_Error(PARSE_FATAL,
+ "Error in archive specification: \"%s\"",
+ tgt);
+ return FALSE;
+ }
- if (!*cp) {
- ParseErrorNoDependency(lstart);
- return FALSE;
- }
+ cp = tgt;
+ continue;
+ }
- /* Insert a null terminator. */
- savec = *cp;
- *cp = '\0';
+ if (*cp == '\0') {
+ ParseErrorNoDependency(lstart);
+ return FALSE;
+ }
- if (!ParseDoDependencyTarget(line, inout_specType, inout_tOp,
- inout_paths))
- return FALSE;
+ /* Insert a null terminator. */
+ savec = *cp;
+ *cp = '\0';
- /*
- * Have word in line. Get or create its node and stick it at
- * the end of the targets list
- */
- if (*inout_specType == SP_NOT && *line != '\0')
- ParseDoDependencyTargetMundane(line, curTargs);
- else if (*inout_specType == SP_PATH && *line != '.' && *line != '\0')
- Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line);
+ if (!ParseDoDependencyTarget(tgt, inout_specType, inout_tOp,
+ inout_paths))
+ return FALSE;
- /* Don't need the inserted null terminator any more. */
- *cp = savec;
+ /*
+ * Have word in line. Get or create its node and stick it at
+ * the end of the targets list
+ */
+ if (*inout_specType == SP_NOT && *tgt != '\0')
+ ParseDoDependencyTargetMundane(tgt, curTargs);
+ else if (*inout_specType == SP_PATH && *tgt != '.' &&
+ *tgt != '\0')
+ Parse_Error(PARSE_WARNING, "Extra target (%s) ignored",
+ tgt);
- /*
- * If it is a special type and not .PATH, it's the only target we
- * allow on this line...
- */
- if (*inout_specType != SP_NOT && *inout_specType != SP_PATH)
- ParseDoDependencyTargetExtraWarn(&cp, lstart);
- else
- pp_skip_whitespace(&cp);
+ /* Don't need the inserted null terminator any more. */
+ *cp = savec;
- line = cp;
- if (*line == '\0')
- break;
- if ((*line == '!' || *line == ':') && !ParseIsEscaped(lstart, line))
- break;
- }
+ /*
+ * If it is a special type and not .PATH, it's the only target
+ * we allow on this line.
+ */
+ if (*inout_specType != SP_NOT && *inout_specType != SP_PATH)
+ ParseDoDependencyTargetExtraWarn(&cp, lstart);
+ else
+ pp_skip_whitespace(&cp);
- *inout_cp = cp;
- *inout_line = line;
- return TRUE;
+ tgt = cp;
+ if (*tgt == '\0')
+ break;
+ if ((*tgt == '!' || *tgt == ':') &&
+ !ParseIsEscaped(lstart, tgt))
+ break;
+ }
+
+ *inout_cp = cp;
+ *inout_line = tgt;
+ return TRUE;
}
static void
ParseDoDependencySourcesSpecial(char *start, char *end,
ParseSpecial specType, SearchPathList *paths)
{
- char savec;
+ char savec;
- while (*start) {
- while (*end && !ch_isspace(*end))
- end++;
- savec = *end;
- *end = '\0';
- ParseDoDependencySourceSpecial(specType, start, paths);
- *end = savec;
- if (savec != '\0')
- end++;
- pp_skip_whitespace(&end);
- start = end;
- }
+ while (*start != '\0') {
+ while (*end != '\0' && !ch_isspace(*end))
+ end++;
+ savec = *end;
+ *end = '\0';
+ ParseDoDependencySourceSpecial(specType, start, paths);
+ *end = savec;
+ if (savec != '\0')
+ end++;
+ pp_skip_whitespace(&end);
+ start = end;
+ }
}
static Boolean
ParseDoDependencySourcesMundane(char *start, char *end,
ParseSpecial specType, GNodeType tOp)
{
- while (*start != '\0') {
- /*
- * The targets take real sources, so we must beware of archive
- * specifications (i.e. things with left parentheses in them)
- * and handle them accordingly.
- */
- for (; *end && !ch_isspace(*end); end++) {
- if (*end == '(' && end > start && end[-1] != '$') {
+ while (*start != '\0') {
/*
- * Only stop for a left parenthesis if it isn't at the
- * start of a word (that'll be for variable changes
- * later) and isn't preceded by a dollar sign (a dynamic
- * source).
+ * The targets take real sources, so we must beware of archive
+ * specifications (i.e. things with left parentheses in them)
+ * and handle them accordingly.
*/
- break;
- }
- }
+ for (; *end != '\0' && !ch_isspace(*end); end++) {
+ if (*end == '(' && end > start && end[-1] != '$') {
+ /*
+ * Only stop for a left parenthesis if it
+ * isn't at the start of a word (that'll be
+ * for variable changes later) and isn't
+ * preceded by a dollar sign (a dynamic
+ * source).
+ */
+ break;
+ }
+ }
- if (*end == '(') {
- GNodeList *sources = Lst_New();
- if (!Arch_ParseArchive(&start, sources, VAR_CMDLINE)) {
- Parse_Error(PARSE_FATAL,
- "Error in source archive spec \"%s\"", start);
- return FALSE;
- }
-
- while (!Lst_IsEmpty(sources)) {
- GNode *gn = Lst_Dequeue(sources);
- ParseDoSrc(tOp, gn->name, specType);
- }
- Lst_Free(sources);
- end = start;
- } else {
- if (*end) {
- *end = '\0';
- end++;
- }
+ if (*end == '(') {
+ GNodeList sources = LST_INIT;
+ if (!Arch_ParseArchive(&start, &sources, VAR_CMDLINE)) {
+ Parse_Error(PARSE_FATAL,
+ "Error in source archive spec \"%s\"",
+ start);
+ return FALSE;
+ }
+
+ while (!Lst_IsEmpty(&sources)) {
+ GNode *gn = Lst_Dequeue(&sources);
+ ParseDependencySource(tOp, gn->name, specType);
+ }
+ Lst_Done(&sources);
+ end = start;
+ } else {
+ if (*end != '\0') {
+ *end = '\0';
+ end++;
+ }
- ParseDoSrc(tOp, start, specType);
+ ParseDependencySource(tOp, start, specType);
+ }
+ pp_skip_whitespace(&end);
+ start = end;
}
- pp_skip_whitespace(&end);
- start = end;
- }
- return TRUE;
+ return TRUE;
}
-/* Parse a dependency line consisting of targets, followed by a dependency
+/*
+ * Parse a dependency line consisting of targets, followed by a dependency
* operator, optionally followed by sources.
*
* The nodes of the sources are linked as children to the nodes of the
@@ -1621,177 +1602,181 @@ ParseDoDependencySourcesMundane(char *start, char *end,
static void
ParseDoDependency(char *line)
{
- char *cp; /* our current position */
- GNodeType op; /* the operator on the line */
- SearchPathList *paths; /* search paths to alter when parsing
+ 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 */
- StringList *curTargs; /* target names to be found and added
- * to the targets list */
- char *lstart = line;
-
- /*
- * specType contains the SPECial TYPE of the current target. It is SP_NOT
- * if the target is unspecial. If it *is* special, however, the children
- * are linked as children of the parent but not vice versa.
- */
- ParseSpecial specType = SP_NOT;
-
- DEBUG1(PARSE, "ParseDoDependency(%s)\n", line);
- tOp = OP_NONE;
-
- paths = NULL;
-
- curTargs = Lst_New();
-
- /*
- * First, grind through the targets.
- */
- if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp, &paths,
- curTargs))
- goto out;
-
- /* Don't need the list of target names anymore.
- * The targets themselves are now in the global variable 'targets'. */
- Lst_Free(curTargs);
- curTargs = NULL;
-
- if (!Lst_IsEmpty(targets))
- ParseDoDependencyCheckSpec(specType);
-
- /*
- * Have now parsed all the target names. Must parse the operator next.
- */
- if (!ParseDoDependencyParseOp(&cp, lstart, &op))
- goto out;
-
- /*
- * Apply the operator to the target. This is how we remember which
- * operator a target was defined with. It fails if the operator
- * used isn't consistent across all references.
- */
- ApplyDependencyOperator(op);
-
- /*
- * Onward to the sources.
- *
- * LINE will now point to the first source word, if any, or the
- * end of the string if not.
- */
- pp_skip_whitespace(&cp);
- line = cp; /* XXX: 'line' is an inappropriate name */
-
- /*
- * Several special targets take different actions if present with no
- * sources:
- * a .SUFFIXES line with no sources clears out all old suffixes
- * a .PRECIOUS line makes all targets precious
- * a .IGNORE line ignores errors for all targets
- * a .SILENT line creates silence when making all targets
- * a .PATH removes all directories from the search path(s).
- */
- if (line[0] == '\0') {
- ParseDoDependencySourcesEmpty(specType, paths);
- } else if (specType == SP_MFLAGS) {
+ GNodeType tOp; /* operator from special target */
+ /* target names to be found and added to the targets list */
+ StringList curTargs = LST_INIT;
+ char *lstart = line;
+
/*
- * Call on functions in main.c to deal with these arguments and
- * set the initial character to a null-character so the loop to
- * get sources won't get anything
+ * 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.
*/
- Main_ParseArgLine(line);
- *line = '\0';
- } else if (specType == SP_SHELL) {
- if (!Job_ParseShell(line)) {
- Parse_Error(PARSE_FATAL, "improper shell specification");
- goto out;
- }
- *line = '\0';
- } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL ||
- specType == SP_DELETE_ON_ERROR) {
- *line = '\0';
- }
-
- /* Now go for the sources. */
- if (specType == SP_SUFFIXES || specType == SP_PATH ||
- specType == SP_INCLUDES || specType == SP_LIBS ||
- specType == SP_NULL || specType == SP_OBJDIR)
- {
- ParseDoDependencySourcesSpecial(line, cp, specType, paths);
- if (paths) {
- Lst_Free(paths);
- paths = NULL;
- }
- if (specType == SP_PATH)
- Dir_SetPATH();
- } else {
- assert(paths == NULL);
- if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp))
- goto out;
- }
-
- FindMainTarget();
+ ParseSpecial specType = SP_NOT;
+
+ DEBUG1(PARSE, "ParseDoDependency(%s)\n", line);
+ tOp = OP_NONE;
+
+ paths = NULL;
+
+ /*
+ * First, grind through the targets.
+ */
+ /* XXX: don't use line as an iterator variable */
+ if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp,
+ &paths, &curTargs))
+ goto out;
+
+ /*
+ * Don't need the list of target names anymore.
+ * The targets themselves are now in the global variable 'targets'.
+ */
+ Lst_Done(&curTargs);
+ Lst_Init(&curTargs);
+
+ if (!Lst_IsEmpty(targets))
+ ParseDoDependencyCheckSpec(specType);
+
+ /*
+ * Have now parsed all the target names. Must parse the operator next.
+ */
+ if (!ParseDoDependencyParseOp(&cp, lstart, &op))
+ goto out;
+
+ /*
+ * Apply the operator to the target. This is how we remember which
+ * operator a target was defined with. It fails if the operator
+ * used isn't consistent across all references.
+ */
+ ApplyDependencyOperator(op);
+
+ /*
+ * Onward to the sources.
+ *
+ * LINE will now point to the first source word, if any, or the
+ * end of the string if not.
+ */
+ pp_skip_whitespace(&cp);
+ line = cp; /* XXX: 'line' is an inappropriate name */
+
+ /*
+ * Several special targets take different actions if present with no
+ * sources:
+ * a .SUFFIXES line with no sources clears out all old suffixes
+ * a .PRECIOUS line makes all targets precious
+ * a .IGNORE line ignores errors for all targets
+ * a .SILENT line creates silence when making all targets
+ * a .PATH removes all directories from the search path(s).
+ */
+ if (line[0] == '\0') {
+ ParseDoDependencySourcesEmpty(specType, paths);
+ } else if (specType == SP_MFLAGS) {
+ /*
+ * Call on functions in main.c to deal with these arguments and
+ * set the initial character to a null-character so the loop to
+ * get sources won't get anything
+ */
+ Main_ParseArgLine(line);
+ *line = '\0';
+ } else if (specType == SP_SHELL) {
+ if (!Job_ParseShell(line)) {
+ Parse_Error(PARSE_FATAL,
+ "improper shell specification");
+ goto out;
+ }
+ *line = '\0';
+ } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL ||
+ specType == SP_DELETE_ON_ERROR) {
+ *line = '\0';
+ }
+
+ /* Now go for the sources. */
+ if (specType == SP_SUFFIXES || specType == SP_PATH ||
+ specType == SP_INCLUDES || specType == SP_LIBS ||
+ specType == SP_NULL || specType == SP_OBJDIR) {
+ ParseDoDependencySourcesSpecial(line, cp, specType, paths);
+ if (paths != NULL) {
+ Lst_Free(paths);
+ paths = NULL;
+ }
+ if (specType == SP_PATH)
+ Dir_SetPATH();
+ } else {
+ assert(paths == NULL);
+ if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp))
+ goto out;
+ }
+
+ FindMainTarget();
out:
- if (paths != NULL)
- Lst_Free(paths);
- if (curTargs != NULL)
- Lst_Free(curTargs);
+ 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 */
+ 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. */
+/*
+ * Determine the assignment operator and adjust the end of the variable
+ * name accordingly.
+ */
static void
AdjustVarassignOp(const VarAssignParsed *pvar, const char *value,
VarAssign *out_var)
{
- const char *op = pvar->eq;
- const char * const name = pvar->nameStart;
- VarAssignOp type;
+ const char *op = pvar->eq;
+ const char *const name = pvar->nameStart;
+ VarAssignOp type;
- if (op > name && op[-1] == '+') {
- type = VAR_APPEND;
- op--;
+ if (op > name && op[-1] == '+') {
+ type = VAR_APPEND;
+ op--;
- } else if (op > name && op[-1] == '?') {
- op--;
- type = VAR_DEFAULT;
+ } else if (op > name && op[-1] == '?') {
+ op--;
+ type = VAR_DEFAULT;
- } else if (op > name && op[-1] == ':') {
- op--;
- type = VAR_SUBST;
+ } else if (op > name && op[-1] == ':') {
+ op--;
+ type = VAR_SUBST;
- } else if (op > name && op[-1] == '!') {
- op--;
- type = VAR_SHELL;
+ } else if (op > name && op[-1] == '!') {
+ op--;
+ type = VAR_SHELL;
- } else {
- type = VAR_NORMAL;
+ } else {
+ type = VAR_NORMAL;
#ifdef SUNSHCMD
- while (op > name && ch_isspace(op[-1]))
- op--;
+ while (op > name && ch_isspace(op[-1]))
+ op--;
- if (op >= name + 3 && op[-3] == ':' && op[-2] == 's' && op[-1] == 'h') {
- type = VAR_SHELL;
- op -= 3;
- }
+ if (op >= name + 3 && op[-3] == ':' && op[-2] == 's' &&
+ op[-1] == 'h') {
+ type = VAR_SHELL;
+ op -= 3;
+ }
#endif
- }
+ }
- {
- const char *nameEnd = pvar->nameEnd < op ? pvar->nameEnd : op;
- out_var->varname = bmake_strsedup(pvar->nameStart, nameEnd);
- out_var->op = type;
- out_var->value = value;
- }
+ {
+ const char *nameEnd = pvar->nameEnd < op ? pvar->nameEnd : op;
+ out_var->varname = bmake_strsedup(pvar->nameStart, nameEnd);
+ out_var->op = type;
+ out_var->value = value;
+ }
}
-/* Parse a variable assignment, consisting of a single-word variable name,
+/*
+ * Parse a variable assignment, consisting of a single-word variable name,
* optional whitespace, an assignment operator, optional whitespace and the
* variable value.
*
@@ -1801,309 +1786,311 @@ AdjustVarassignOp(const VarAssignParsed *pvar, const char *value,
* C++=/usr/bin/CC
* is interpreted as "C+ +=" instead of "C++ =".
*
- * Used for both lines in a file and command line arguments. */
+ * Used for both lines in a file and command line arguments.
+ */
Boolean
Parse_IsVar(const char *p, VarAssign *out_var)
{
- VarAssignParsed pvar;
- const char *firstSpace = NULL;
- int level = 0;
+ VarAssignParsed pvar;
+ const char *firstSpace = NULL;
+ int level = 0;
- cpp_skip_hspace(&p); /* Skip to variable name */
+ cpp_skip_hspace(&p); /* Skip to variable name */
- /* During parsing, the '+' of the '+=' operator is initially parsed
- * as part of the variable name. It is later corrected, as is the ':sh'
- * modifier. Of these two (nameEnd and op), the earlier one determines the
- * actual end of the variable name. */
- pvar.nameStart = p;
+ /*
+ * During parsing, the '+' of the '+=' operator is initially parsed
+ * as part of the variable name. It is later corrected, as is the
+ * ':sh' modifier. Of these two (nameEnd and op), the earlier one
+ * determines the actual end of the variable name.
+ */
+ pvar.nameStart = p;
#ifdef CLEANUP
- pvar.nameEnd = NULL;
- pvar.eq = NULL;
+ pvar.nameEnd = NULL;
+ pvar.eq = NULL;
#endif
- /* Scan for one of the assignment operators outside a variable expansion */
- while (*p != '\0') {
- char ch = *p++;
- if (ch == '(' || ch == '{') {
- level++;
- continue;
- }
- if (ch == ')' || ch == '}') {
- level--;
- continue;
- }
+ /*
+ * Scan for one of the assignment operators outside a variable
+ * expansion.
+ */
+ while (*p != '\0') {
+ char ch = *p++;
+ if (ch == '(' || ch == '{') {
+ level++;
+ continue;
+ }
+ if (ch == ')' || ch == '}') {
+ level--;
+ continue;
+ }
- if (level != 0)
- continue;
+ if (level != 0)
+ continue;
- if (ch == ' ' || ch == '\t')
- if (firstSpace == NULL)
- firstSpace = p - 1;
- while (ch == ' ' || ch == '\t')
- ch = *p++;
+ if (ch == ' ' || ch == '\t')
+ if (firstSpace == NULL)
+ firstSpace = p - 1;
+ while (ch == ' ' || ch == '\t')
+ ch = *p++;
#ifdef SUNSHCMD
- if (ch == ':' && p[0] == 's' && p[1] == 'h') {
- p += 2;
- continue;
- }
+ 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 (ch == '=') {
+ pvar.eq = p - 1;
+ pvar.nameEnd = firstSpace != NULL ? firstSpace : p - 1;
+ cpp_skip_whitespace(&p);
+ AdjustVarassignOp(&pvar, p, out_var);
+ return TRUE;
+ }
+ if (*p == '=' &&
+ (ch == '+' || ch == ':' || ch == '?' || ch == '!')) {
+ pvar.eq = p;
+ pvar.nameEnd = firstSpace != NULL ? firstSpace : p;
+ p++;
+ cpp_skip_whitespace(&p);
+ AdjustVarassignOp(&pvar, p, out_var);
+ return TRUE;
+ }
+ if (firstSpace != NULL)
+ return FALSE;
}
- if (firstSpace != NULL)
- return FALSE;
- }
- return FALSE;
+ return FALSE;
}
+/*
+ * Check for syntax errors such as unclosed expressions or unknown modifiers.
+ */
static void
VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *ctxt)
{
- if (opts.lint) {
- if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) {
- /* Check for syntax errors such as unclosed expressions or
- * unknown modifiers. */
- char *expandedValue;
+ if (opts.strict) {
+ if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) {
+ char *expandedValue;
- (void)Var_Subst(uvalue, ctxt, VARE_NONE, &expandedValue);
- /* TODO: handle errors */
- free(expandedValue);
+ (void)Var_Subst(uvalue, ctxt, VARE_NONE,
+ &expandedValue);
+ /* TODO: handle errors */
+ free(expandedValue);
+ }
}
- }
}
static void
VarAssign_EvalSubst(const char *name, const char *uvalue, GNode *ctxt,
- const char **out_avalue, void **out_avalue_freeIt)
-{
- const char *avalue = uvalue;
- char *evalue;
- Boolean savedPreserveUndefined = preserveUndefined;
-
- /* TODO: Can this assignment to preserveUndefined be moved further down
- * to the actually interesting Var_Subst call, without affecting any
- * edge cases?
- *
- * It might affect the implicit expansion of the variable name in the
- * Var_Exists and Var_Set calls, even though it's unlikely that anyone
- * cared about this edge case when adding this code. In addition,
- * variable assignments should not refer to any undefined variables in
- * the variable name. */
- preserveUndefined = TRUE;
-
- /*
- * make sure that we set the variable the first time to nothing
- * so that it gets substituted!
- */
- if (!Var_Exists(name, ctxt))
- Var_Set(name, "", ctxt);
-
- (void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_KEEP_DOLLAR, &evalue);
- /* TODO: handle errors */
- preserveUndefined = savedPreserveUndefined;
- avalue = evalue;
- Var_Set(name, avalue, ctxt);
-
- *out_avalue = avalue;
- *out_avalue_freeIt = evalue;
+ FStr *out_avalue)
+{
+ const char *avalue;
+ char *evalue;
+
+ /*
+ * make sure that we set the variable the first time to nothing
+ * so that it gets substituted!
+ */
+ if (!Var_Exists(name, ctxt))
+ Var_Set(name, "", ctxt);
+
+ (void)Var_Subst(uvalue, ctxt,
+ VARE_WANTRES | VARE_KEEP_DOLLAR | VARE_KEEP_UNDEF, &evalue);
+ /* TODO: handle errors */
+
+ avalue = evalue;
+ Var_Set(name, avalue, ctxt);
+
+ *out_avalue = (FStr){ avalue, evalue };
}
static void
VarAssign_EvalShell(const char *name, const char *uvalue, GNode *ctxt,
- const char **out_avalue, void **out_avalue_freeIt)
-{
- const char *cmd, *errfmt;
- char *cmdOut;
- void *cmd_freeIt = NULL;
-
- cmd = uvalue;
- if (strchr(cmd, '$') != NULL) {
- char *ecmd;
- (void)Var_Subst(cmd, VAR_CMDLINE, VARE_WANTRES | VARE_UNDEFERR, &ecmd);
- /* TODO: handle errors */
- cmd = cmd_freeIt = ecmd;
- }
+ FStr *out_avalue)
+{
+ FStr cmd;
+ const char *errfmt;
+ char *cmdOut;
+
+ cmd = FStr_InitRefer(uvalue);
+ if (strchr(cmd.str, '$') != NULL) {
+ char *expanded;
+ (void)Var_Subst(cmd.str, VAR_CMDLINE,
+ VARE_WANTRES | VARE_UNDEFERR, &expanded);
+ /* TODO: handle errors */
+ cmd = FStr_InitOwn(expanded);
+ }
- cmdOut = Cmd_Exec(cmd, &errfmt);
- Var_Set(name, cmdOut, ctxt);
- *out_avalue = *out_avalue_freeIt = cmdOut;
+ cmdOut = Cmd_Exec(cmd.str, &errfmt);
+ Var_Set(name, cmdOut, ctxt);
+ *out_avalue = FStr_InitOwn(cmdOut);
- if (errfmt)
- Parse_Error(PARSE_WARNING, errfmt, cmd);
+ if (errfmt != NULL)
+ Parse_Error(PARSE_WARNING, errfmt, cmd.str);
- free(cmd_freeIt);
+ FStr_Done(&cmd);
}
-/* Perform a variable assignment.
+/*
+ * Perform a variable assignment.
*
* The actual value of the variable is returned in *out_avalue and
* *out_avalue_freeIt. Especially for VAR_SUBST and VAR_SHELL this can differ
* from the literal value.
*
* Return whether the assignment was actually done. The assignment is only
- * skipped if the operator is '?=' and the variable already exists. */
+ * skipped if the operator is '?=' and the variable already exists.
+ */
static Boolean
VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue,
- GNode *ctxt, const char **out_avalue, void **out_avalue_freeIt)
-{
- const char *avalue = uvalue;
- void *avalue_freeIt = NULL;
+ GNode *ctxt, FStr *out_TRUE_avalue)
+{
+ FStr avalue = FStr_InitRefer(uvalue);
+
+ if (op == VAR_APPEND)
+ Var_Append(name, uvalue, ctxt);
+ else if (op == VAR_SUBST)
+ VarAssign_EvalSubst(name, uvalue, ctxt, &avalue);
+ else if (op == VAR_SHELL)
+ VarAssign_EvalShell(name, uvalue, ctxt, &avalue);
+ else {
+ if (op == VAR_DEFAULT && Var_Exists(name, ctxt))
+ return FALSE;
- if (op == VAR_APPEND)
- Var_Append(name, uvalue, ctxt);
- else if (op == VAR_SUBST)
- VarAssign_EvalSubst(name, uvalue, ctxt, &avalue, &avalue_freeIt);
- else if (op == VAR_SHELL)
- VarAssign_EvalShell(name, uvalue, ctxt, &avalue, &avalue_freeIt);
- else {
- if (op == VAR_DEFAULT && Var_Exists(name, ctxt)) {
- *out_avalue_freeIt = NULL;
- return FALSE;
+ /* Normal assignment -- just do it. */
+ Var_Set(name, uvalue, ctxt);
}
- /* Normal assignment -- just do it. */
- Var_Set(name, uvalue, ctxt);
- }
-
- *out_avalue = avalue;
- *out_avalue_freeIt = avalue_freeIt;
- return TRUE;
+ *out_TRUE_avalue = avalue;
+ return TRUE;
}
static void
VarAssignSpecial(const char *name, const char *avalue)
{
- if (strcmp(name, MAKEOVERRIDES) == 0)
- Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */
- else if (strcmp(name, ".CURDIR") == 0) {
- /*
- * Someone is being (too?) clever...
- * Let's pretend they know what they are doing and
- * re-initialize the 'cur' CachedDir.
- */
- Dir_InitCur(avalue);
- Dir_SetPATH();
- } else if (strcmp(name, MAKE_JOB_PREFIX) == 0)
- Job_SetPrefix();
- else if (strcmp(name, MAKE_EXPORTED) == 0)
- Var_Export(avalue, FALSE);
+ if (strcmp(name, MAKEOVERRIDES) == 0)
+ Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */
+ else if (strcmp(name, ".CURDIR") == 0) {
+ /*
+ * Someone is being (too?) clever...
+ * Let's pretend they know what they are doing and
+ * re-initialize the 'cur' CachedDir.
+ */
+ Dir_InitCur(avalue);
+ Dir_SetPATH();
+ } else if (strcmp(name, MAKE_JOB_PREFIX) == 0)
+ Job_SetPrefix();
+ else if (strcmp(name, MAKE_EXPORTED) == 0)
+ Var_ExportVars(avalue);
}
/* Perform the variable variable assignment in the given context. */
void
Parse_DoVar(VarAssign *var, GNode *ctxt)
{
- const char *avalue; /* actual value (maybe expanded) */
- void *avalue_freeIt;
+ FStr avalue; /* actual value (maybe expanded) */
- VarCheckSyntax(var->op, var->value, ctxt);
- if (VarAssign_Eval(var->varname, var->op, var->value, ctxt,
- &avalue, &avalue_freeIt))
- VarAssignSpecial(var->varname, avalue);
+ VarCheckSyntax(var->op, var->value, ctxt);
+ if (VarAssign_Eval(var->varname, var->op, var->value, ctxt, &avalue)) {
+ VarAssignSpecial(var->varname, avalue.str);
+ FStr_Done(&avalue);
+ }
- free(avalue_freeIt);
- free(var->varname);
+ free(var->varname);
}
-/* See if the command possibly calls a sub-make by using the variable
- * expressions ${.MAKE}, ${MAKE} or the plain word "make". */
+/*
+ * See if the command possibly calls a sub-make by using the variable
+ * expressions ${.MAKE}, ${MAKE} or the plain word "make".
+ */
static Boolean
MaybeSubMake(const char *cmd)
{
- const char *start;
+ const char *start;
- for (start = cmd; *start != '\0'; start++) {
- const char *p = start;
- char endc;
+ for (start = cmd; *start != '\0'; start++) {
+ const char *p = start;
+ char endc;
- /* XXX: What if progname != "make"? */
- if (p[0] == 'm' && p[1] == 'a' && p[2] == 'k' && p[3] == 'e')
- if (start == cmd || !ch_isalnum(p[-1]))
- if (!ch_isalnum(p[4]))
- return TRUE;
+ /* XXX: What if progname != "make"? */
+ if (p[0] == 'm' && p[1] == 'a' && p[2] == 'k' && p[3] == 'e')
+ if (start == cmd || !ch_isalnum(p[-1]))
+ if (!ch_isalnum(p[4]))
+ return TRUE;
- if (*p != '$')
- continue;
- p++;
+ if (*p != '$')
+ continue;
+ p++;
- if (*p == '{')
- endc = '}';
- else if (*p == '(')
- endc = ')';
- else
- continue;
- p++;
+ if (*p == '{')
+ endc = '}';
+ else if (*p == '(')
+ endc = ')';
+ else
+ continue;
+ p++;
- if (*p == '.') /* Accept either ${.MAKE} or ${MAKE}. */
- p++;
+ if (*p == '.') /* Accept either ${.MAKE} or ${MAKE}. */
+ p++;
- if (p[0] == 'M' && p[1] == 'A' && p[2] == 'K' && p[3] == 'E')
- if (p[4] == endc)
- return TRUE;
- }
- return FALSE;
+ if (p[0] == 'M' && p[1] == 'A' && p[2] == 'K' && p[3] == 'E')
+ if (p[4] == endc)
+ return TRUE;
+ }
+ return FALSE;
}
-/* Append the command to the target node.
+/*
+ * Append the command to the target node.
*
* The node may be marked as a submake node if the command is determined to
- * be that. */
+ * be that.
+ */
static void
ParseAddCmd(GNode *gn, char *cmd)
{
- /* Add to last (ie current) cohort for :: targets */
- if ((gn->type & OP_DOUBLEDEP) && gn->cohorts->last != NULL)
- gn = gn->cohorts->last->datum;
-
- /* if target already supplied, ignore commands */
- if (!(gn->type & OP_HAS_COMMANDS)) {
- Lst_Append(gn->commands, cmd);
- if (MaybeSubMake(cmd))
- gn->type |= OP_SUBMAKE;
- ParseMark(gn);
- } else {
+ /* Add to last (ie current) cohort for :: targets */
+ if ((gn->type & OP_DOUBLEDEP) && gn->cohorts.last != NULL)
+ gn = gn->cohorts.last->datum;
+
+ /* if target already supplied, ignore commands */
+ if (!(gn->type & OP_HAS_COMMANDS)) {
+ Lst_Append(&gn->commands, cmd);
+ if (MaybeSubMake(cmd))
+ gn->type |= OP_SUBMAKE;
+ ParseMark(gn);
+ } else {
#if 0
- /* XXX: We cannot do this until we fix the tree */
- Lst_Append(gn->commands, cmd);
- Parse_Error(PARSE_WARNING,
- "overriding commands for target \"%s\"; "
- "previous commands defined at %s: %d ignored",
- gn->name, gn->fname, gn->lineno);
+ /* XXX: We cannot do this until we fix the tree */
+ Lst_Append(&gn->commands, cmd);
+ Parse_Error(PARSE_WARNING,
+ "overriding commands for target \"%s\"; "
+ "previous commands defined at %s: %d ignored",
+ gn->name, gn->fname, gn->lineno);
#else
- Parse_Error(PARSE_WARNING,
+ Parse_Error(PARSE_WARNING,
"duplicate script for target \"%s\" ignored",
gn->name);
- ParseErrorInternal(gn->fname, (size_t)gn->lineno, PARSE_WARNING,
- "using previous script for \"%s\" defined here",
- gn->name);
+ ParseErrorInternal(gn->fname, (size_t)gn->lineno, PARSE_WARNING,
+ "using previous script for \"%s\" defined here",
+ gn->name);
#endif
- }
+ }
}
-/* Add a directory to the path searched for included makefiles bracketed
- * by double-quotes. */
+/*
+ * Add a directory to the path searched for included makefiles bracketed
+ * by double-quotes.
+ */
void
Parse_AddIncludeDir(const char *dir)
{
- (void)Dir_AddDir(parseIncPath, dir);
+ (void)Dir_AddDir(parseIncPath, dir);
}
-/* Handle one of the .[-ds]include directives by remembering the current file
+/*
+ * Handle one of the .[-ds]include directives by remembering the current file
* and pushing the included file on the stack. After the included file has
* finished, parsing continues with the including file; see Parse_SetInput
* and ParseEOF.
@@ -2115,314 +2102,331 @@ Parse_AddIncludeDir(const char *dir)
static void
Parse_include_file(char *file, Boolean isSystem, Boolean depinc, Boolean silent)
{
- struct loadedfile *lf;
- char *fullname; /* full pathname of file */
- char *newName;
- char *slash, *incdir;
- int fd;
- int i;
+ struct loadedfile *lf;
+ char *fullname; /* full pathname of file */
+ char *newName;
+ char *slash, *incdir;
+ int fd;
+ int i;
- fullname = file[0] == '/' ? bmake_strdup(file) : NULL;
+ fullname = file[0] == '/' ? bmake_strdup(file) : NULL;
- if (fullname == NULL && !isSystem) {
- /*
- * Include files contained in double-quotes are first searched
- * relative to the including file's location. We don't want to
- * cd there, of course, so we just tack on the old file's
- * leading path components and call Dir_FindFile to see if
- * we can locate the file.
- */
+ if (fullname == NULL && !isSystem) {
+ /*
+ * Include files contained in double-quotes are first searched
+ * relative to the including file's location. We don't want to
+ * cd there, of course, so we just tack on the old file's
+ * leading path components and call Dir_FindFile to see if
+ * we can locate the file.
+ */
- incdir = bmake_strdup(CurFile()->fname);
- slash = strrchr(incdir, '/');
- if (slash != NULL) {
- *slash = '\0';
- /* Now do lexical processing of leading "../" on the filename */
- for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) {
- slash = strrchr(incdir + 1, '/');
- if (slash == NULL || strcmp(slash, "/..") == 0)
- break;
- *slash = '\0';
- }
- newName = str_concat3(incdir, "/", file + i);
- fullname = Dir_FindFile(newName, parseIncPath);
- if (fullname == NULL)
- fullname = Dir_FindFile(newName, dirSearchPath);
- free(newName);
- }
- free(incdir);
+ incdir = bmake_strdup(CurFile()->fname);
+ slash = strrchr(incdir, '/');
+ if (slash != NULL) {
+ *slash = '\0';
+ /*
+ * Now do lexical processing of leading "../" on the
+ * filename.
+ */
+ for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) {
+ slash = strrchr(incdir + 1, '/');
+ if (slash == NULL || strcmp(slash, "/..") == 0)
+ break;
+ *slash = '\0';
+ }
+ newName = str_concat3(incdir, "/", file + i);
+ fullname = Dir_FindFile(newName, parseIncPath);
+ if (fullname == NULL)
+ fullname = Dir_FindFile(newName,
+ &dirSearchPath);
+ free(newName);
+ }
+ free(incdir);
+
+ if (fullname == NULL) {
+ /*
+ * Makefile wasn't found in same directory as included
+ * makefile.
+ *
+ * Search for it first on the -I search path, then on
+ * the .PATH search path, if not found in a -I
+ * directory. If we have a suffix-specific path, we
+ * should use that.
+ */
+ const char *suff;
+ SearchPath *suffPath = NULL;
+
+ if ((suff = strrchr(file, '.')) != NULL) {
+ suffPath = Suff_GetPath(suff);
+ if (suffPath != NULL)
+ fullname = Dir_FindFile(file, suffPath);
+ }
+ if (fullname == NULL) {
+ fullname = Dir_FindFile(file, parseIncPath);
+ if (fullname == NULL)
+ fullname = Dir_FindFile(file,
+ &dirSearchPath);
+ }
+ }
+ }
+ /* Looking for a system file or file still not found */
if (fullname == NULL) {
- /*
- * Makefile wasn't found in same directory as included makefile.
- * Search for it first on the -I search path,
- * then on the .PATH search path, if not found in a -I directory.
- * If we have a suffix specific path we should use that.
- */
- const char *suff;
- SearchPath *suffPath = NULL;
-
- if ((suff = strrchr(file, '.'))) {
- suffPath = Suff_GetPath(suff);
- if (suffPath != NULL)
- fullname = Dir_FindFile(file, suffPath);
- }
- if (fullname == NULL) {
- fullname = Dir_FindFile(file, parseIncPath);
- if (fullname == NULL)
- fullname = Dir_FindFile(file, dirSearchPath);
- }
- }
- }
-
- /* Looking for a system file or file still not found */
- if (fullname == NULL) {
- /*
- * Look for it on the system path
- */
- SearchPath *path = Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath;
- fullname = Dir_FindFile(file, path);
- }
-
- if (fullname == NULL) {
- if (!silent)
- Parse_Error(PARSE_FATAL, "Could not find %s", file);
- return;
- }
-
- /* Actually open the file... */
- fd = open(fullname, O_RDONLY);
- if (fd == -1) {
- if (!silent)
- Parse_Error(PARSE_FATAL, "Cannot open %s", fullname);
- free(fullname);
- return;
- }
-
- /* load it */
- lf = loadfile(fullname, fd);
-
- /* Start reading from this file next */
- Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf);
- CurFile()->lf = lf;
- if (depinc)
- doing_depend = depinc; /* only turn it on */
+ /*
+ * Look for it on the system path
+ */
+ SearchPath *path = Lst_IsEmpty(sysIncPath) ? defSysIncPath
+ : sysIncPath;
+ fullname = Dir_FindFile(file, path);
+ }
+
+ if (fullname == NULL) {
+ if (!silent)
+ Parse_Error(PARSE_FATAL, "Could not find %s", file);
+ return;
+ }
+
+ /* Actually open the file... */
+ fd = open(fullname, O_RDONLY);
+ if (fd == -1) {
+ if (!silent)
+ Parse_Error(PARSE_FATAL, "Cannot open %s", fullname);
+ free(fullname);
+ return;
+ }
+
+ /* load it */
+ lf = loadfile(fullname, fd);
+
+ /* Start reading from this file next */
+ Parse_SetInput(fullname, 0, -1, loadedfile_readMore, lf);
+ CurFile()->lf = lf;
+ if (depinc)
+ doing_depend = depinc; /* only turn it on */
}
static void
-ParseDoInclude(char *line)
+ParseDoInclude(char *line /* XXX: bad name */)
{
- char endc; /* the character which ends the file spec */
- char *cp; /* current position in file spec */
- Boolean silent = *line != 'i';
- char *file = line + (silent ? 8 : 7);
+ char endc; /* the character which ends the file spec */
+ char *cp; /* current position in file spec */
+ Boolean silent = line[0] != 'i';
+ char *file = line + (silent ? 8 : 7);
- /* Skip to delimiter character so we know where to look */
- pp_skip_hspace(&file);
+ /* Skip to delimiter character so we know where to look */
+ pp_skip_hspace(&file);
- if (*file != '"' && *file != '<') {
- Parse_Error(PARSE_FATAL,
+ if (*file != '"' && *file != '<') {
+ Parse_Error(PARSE_FATAL,
".include filename must be delimited by '\"' or '<'");
- return;
- }
-
- /*
- * Set the search path on which to find the include file based on the
- * characters which bracket its name. Angle-brackets imply it's
- * a system Makefile while double-quotes imply it's a user makefile
- */
- if (*file == '<')
- endc = '>';
- else
- endc = '"';
-
- /* Skip to matching delimiter */
- for (cp = ++file; *cp && *cp != endc; cp++)
- continue;
-
- if (*cp != endc) {
- Parse_Error(PARSE_FATAL,
+ return;
+ }
+
+ /*
+ * Set the search path on which to find the include file based on the
+ * characters which bracket its name. Angle-brackets imply it's
+ * a system Makefile while double-quotes imply it's a user makefile
+ */
+ if (*file == '<')
+ endc = '>';
+ else
+ endc = '"';
+
+ /* Skip to matching delimiter */
+ for (cp = ++file; *cp != '\0' && *cp != endc; cp++)
+ continue;
+
+ if (*cp != endc) {
+ Parse_Error(PARSE_FATAL,
"Unclosed .include filename. '%c' expected", endc);
- return;
- }
+ return;
+ }
- *cp = '\0';
+ *cp = '\0';
- /*
- * Substitute for any variables in the filename before trying to
- * find the file.
- */
- (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &file);
- /* TODO: handle errors */
+ /*
+ * Substitute for any variables in the filename before trying to
+ * find the file.
+ */
+ (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &file);
+ /* TODO: handle errors */
- Parse_include_file(file, endc == '>', *line == 'd', silent);
- free(file);
+ Parse_include_file(file, endc == '>', line[0] == 'd', silent);
+ free(file);
}
-/* Split filename into dirname + basename, then assign these to the
- * given variables. */
+/*
+ * Split filename into dirname + basename, then assign these to the
+ * given variables.
+ */
static void
SetFilenameVars(const char *filename, const char *dirvar, const char *filevar)
{
- const char *slash, *dirname, *basename;
- void *freeIt;
+ const char *slash, *dirname, *basename;
+ void *freeIt;
- slash = strrchr(filename, '/');
- if (slash == NULL) {
- dirname = curdir;
- basename = filename;
- freeIt = NULL;
- } else {
- dirname = freeIt = bmake_strsedup(filename, slash);
- basename = slash + 1;
- }
+ slash = strrchr(filename, '/');
+ if (slash == NULL) {
+ dirname = curdir;
+ basename = filename;
+ freeIt = NULL;
+ } else {
+ dirname = freeIt = bmake_strsedup(filename, slash);
+ basename = slash + 1;
+ }
- Var_Set(dirvar, dirname, VAR_GLOBAL);
- Var_Set(filevar, basename, VAR_GLOBAL);
+ Var_Set(dirvar, dirname, VAR_GLOBAL);
+ Var_Set(filevar, basename, VAR_GLOBAL);
- DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n",
- __func__, dirvar, dirname, filevar, basename);
- free(freeIt);
+ DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n",
+ __func__, dirvar, dirname, filevar, basename);
+ free(freeIt);
}
-/* Return the immediately including file.
+/*
+ * Return the immediately including file.
*
* This is made complicated since the .for loop is implemented as a special
- * kind of .include; see For_Run. */
+ * kind of .include; see For_Run.
+ */
static const char *
GetActuallyIncludingFile(void)
{
- size_t i;
- const IFile *incs = GetInclude(0);
+ size_t i;
+ const IFile *incs = GetInclude(0);
- for (i = includes.len; i >= 2; i--)
- if (!incs[i - 1].fromForLoop)
- return incs[i - 2].fname;
- return NULL;
+ for (i = includes.len; i >= 2; i--)
+ if (!incs[i - 1].fromForLoop)
+ return incs[i - 2].fname;
+ return NULL;
}
/* Set .PARSEDIR, .PARSEFILE, .INCLUDEDFROMDIR and .INCLUDEDFROMFILE. */
static void
ParseSetParseFile(const char *filename)
{
- const char *including;
+ const char *including;
- SetFilenameVars(filename, ".PARSEDIR", ".PARSEFILE");
+ SetFilenameVars(filename, ".PARSEDIR", ".PARSEFILE");
- including = GetActuallyIncludingFile();
- if (including != NULL) {
- SetFilenameVars(including,
- ".INCLUDEDFROMDIR", ".INCLUDEDFROMFILE");
- } else {
- Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL);
- Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL);
- }
+ including = GetActuallyIncludingFile();
+ if (including != NULL) {
+ SetFilenameVars(including,
+ ".INCLUDEDFROMDIR", ".INCLUDEDFROMFILE");
+ } else {
+ Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL);
+ Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL);
+ }
}
static Boolean
StrContainsWord(const char *str, const char *word)
{
- size_t strLen = strlen(str);
- size_t wordLen = strlen(word);
- const char *p, *end;
+ size_t strLen = strlen(str);
+ size_t wordLen = strlen(word);
+ const char *p, *end;
- if (strLen < wordLen)
- return FALSE; /* str is too short to contain word */
+ if (strLen < wordLen)
+ return FALSE; /* str is too short to contain word */
- end = str + strLen - wordLen;
- for (p = str; p != NULL; p = strchr(p, ' ')) {
- if (*p == ' ')
- p++;
- if (p > end)
- return FALSE; /* cannot contain word */
+ end = str + strLen - wordLen;
+ for (p = str; p != NULL; p = strchr(p, ' ')) {
+ if (*p == ' ')
+ p++;
+ if (p > end)
+ return FALSE; /* cannot contain word */
- if (memcmp(p, word, wordLen) == 0 &&
- (p[wordLen] == '\0' || p[wordLen] == ' '))
- return TRUE;
- }
- return FALSE;
+ if (memcmp(p, word, wordLen) == 0 &&
+ (p[wordLen] == '\0' || p[wordLen] == ' '))
+ return TRUE;
+ }
+ return FALSE;
}
-/* XXX: Searching through a set of words with this linear search is
- * inefficient for variables that contain thousands of words. */
-/* XXX: The paths in this list don't seem to be normalized in any way. */
+/*
+ * XXX: Searching through a set of words with this linear search is
+ * inefficient for variables that contain thousands of words.
+ *
+ * XXX: The paths in this list don't seem to be normalized in any way.
+ */
static Boolean
VarContainsWord(const char *varname, const char *word)
{
- void *val_freeIt;
- const char *val = Var_Value(varname, VAR_GLOBAL, &val_freeIt);
- Boolean found = val != NULL && StrContainsWord(val, word);
- bmake_free(val_freeIt);
- return found;
+ FStr val = Var_Value(varname, VAR_GLOBAL);
+ Boolean found = val.str != NULL && StrContainsWord(val.str, word);
+ FStr_Done(&val);
+ return found;
}
-/* Track the makefiles we read - so makefiles can set dependencies on them.
+/*
+ * Track the makefiles we read - so makefiles can set dependencies on them.
* Avoid adding anything more than once.
*
* Time complexity: O(n) per call, in total O(n^2), where n is the number
- * of makefiles that have been loaded. */
+ * of makefiles that have been loaded.
+ */
static void
ParseTrackInput(const char *name)
{
- if (!VarContainsWord(MAKE_MAKEFILES, name))
- Var_Append(MAKE_MAKEFILES, name, VAR_GLOBAL);
+ if (!VarContainsWord(MAKE_MAKEFILES, name))
+ Var_Append(MAKE_MAKEFILES, name, VAR_GLOBAL);
}
-/* Start parsing from the given source.
+/*
+ * Start parsing from the given source.
*
- * The given file is added to the includes stack. */
+ * The given file is added to the includes stack.
+ */
void
-Parse_SetInput(const char *name, int line, int fd,
- char *(*nextbuf)(void *, size_t *), void *arg)
-{
- IFile *curFile;
- char *buf;
- size_t len;
- Boolean fromForLoop = name == NULL;
-
- if (fromForLoop)
- name = CurFile()->fname;
- else
- ParseTrackInput(name);
-
- if (DEBUG(PARSE))
- debug_printf("%s: file %s, line %d, fd %d, nextbuf %s, arg %p\n",
- __func__, name, line, fd,
- nextbuf == loadedfile_nextbuf ? "loadedfile" : "other",
- arg);
-
- if (fd == -1 && nextbuf == NULL)
- /* sanity */
- return;
-
- curFile = Vector_Push(&includes);
- curFile->fname = bmake_strdup(name);
- curFile->fromForLoop = fromForLoop;
- curFile->lineno = line;
- curFile->first_lineno = line;
- curFile->nextbuf = nextbuf;
- curFile->nextbuf_arg = arg;
- curFile->lf = NULL;
- curFile->depending = doing_depend; /* restore this on EOF */
-
- assert(nextbuf != NULL);
-
- /* Get first block of input data */
- buf = curFile->nextbuf(curFile->nextbuf_arg, &len);
- if (buf == NULL) {
- /* Was all a waste of time ... */
- if (curFile->fname)
- free(curFile->fname);
- free(curFile);
- return;
- }
- curFile->buf_freeIt = buf;
- curFile->buf_ptr = buf;
- curFile->buf_end = buf + len;
-
- curFile->cond_depth = Cond_save_depth();
- ParseSetParseFile(name);
+Parse_SetInput(const char *name, int lineno, int fd,
+ ReadMoreProc readMore, void *readMoreArg)
+{
+ IFile *curFile;
+ char *buf;
+ size_t len;
+ Boolean fromForLoop = name == NULL;
+
+ if (fromForLoop)
+ name = CurFile()->fname;
+ else
+ ParseTrackInput(name);
+
+ DEBUG3(PARSE, "Parse_SetInput: %s %s, line %d\n",
+ readMore == loadedfile_readMore ? "file" : ".for loop in",
+ name, lineno);
+
+ if (fd == -1 && readMore == NULL)
+ /* sanity */
+ return;
+
+ curFile = Vector_Push(&includes);
+ curFile->fname = bmake_strdup(name);
+ curFile->fromForLoop = fromForLoop;
+ curFile->lineno = lineno;
+ curFile->first_lineno = lineno;
+ curFile->readMore = readMore;
+ curFile->readMoreArg = readMoreArg;
+ curFile->lf = NULL;
+ curFile->depending = doing_depend; /* restore this on EOF */
+
+ assert(readMore != NULL);
+
+ /* Get first block of input data */
+ buf = curFile->readMore(curFile->readMoreArg, &len);
+ if (buf == NULL) {
+ /* Was all a waste of time ... */
+ if (curFile->fname != NULL)
+ free(curFile->fname);
+ free(curFile);
+ return;
+ }
+ curFile->buf_freeIt = buf;
+ curFile->buf_ptr = buf;
+ curFile->buf_end = buf + len;
+
+ curFile->cond_depth = Cond_save_depth();
+ ParseSetParseFile(name);
}
/* Check if the directive is an include directive. */
@@ -2468,42 +2472,42 @@ IsSysVInclude(const char *line)
static void
ParseTraditionalInclude(char *line)
{
- char *cp; /* current position in file spec */
- Boolean done = FALSE;
- Boolean silent = line[0] != 'i';
- char *file = line + (silent ? 8 : 7);
- char *all_files;
+ char *cp; /* current position in file spec */
+ Boolean done = FALSE;
+ Boolean silent = line[0] != 'i';
+ char *file = line + (silent ? 8 : 7);
+ char *all_files;
- DEBUG2(PARSE, "%s: %s\n", __func__, file);
+ DEBUG2(PARSE, "%s: %s\n", __func__, file);
- pp_skip_whitespace(&file);
+ pp_skip_whitespace(&file);
- /*
- * Substitute for any variables in the file name before trying to
- * find the thing.
- */
- (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &all_files);
- /* TODO: handle errors */
+ /*
+ * Substitute for any variables in the file name before trying to
+ * find the thing.
+ */
+ (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &all_files);
+ /* TODO: handle errors */
- if (*file == '\0') {
- Parse_Error(PARSE_FATAL, "Filename missing from \"include\"");
- goto out;
- }
+ if (*file == '\0') {
+ Parse_Error(PARSE_FATAL, "Filename missing from \"include\"");
+ goto out;
+ }
- for (file = all_files; !done; file = cp + 1) {
- /* Skip to end of line or next whitespace */
- for (cp = file; *cp && !ch_isspace(*cp); cp++)
- continue;
+ for (file = all_files; !done; file = cp + 1) {
+ /* Skip to end of line or next whitespace */
+ for (cp = file; *cp != '\0' && !ch_isspace(*cp); cp++)
+ continue;
- if (*cp != '\0')
- *cp = '\0';
- else
- done = TRUE;
+ if (*cp != '\0')
+ *cp = '\0';
+ else
+ done = TRUE;
- Parse_include_file(file, FALSE, FALSE, silent);
- }
+ Parse_include_file(file, FALSE, FALSE, silent);
+ }
out:
- free(all_files);
+ free(all_files);
}
#endif
@@ -2512,35 +2516,36 @@ out:
static void
ParseGmakeExport(char *line)
{
- char *variable = line + 6;
- char *value;
+ char *variable = line + 6;
+ char *value;
- DEBUG2(PARSE, "%s: %s\n", __func__, variable);
+ DEBUG2(PARSE, "%s: %s\n", __func__, variable);
- pp_skip_whitespace(&variable);
+ pp_skip_whitespace(&variable);
- for (value = variable; *value && *value != '='; value++)
- continue;
+ for (value = variable; *value != '\0' && *value != '='; value++)
+ continue;
- if (*value != '=') {
- Parse_Error(PARSE_FATAL,
+ if (*value != '=') {
+ Parse_Error(PARSE_FATAL,
"Variable/Value missing from \"export\"");
- return;
- }
- *value++ = '\0'; /* terminate variable */
+ return;
+ }
+ *value++ = '\0'; /* terminate variable */
- /*
- * Expand the value before putting it in the environment.
- */
- (void)Var_Subst(value, VAR_CMDLINE, VARE_WANTRES, &value);
- /* TODO: handle errors */
+ /*
+ * Expand the value before putting it in the environment.
+ */
+ (void)Var_Subst(value, VAR_CMDLINE, VARE_WANTRES, &value);
+ /* TODO: handle errors */
- setenv(variable, value, 1);
- free(value);
+ setenv(variable, value, 1);
+ free(value);
}
#endif
-/* Called when EOF is reached in the current file. If we were reading an
+/*
+ * Called when EOF is reached in the current file. If we were reading an
* include file or a .for loop, the includes stack is popped and things set
* up to go back to reading the previous file at the previous location.
*
@@ -2551,528 +2556,644 @@ ParseGmakeExport(char *line)
static Boolean
ParseEOF(void)
{
- char *ptr;
- size_t len;
- IFile *curFile = CurFile();
+ char *ptr;
+ size_t len;
+ IFile *curFile = CurFile();
- assert(curFile->nextbuf != NULL);
+ assert(curFile->readMore != NULL);
- doing_depend = curFile->depending; /* restore this */
- /* get next input buffer, if any */
- ptr = curFile->nextbuf(curFile->nextbuf_arg, &len);
- curFile->buf_ptr = ptr;
- curFile->buf_freeIt = ptr;
- curFile->buf_end = ptr + len; /* XXX: undefined behavior if ptr == NULL */
- curFile->lineno = curFile->first_lineno;
- if (ptr != NULL)
- return TRUE; /* Iterate again */
+ 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);
+ /* Ensure the makefile (or loop) didn't have mismatched conditionals */
+ Cond_restore_depth(curFile->cond_depth);
- if (curFile->lf != NULL) {
- loadedfile_destroy(curFile->lf);
- curFile->lf = NULL;
- }
-
- /* Dispose of curFile info */
- /* Leak curFile->fname because all the gnodes have pointers to it */
- free(curFile->buf_freeIt);
- Vector_Pop(&includes);
-
- if (includes.len == 0) {
- /* We've run out of input */
- Var_Delete(".PARSEDIR", VAR_GLOBAL);
- Var_Delete(".PARSEFILE", VAR_GLOBAL);
- Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL);
- Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL);
- return FALSE;
- }
+ if (curFile->lf != NULL) {
+ loadedfile_destroy(curFile->lf);
+ curFile->lf = NULL;
+ }
+
+ /* Dispose of curFile info */
+ /* Leak curFile->fname because all the gnodes have pointers to it. */
+ free(curFile->buf_freeIt);
+ Vector_Pop(&includes);
+
+ if (includes.len == 0) {
+ /* We've run out of input */
+ Var_Delete(".PARSEDIR", VAR_GLOBAL);
+ Var_Delete(".PARSEFILE", VAR_GLOBAL);
+ Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL);
+ Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL);
+ return FALSE;
+ }
- curFile = CurFile();
- DEBUG2(PARSE, "ParseEOF: returning to file %s, line %d\n",
- curFile->fname, curFile->lineno);
+ curFile = CurFile();
+ DEBUG2(PARSE, "ParseEOF: returning to file %s, line %d\n",
+ curFile->fname, curFile->lineno);
- ParseSetParseFile(curFile->fname);
- return TRUE;
+ ParseSetParseFile(curFile->fname);
+ return TRUE;
}
-typedef enum GetLineMode {
- PARSE_NORMAL,
- PARSE_RAW,
- PARSE_SKIP
-} GetLineMode;
+typedef enum ParseRawLineResult {
+ PRLR_LINE,
+ PRLR_EOF,
+ PRLR_ERROR
+} ParseRawLineResult;
-static char *
-ParseGetLine(GetLineMode mode)
+/*
+ * Parse until the end of a line, taking into account lines that end with
+ * backslash-newline.
+ */
+static ParseRawLineResult
+ParseRawLine(IFile *curFile, char **out_line, char **out_line_end,
+ char **out_firstBackslash, char **out_firstComment)
{
- IFile *cf = CurFile();
- char *ptr;
- char ch;
- char *line;
- char *line_end;
- char *escaped;
- char *comment;
- char *tp;
-
- /* Loop through blank lines and comment lines */
- for (;;) {
- cf->lineno++;
- line = cf->buf_ptr;
- ptr = line;
- line_end = line;
- escaped = NULL;
- comment = NULL;
+ char *line = curFile->buf_ptr;
+ char *p = line;
+ char *line_end = line;
+ char *firstBackslash = NULL;
+ char *firstComment = NULL;
+ ParseRawLineResult res = PRLR_LINE;
+
+ curFile->lineno++;
+
for (;;) {
- /* XXX: can buf_end ever be null? */
- if (cf->buf_end != NULL && ptr == cf->buf_end) {
- /* end of buffer */
- ch = '\0';
- break;
- }
- ch = *ptr;
- if (ch == '\0' || (ch == '\\' && ptr[1] == '\0')) {
- /* XXX: can buf_end ever be null? */
- if (cf->buf_end == NULL)
- /* End of string (aka for loop) data */
- break;
- /* see if there is more we can parse */
- while (ptr++ < cf->buf_end) {
- if ((ch = *ptr) == '\n') {
- if (ptr > line && ptr[-1] == '\\')
- continue;
- Parse_Error(PARSE_WARNING,
- "Zero byte read from file, "
- "skipping rest of line.");
+ char ch;
+
+ if (p == curFile->buf_end) {
+ res = PRLR_EOF;
break;
- }
}
- /* XXX: Can cf->nextbuf ever be NULL? */
- if (cf->nextbuf != NULL) {
- /*
- * End of this buffer; return EOF and outer logic
- * will get the next one. (eww)
- */
- break;
+
+ ch = *p;
+ if (ch == '\0' ||
+ (ch == '\\' && p + 1 < curFile->buf_end && p[1] == '\0')) {
+ Parse_Error(PARSE_FATAL, "Zero byte read from file");
+ return PRLR_ERROR;
}
- Parse_Error(PARSE_FATAL, "Zero byte read from file");
- return NULL;
- }
-
- if (ch == '\\') {
- /* Don't treat next character as special, remember first one */
- if (escaped == NULL)
- escaped = ptr;
- if (ptr[1] == '\n')
- cf->lineno++;
- ptr += 2;
- line_end = ptr;
- continue;
- }
- if (ch == '#' && comment == NULL) {
- /* Remember first '#' for comment stripping */
- /* Unless previous char was '[', as in modifier :[#] */
- if (!(ptr > line && ptr[-1] == '['))
- comment = line_end;
- }
- ptr++;
- if (ch == '\n')
- break;
- if (!ch_isspace(ch))
- /* We are not interested in trailing whitespace */
- line_end = ptr;
- }
- /* Save next 'to be processed' location */
- cf->buf_ptr = ptr;
+ /* Treat next character after '\' as literal. */
+ if (ch == '\\') {
+ if (firstBackslash == NULL)
+ firstBackslash = p;
+ if (p[1] == '\n') {
+ curFile->lineno++;
+ if (p + 2 == curFile->buf_end) {
+ line_end = p;
+ *line_end = '\n';
+ p += 2;
+ continue;
+ }
+ }
+ p += 2;
+ line_end = p;
+ assert(p <= curFile->buf_end);
+ continue;
+ }
- /* Check we have a non-comment, non-blank line */
- if (line_end == line || comment == line) {
- if (ch == '\0')
- /* At end of file */
- return NULL;
- /* Parse another line */
- continue;
- }
+ /*
+ * Remember the first '#' for comment stripping, unless
+ * the previous char was '[', as in the modifier ':[#]'.
+ */
+ if (ch == '#' && firstComment == NULL &&
+ !(p > line && p[-1] == '['))
+ firstComment = line_end;
- /* We now have a line of data */
- *line_end = '\0';
+ p++;
+ if (ch == '\n')
+ break;
- if (mode == PARSE_RAW) {
- /* Leave '\' (etc) in line buffer (eg 'for' lines) */
- return line;
+ /* We are not interested in trailing whitespace. */
+ if (!ch_isspace(ch))
+ line_end = p;
}
- if (mode == PARSE_SKIP) {
- /* Completely ignore non-directives */
- if (line[0] != '.')
- continue;
- /* We could do more of the .else/.elif/.endif checks here */
+ *out_line = line;
+ curFile->buf_ptr = p;
+ *out_line_end = line_end;
+ *out_firstBackslash = firstBackslash;
+ *out_firstComment = firstComment;
+ return res;
+}
+
+/*
+ * Beginning at start, unescape '\#' to '#' and replace backslash-newline
+ * with a single space.
+ */
+static void
+UnescapeBackslash(char *line, char *start)
+{
+ char *src = start;
+ char *dst = start;
+ char *spaceStart = line;
+
+ for (;;) {
+ char ch = *src++;
+ if (ch != '\\') {
+ if (ch == '\0')
+ break;
+ *dst++ = ch;
+ continue;
+ }
+
+ ch = *src++;
+ if (ch == '\0') {
+ /* Delete '\\' at end of buffer */
+ dst--;
+ break;
+ }
+
+ /* Delete '\\' from before '#' on non-command lines */
+ if (ch == '#' && line[0] != '\t') {
+ *dst++ = ch;
+ continue;
+ }
+
+ if (ch != '\n') {
+ /* Leave '\\' in buffer for later */
+ *dst++ = '\\';
+ /*
+ * Make sure we don't delete an escaped ' ' from the
+ * line end.
+ */
+ spaceStart = dst + 1;
+ *dst++ = ch;
+ continue;
+ }
+
+ /*
+ * Escaped '\n' -- replace following whitespace with a single
+ * ' '.
+ */
+ pp_skip_hspace(&src);
+ *dst++ = ' ';
}
- break;
- }
- /* Brutally ignore anything after a non-escaped '#' in non-commands */
- if (comment != NULL && line[0] != '\t') {
- line_end = comment;
- *line_end = '\0';
- }
+ /* Delete any trailing spaces - eg from empty continuations */
+ while (dst > spaceStart && ch_isspace(dst[-1]))
+ dst--;
+ *dst = '\0';
+}
- /* If we didn't see a '\\' then the in-situ data is fine */
- if (escaped == NULL)
- return line;
+typedef enum GetLineMode {
+ /*
+ * Return the next line that is neither empty nor a comment.
+ * Backslash line continuations are folded into a single space.
+ * A trailing comment, if any, is discarded.
+ */
+ GLM_NONEMPTY,
- /* Remove escapes from '\n' and '#' */
- tp = ptr = escaped;
- escaped = line;
- for (; ; *tp++ = ch) {
- ch = *ptr++;
- if (ch != '\\') {
- if (ch == '\0')
+ /*
+ * Return the next line, even if it is empty or a comment.
+ * Preserve backslash-newline to keep the line numbers correct.
+ *
+ * Used in .for loops to collect the body of the loop while waiting
+ * for the corresponding .endfor.
+ */
+ GLM_FOR_BODY,
+
+ /*
+ * Return the next line that starts with a dot.
+ * Backslash line continuations are folded into a single space.
+ * A trailing comment, if any, is discarded.
+ *
+ * Used in .if directives to skip over irrelevant branches while
+ * waiting for the corresponding .endif.
+ */
+ GLM_DOT
+} GetLineMode;
+
+/* Return the next "interesting" logical line from the current file. */
+static char *
+ParseGetLine(GetLineMode mode)
+{
+ IFile *curFile = CurFile();
+ char *line;
+ char *line_end;
+ char *firstBackslash;
+ char *firstComment;
+
+ /* Loop through blank lines and comment lines */
+ for (;;) {
+ ParseRawLineResult res = ParseRawLine(curFile,
+ &line, &line_end, &firstBackslash, &firstComment);
+ if (res == PRLR_ERROR)
+ return NULL;
+
+ if (line_end == line || firstComment == line) {
+ if (res == PRLR_EOF)
+ return NULL;
+ if (mode != GLM_FOR_BODY)
+ continue;
+ }
+
+ /* We now have a line of data */
+ assert(ch_isspace(*line_end));
+ *line_end = '\0';
+
+ if (mode == GLM_FOR_BODY)
+ return line; /* Don't join the physical lines. */
+
+ if (mode == GLM_DOT && line[0] != '.')
+ continue;
break;
- continue;
}
- ch = *ptr++;
- if (ch == '\0') {
- /* Delete '\\' at end of buffer */
- tp--;
- break;
- }
+ /* Brutally ignore anything after a non-escaped '#' in non-commands. */
+ if (firstComment != NULL && line[0] != '\t')
+ *firstComment = '\0';
- if (ch == '#' && line[0] != '\t')
- /* Delete '\\' from before '#' on non-command lines */
- continue;
+ /* If we didn't see a '\\' then the in-situ data is fine. */
+ if (firstBackslash == NULL)
+ return line;
- if (ch != '\n') {
- /* Leave '\\' in buffer for later */
- *tp++ = '\\';
- /* Make sure we don't delete an escaped ' ' from the line end */
- escaped = tp + 1;
- continue;
+ /* Remove escapes from '\n' and '#' */
+ UnescapeBackslash(line, firstBackslash);
+
+ return line;
+}
+
+static Boolean
+ParseSkippedBranches(void)
+{
+ char *line;
+
+ while ((line = ParseGetLine(GLM_DOT)) != NULL) {
+ if (Cond_EvalLine(line) == COND_PARSE)
+ break;
+ /*
+ * TODO: Check for typos in .elif directives
+ * such as .elsif or .elseif.
+ *
+ * This check will probably duplicate some of
+ * the code in ParseLine. Most of the code
+ * there cannot apply, only ParseVarassign and
+ * ParseDependency can, and to prevent code
+ * duplication, these would need to be called
+ * with a flag called onlyCheckSyntax.
+ *
+ * See directive-elif.mk for details.
+ */
}
- /* Escaped '\n' -- replace following whitespace with a single ' '. */
- pp_skip_hspace(&ptr);
- ch = ' ';
- }
+ return line != NULL;
+}
+
+static Boolean
+ParseForLoop(const char *line)
+{
+ int rval;
+ int firstLineno;
+
+ rval = For_Eval(line);
+ if (rval == 0)
+ return FALSE; /* Not a .for line */
+ if (rval < 0)
+ return TRUE; /* Syntax error - error printed, ignore line */
+
+ firstLineno = CurFile()->lineno;
+
+ /* Accumulate loop lines until matching .endfor */
+ do {
+ line = ParseGetLine(GLM_FOR_BODY);
+ if (line == NULL) {
+ Parse_Error(PARSE_FATAL,
+ "Unexpected end of file in for loop.");
+ break;
+ }
+ } while (For_Accum(line));
- /* Delete any trailing spaces - eg from empty continuations */
- while (tp > escaped && ch_isspace(tp[-1]))
- tp--;
+ For_Run(firstLineno); /* Stash each iteration as a new 'input file' */
- *tp = '\0';
- return line;
+ return TRUE; /* Read next line from for-loop buffer */
}
-/* Read an entire line from the input file. Called only by Parse_File.
+/*
+ * Read an entire line from the input file.
+ *
+ * Empty lines, .if and .for are completely handled by this function,
+ * leaving only variable assignments, other directives, dependency lines
+ * and shell commands to the caller.
*
* Results:
- * A line without its newline and without any trailing whitespace.
+ * A line without its newline and without any trailing whitespace,
+ * or NULL.
*/
static char *
ParseReadLine(void)
{
- char *line; /* Result */
- int lineno; /* Saved line # */
- int rval;
+ char *line;
- for (;;) {
- line = ParseGetLine(PARSE_NORMAL);
- if (line == NULL)
- return NULL;
+ for (;;) {
+ line = ParseGetLine(GLM_NONEMPTY);
+ if (line == NULL)
+ return NULL;
- if (line[0] != '.')
- return line;
+ if (line[0] != '.')
+ return line;
- /*
- * The line might be a conditional. Ask the conditional module
- * about it and act accordingly
- */
- switch (Cond_EvalLine(line)) {
- case COND_SKIP:
- /* Skip to next conditional that evaluates to COND_PARSE. */
- do {
- line = ParseGetLine(PARSE_SKIP);
- } while (line && Cond_EvalLine(line) != COND_PARSE);
- if (line == NULL)
- break;
- continue;
- case COND_PARSE:
- continue;
- case COND_INVALID: /* Not a conditional line */
- /* Check for .for loops */
- rval = For_Eval(line);
- if (rval == 0)
- /* Not a .for line */
- break;
- if (rval < 0)
- /* Syntax error - error printed, ignore line */
- continue;
- /* Start of a .for loop */
- lineno = CurFile()->lineno;
- /* Accumulate loop lines until matching .endfor */
- do {
- line = ParseGetLine(PARSE_RAW);
- if (line == NULL) {
- Parse_Error(PARSE_FATAL,
- "Unexpected end of file in for loop.");
- break;
+ /*
+ * The line might be a conditional. Ask the conditional module
+ * about it and act accordingly
+ */
+ switch (Cond_EvalLine(line)) {
+ case COND_SKIP:
+ if (!ParseSkippedBranches())
+ return NULL;
+ continue;
+ case COND_PARSE:
+ continue;
+ case COND_INVALID: /* Not a conditional line */
+ if (ParseForLoop(line))
+ continue;
+ break;
}
- } while (For_Accum(line));
- /* Stash each iteration as a new 'input file' */
- For_Run(lineno);
- /* Read next line from for-loop buffer */
- continue;
+ return line;
}
- return line;
- }
}
static void
FinishDependencyGroup(void)
{
- GNodeListNode *ln;
+ GNodeListNode *ln;
- if (targets == NULL)
- return;
+ if (targets == NULL)
+ return;
- for (ln = targets->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
+ for (ln = targets->first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
- Suff_EndTransform(gn);
+ Suff_EndTransform(gn);
- /* Mark the target as already having commands if it does, to
- * keep from having shell commands on multiple dependency lines. */
- if (!Lst_IsEmpty(gn->commands))
- gn->type |= OP_HAS_COMMANDS;
- }
+ /*
+ * Mark the target as already having commands if it does, to
+ * keep from having shell commands on multiple dependency
+ * lines.
+ */
+ if (!Lst_IsEmpty(&gn->commands))
+ gn->type |= OP_HAS_COMMANDS;
+ }
- Lst_Free(targets);
- targets = NULL;
+ Lst_Free(targets);
+ targets = NULL;
}
/* Add the command to each target from the current dependency spec. */
static void
ParseLine_ShellCommand(const char *p)
{
- cpp_skip_whitespace(&p);
- if (*p == '\0')
- return; /* skip empty commands */
+ cpp_skip_whitespace(&p);
+ if (*p == '\0')
+ return; /* skip empty commands */
- if (targets == NULL) {
- Parse_Error(PARSE_FATAL, "Unassociated shell command \"%s\"", p);
- return;
- }
+ if (targets == NULL) {
+ Parse_Error(PARSE_FATAL,
+ "Unassociated shell command \"%s\"", p);
+ return;
+ }
- {
- char *cmd = bmake_strdup(p);
- GNodeListNode *ln;
+ {
+ char *cmd = bmake_strdup(p);
+ GNodeListNode *ln;
- for (ln = targets->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
- ParseAddCmd(gn, cmd);
- }
+ for (ln = targets->first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
+ ParseAddCmd(gn, cmd);
+ }
#ifdef CLEANUP
- Lst_Append(targCmds, cmd);
+ Lst_Append(&targCmds, cmd);
#endif
- }
+ }
}
+MAKE_INLINE Boolean
+IsDirective(const char *dir, size_t dirlen, const char *name)
+{
+ return dirlen == strlen(name) && memcmp(dir, name, dirlen) == 0;
+}
+
+/*
+ * See if the line starts with one of the known directives, and if so, handle
+ * the directive.
+ */
static Boolean
ParseDirective(char *line)
{
- char *cp;
+ char *cp = line + 1;
+ const char *dir, *arg;
+ size_t dirlen;
- if (*line == '.') {
- /*
- * Lines that begin with '.' can be pretty much anything:
- * - directives like '.include' or '.if',
- * - suffix rules like '.c.o:',
- * - dependencies for filenames that start with '.',
- * - variable assignments like '.tmp=value'.
- */
- cp = line + 1;
pp_skip_whitespace(&cp);
if (IsInclude(cp, FALSE)) {
- ParseDoInclude(cp);
- return TRUE;
- }
- if (strncmp(cp, "undef", 5) == 0) {
- const char *varname;
- cp += 5;
- pp_skip_whitespace(&cp);
- varname = cp;
- for (; !ch_isspace(*cp) && *cp != '\0'; cp++)
- continue;
- *cp = '\0';
- Var_Delete(varname, VAR_GLOBAL);
- /* TODO: undefine all variables, not only the first */
- /* TODO: use Str_Words, like everywhere else */
- return TRUE;
- } else if (strncmp(cp, "export", 6) == 0) {
- cp += 6;
- pp_skip_whitespace(&cp);
- Var_Export(cp, TRUE);
- return TRUE;
- } else if (strncmp(cp, "unexport", 8) == 0) {
- Var_UnExport(cp);
- return TRUE;
- } else if (strncmp(cp, "info", 4) == 0 ||
- strncmp(cp, "error", 5) == 0 ||
- strncmp(cp, "warning", 7) == 0) {
- if (ParseMessage(cp))
+ ParseDoInclude(cp);
return TRUE;
}
- }
- return FALSE;
+
+ dir = cp;
+ while (ch_isalpha(*cp) || *cp == '-')
+ cp++;
+ dirlen = (size_t)(cp - dir);
+
+ if (*cp != '\0' && !ch_isspace(*cp))
+ return FALSE;
+
+ pp_skip_whitespace(&cp);
+ arg = cp;
+
+ if (IsDirective(dir, dirlen, "undef")) {
+ Var_Undef(cp);
+ return TRUE;
+ } else if (IsDirective(dir, dirlen, "export")) {
+ Var_Export(VEM_PLAIN, arg);
+ return TRUE;
+ } else if (IsDirective(dir, dirlen, "export-env")) {
+ Var_Export(VEM_ENV, arg);
+ return TRUE;
+ } else if (IsDirective(dir, dirlen, "export-literal")) {
+ Var_Export(VEM_LITERAL, arg);
+ return TRUE;
+ } else if (IsDirective(dir, dirlen, "unexport")) {
+ Var_UnExport(FALSE, arg);
+ return TRUE;
+ } else if (IsDirective(dir, dirlen, "unexport-env")) {
+ Var_UnExport(TRUE, arg);
+ return TRUE;
+ } else if (IsDirective(dir, dirlen, "info")) {
+ ParseMessage(PARSE_INFO, "info", arg);
+ return TRUE;
+ } else if (IsDirective(dir, dirlen, "warning")) {
+ ParseMessage(PARSE_WARNING, "warning", arg);
+ return TRUE;
+ } else if (IsDirective(dir, dirlen, "error")) {
+ ParseMessage(PARSE_FATAL, "error", arg);
+ return TRUE;
+ }
+ return FALSE;
}
static Boolean
ParseVarassign(const char *line)
{
- VarAssign var;
+ VarAssign var;
- if (!Parse_IsVar(line, &var))
- return FALSE;
+ if (!Parse_IsVar(line, &var))
+ return FALSE;
- FinishDependencyGroup();
- Parse_DoVar(&var, VAR_GLOBAL);
- return TRUE;
+ FinishDependencyGroup();
+ Parse_DoVar(&var, VAR_GLOBAL);
+ return TRUE;
}
static char *
FindSemicolon(char *p)
{
- int level = 0;
+ int level = 0;
- for (; *p != '\0'; p++) {
- if (*p == '\\' && p[1] != '\0') {
- p++;
- continue;
- }
+ for (; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0') {
+ p++;
+ continue;
+ }
- if (*p == '$' && (p[1] == '(' || p[1] == '{'))
- level++;
- else if (level > 0 && (*p == ')' || *p == '}'))
- level--;
- else if (level == 0 && *p == ';')
- break;
- }
- return p;
+ if (*p == '$' && (p[1] == '(' || p[1] == '{'))
+ level++;
+ else if (level > 0 && (*p == ')' || *p == '}'))
+ level--;
+ else if (level == 0 && *p == ';')
+ break;
+ }
+ return p;
}
-/* dependency -> target... op [source...]
- * op -> ':' | '::' | '!' */
+/*
+ * dependency -> target... op [source...]
+ * op -> ':' | '::' | '!'
+ */
static void
ParseDependency(char *line)
{
- VarEvalFlags eflags;
- char *expanded_line;
- const char *shellcmd = NULL;
-
- /*
- * For some reason - probably to make the parser impossible -
- * a ';' can be used to separate commands from dependencies.
- * Attempt to avoid ';' inside substitution patterns.
- */
- {
- char *semicolon = FindSemicolon(line);
- if (*semicolon != '\0') {
- /* Terminate the dependency list at the ';' */
- *semicolon = '\0';
- shellcmd = semicolon + 1;
- }
- }
-
- /*
- * We now know it's a dependency line so it needs to have all
- * variables expanded before being parsed.
- *
- * XXX: Ideally the dependency line would first be split into
- * its left-hand side, dependency operator and right-hand side,
- * and then each side would be expanded on its own. This would
- * allow for the left-hand side to allow only defined variables
- * and to allow variables on the right-hand side to be undefined
- * as well.
- *
- * Parsing the line first would also prevent that targets
- * generated from variable expressions are interpreted as the
- * dependency operator, such as in "target${:U\:} middle: source",
- * in which the middle is interpreted as a source, not a target.
- */
-
- /* In lint mode, allow undefined variables to appear in
- * dependency lines.
- *
- * Ideally, only the right-hand side would allow undefined
- * variables since it is common to have optional dependencies.
- * Having undefined variables on the left-hand side is more
- * unusual though. Since both sides are expanded in a single
- * pass, there is not much choice what to do here.
- *
- * In normal mode, it does not matter whether undefined
- * variables are allowed or not since as of 2020-09-14,
- * Var_Parse does not print any parse errors in such a case.
- * It simply returns the special empty string var_Error,
- * which cannot be detected in the result of Var_Subst. */
- eflags = opts.lint ? VARE_WANTRES : VARE_WANTRES | VARE_UNDEFERR;
- (void)Var_Subst(line, VAR_CMDLINE, eflags, &expanded_line);
- /* TODO: handle errors */
-
- /* Need a fresh list for the target nodes */
- if (targets != NULL)
- Lst_Free(targets);
- targets = Lst_New();
+ VarEvalFlags eflags;
+ char *expanded_line;
+ const char *shellcmd = NULL;
- ParseDoDependency(expanded_line);
- free(expanded_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.
+ */
+ {
+ char *semicolon = FindSemicolon(line);
+ if (*semicolon != '\0') {
+ /* Terminate the dependency list at the ';' */
+ *semicolon = '\0';
+ shellcmd = semicolon + 1;
+ }
+ }
+
+ /*
+ * We now know it's a dependency line so it needs to have all
+ * variables expanded before being parsed.
+ *
+ * XXX: Ideally the dependency line would first be split into
+ * its left-hand side, dependency operator and right-hand side,
+ * and then each side would be expanded on its own. This would
+ * allow for the left-hand side to allow only defined variables
+ * and to allow variables on the right-hand side to be undefined
+ * as well.
+ *
+ * Parsing the line first would also prevent that targets
+ * generated from variable expressions are interpreted as the
+ * dependency operator, such as in "target${:U\:} middle: source",
+ * in which the middle is interpreted as a source, not a target.
+ */
- if (shellcmd != NULL)
- ParseLine_ShellCommand(shellcmd);
+ /* In lint mode, allow undefined variables to appear in
+ * dependency lines.
+ *
+ * Ideally, only the right-hand side would allow undefined
+ * variables since it is common to have optional dependencies.
+ * Having undefined variables on the left-hand side is more
+ * unusual though. Since both sides are expanded in a single
+ * pass, there is not much choice what to do here.
+ *
+ * In normal mode, it does not matter whether undefined
+ * variables are allowed or not since as of 2020-09-14,
+ * Var_Parse does not print any parse errors in such a case.
+ * It simply returns the special empty string var_Error,
+ * which cannot be detected in the result of Var_Subst. */
+ eflags = opts.strict ? VARE_WANTRES : VARE_WANTRES | VARE_UNDEFERR;
+ (void)Var_Subst(line, VAR_CMDLINE, eflags, &expanded_line);
+ /* TODO: handle errors */
+
+ /* Need a fresh list for the target nodes */
+ if (targets != NULL)
+ Lst_Free(targets);
+ targets = Lst_New();
+
+ ParseDoDependency(expanded_line);
+ free(expanded_line);
+
+ if (shellcmd != NULL)
+ ParseLine_ShellCommand(shellcmd);
}
static void
ParseLine(char *line)
{
- if (ParseDirective(line))
- return;
+ /*
+ * Lines that begin with '.' can be pretty much anything:
+ * - directives like '.include' or '.if',
+ * - suffix rules like '.c.o:',
+ * - dependencies for filenames that start with '.',
+ * - variable assignments like '.tmp=value'.
+ */
+ if (line[0] == '.' && ParseDirective(line))
+ return;
- if (*line == '\t') {
- ParseLine_ShellCommand(line + 1);
- return;
- }
+ if (line[0] == '\t') {
+ ParseLine_ShellCommand(line + 1);
+ return;
+ }
#ifdef SYSVINCLUDE
- if (IsSysVInclude(line)) {
- /*
- * It's an S3/S5-style "include".
- */
- ParseTraditionalInclude(line);
- return;
- }
+ if (IsSysVInclude(line)) {
+ /*
+ * It's an S3/S5-style "include".
+ */
+ ParseTraditionalInclude(line);
+ return;
+ }
#endif
#ifdef GMAKEEXPORT
- if (strncmp(line, "export", 6) == 0 && ch_isspace(line[6]) &&
- strchr(line, ':') == NULL) {
- /*
- * It's a Gmake "export".
- */
- ParseGmakeExport(line);
- return;
- }
+ if (strncmp(line, "export", 6) == 0 && ch_isspace(line[6]) &&
+ strchr(line, ':') == NULL) {
+ /*
+ * It's a Gmake "export".
+ */
+ ParseGmakeExport(line);
+ return;
+ }
#endif
- if (ParseVarassign(line))
- return;
+ if (ParseVarassign(line))
+ return;
- FinishDependencyGroup();
+ FinishDependencyGroup();
- ParseDependency(line);
+ ParseDependency(line);
}
-/* Parse a top-level makefile, incorporating its content into the global
+/*
+ * Parse a top-level makefile, incorporating its content into the global
* dependency graph.
*
* Input:
@@ -3082,54 +3203,49 @@ ParseLine(char *line)
void
Parse_File(const char *name, int fd)
{
- char *line; /* the line we're working on */
- struct loadedfile *lf;
+ char *line; /* the line we're working on */
+ struct loadedfile *lf;
- lf = loadfile(name, fd);
+ lf = loadfile(name, fd);
- assert(targets == NULL);
+ assert(targets == NULL);
- if (name == NULL)
- name = "(stdin)";
+ if (name == NULL)
+ name = "(stdin)";
- Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf);
- CurFile()->lf = lf;
+ Parse_SetInput(name, 0, -1, loadedfile_readMore, lf);
+ CurFile()->lf = lf;
- do {
- while ((line = ParseReadLine()) != NULL) {
- DEBUG2(PARSE, "ParseReadLine (%d): '%s'\n",
- CurFile()->lineno, line);
- ParseLine(line);
+ do {
+ while ((line = ParseReadLine()) != NULL) {
+ DEBUG2(PARSE, "ParseReadLine (%d): '%s'\n",
+ CurFile()->lineno, line);
+ ParseLine(line);
+ }
+ /* Reached EOF, but it may be just EOF of an include file. */
+ } while (ParseEOF());
+
+ FinishDependencyGroup();
+
+ if (fatals != 0) {
+ (void)fflush(stdout);
+ (void)fprintf(stderr,
+ "%s: Fatal errors encountered -- cannot continue",
+ progname);
+ PrintOnError(NULL, NULL);
+ exit(1);
}
- /*
- * Reached EOF, but it may be just EOF of an include file...
- */
- } while (ParseEOF());
-
- FinishDependencyGroup();
-
- if (fatals != 0) {
- (void)fflush(stdout);
- (void)fprintf(stderr,
- "%s: Fatal errors encountered -- cannot continue",
- progname);
- PrintOnError(NULL, NULL);
- exit(1);
- }
}
/* Initialize the parsing module. */
void
Parse_Init(void)
{
- mainNode = NULL;
- parseIncPath = Lst_New();
- sysIncPath = Lst_New();
- defSysIncPath = Lst_New();
- Vector_Init(&includes, sizeof(IFile));
-#ifdef CLEANUP
- targCmds = Lst_New();
-#endif
+ mainNode = NULL;
+ parseIncPath = SearchPath_New();
+ sysIncPath = SearchPath_New();
+ defSysIncPath = SearchPath_New();
+ Vector_Init(&includes, sizeof(IFile));
}
/* Clean up the parsing module. */
@@ -3137,13 +3253,13 @@ void
Parse_End(void)
{
#ifdef CLEANUP
- Lst_Destroy(targCmds, free);
- assert(targets == NULL);
- Lst_Destroy(defSysIncPath, Dir_Destroy);
- Lst_Destroy(sysIncPath, Dir_Destroy);
- Lst_Destroy(parseIncPath, Dir_Destroy);
- assert(includes.len == 0);
- Vector_Done(&includes);
+ Lst_DoneCall(&targCmds, free);
+ assert(targets == NULL);
+ SearchPath_Free(defSysIncPath);
+ SearchPath_Free(sysIncPath);
+ SearchPath_Free(parseIncPath);
+ assert(includes.len == 0);
+ Vector_Done(&includes);
#endif
}
@@ -3152,29 +3268,23 @@ Parse_End(void)
* Return a list containing the single main target to create.
* If no such target exists, we Punt with an obnoxious error message.
*/
-GNodeList *
-Parse_MainName(void)
+void
+Parse_MainName(GNodeList *mainList)
{
- GNodeList *mainList;
-
- mainList = Lst_New();
-
- if (mainNode == NULL)
- Punt("no target to make.");
+ if (mainNode == NULL)
+ Punt("no target to make.");
- if (mainNode->type & OP_DOUBLEDEP) {
- Lst_Append(mainList, mainNode);
- Lst_AppendAll(mainList, mainNode->cohorts);
- } else
- Lst_Append(mainList, mainNode);
-
- Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL);
+ if (mainNode->type & OP_DOUBLEDEP) {
+ Lst_Append(mainList, mainNode);
+ Lst_AppendAll(mainList, &mainNode->cohorts);
+ } else
+ Lst_Append(mainList, mainNode);
- return mainList;
+ Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL);
}
int
Parse_GetFatals(void)
{
- return fatals;
+ return fatals;
}
diff --git a/pathnames.h b/pathnames.h
index 9c597b1e6758..2fdff86c9e7c 100644
--- a/pathnames.h
+++ b/pathnames.h
@@ -1,4 +1,4 @@
-/* $NetBSD: pathnames.h,v 1.17 2009/04/11 09:41:18 apb Exp $ */
+/* $NetBSD: pathnames.h,v 1.18 2020/11/29 09:27:40 rillig Exp $ */
/*
* Copyright (c) 1990, 1993
@@ -29,7 +29,7 @@
* SUCH DAMAGE.
*
* from: @(#)pathnames.h 5.2 (Berkeley) 6/1/90
- * $Id: pathnames.h,v 1.13 2009/08/26 23:43:42 sjg Exp $
+ * $Id: pathnames.h,v 1.14 2020/11/30 19:27:41 sjg Exp $
*/
#if HAVE_CONFIG_H
@@ -43,12 +43,13 @@
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif
-#define _PATH_OBJDIR "obj"
-#define _PATH_OBJDIRPREFIX "/usr/obj"
+
+#define _PATH_OBJDIR "obj"
+#define _PATH_OBJDIRPREFIX "/usr/obj"
#ifndef _PATH_DEFSHELLDIR
-#define _PATH_DEFSHELLDIR "/bin"
+#define _PATH_DEFSHELLDIR "/bin"
#endif
-#define _PATH_DEFSYSMK "sys.mk"
+#define _PATH_DEFSYSMK "sys.mk"
#define _path_defsyspath "/usr/share/mk:/usr/local/share/mk:/opt/share/mk"
#ifndef _PATH_DEFSYSPATH
# ifdef _PATH_PREFIX_SYSPATH
@@ -58,5 +59,5 @@
# endif
#endif
#ifndef _PATH_TMP
-#define _PATH_TMP "/tmp/" /* with trailing slash */
+#define _PATH_TMP "/tmp/" /* with trailing slash */
#endif
diff --git a/str.c b/str.c
index 6633482772d3..c2954ec5326d 100644
--- a/str.c
+++ b/str.c
@@ -1,4 +1,4 @@
-/* $NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig Exp $ */
+/* $NetBSD: str.c,v 1.78 2021/01/10 23:59:53 rillig Exp $ */
/*-
* Copyright (c) 1988, 1989, 1990, 1993
@@ -71,7 +71,7 @@
#include "make.h"
/* "@(#)str.c 5.8 (Berkeley) 6/1/90" */
-MAKE_RCSID("$NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig Exp $");
+MAKE_RCSID("$NetBSD: str.c,v 1.78 2021/01/10 23:59:53 rillig Exp $");
/* Return the concatenation of s1 and s2, freshly allocated. */
char *
@@ -115,7 +115,8 @@ str_concat4(const char *s1, const char *s2, const char *s3, const char *s4)
return result;
}
-/* Fracture a string into an array of words (as delineated by tabs or spaces)
+/*
+ * Fracture a string into an array of words (as delineated by tabs or spaces)
* taking quotation marks into account.
*
* If expand is TRUE, quotes are removed and escape sequences such as \r, \t,
@@ -142,7 +143,7 @@ Str_Words(const char *str, Boolean expand)
/* words_buf holds the words, separated by '\0'. */
str_len = strlen(str);
- words_buf = bmake_malloc(strlen(str) + 1);
+ words_buf = bmake_malloc(str_len + 1);
words_cap = str_len / 5 > 50 ? str_len / 5 : 50;
words = bmake_malloc((words_cap + 1) * sizeof(char *));
@@ -160,7 +161,7 @@ Str_Words(const char *str, Boolean expand)
switch (ch) {
case '"':
case '\'':
- if (inquote) {
+ if (inquote != '\0') {
if (inquote == ch)
inquote = '\0';
else
@@ -188,7 +189,7 @@ Str_Words(const char *str, Boolean expand)
case ' ':
case '\t':
case '\n':
- if (inquote)
+ if (inquote != '\0')
break;
if (word_start == NULL)
continue;
@@ -211,7 +212,7 @@ Str_Words(const char *str, Boolean expand)
words[words_len++] = word_start;
word_start = NULL;
if (ch == '\n' || ch == '\0') {
- if (expand && inquote) {
+ if (expand && inquote != '\0') {
free(words);
free(words_buf);
return (Words){ NULL, 0, NULL };
diff --git a/suff.c b/suff.c
index f0be002cebe1..db9ad982ecef 100644
--- a/suff.c
+++ b/suff.c
@@ -1,4 +1,4 @@
-/* $NetBSD: suff.c,v 1.247 2020/11/16 23:27:41 rillig Exp $ */
+/* $NetBSD: suff.c,v 1.335 2021/01/10 21:20:46 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -114,273 +114,370 @@
#include "dir.h"
/* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */
-MAKE_RCSID("$NetBSD: suff.c,v 1.247 2020/11/16 23:27:41 rillig Exp $");
+MAKE_RCSID("$NetBSD: suff.c,v 1.335 2021/01/10 21:20:46 rillig Exp $");
-#define SUFF_DEBUG0(text) DEBUG0(SUFF, text)
-#define SUFF_DEBUG1(fmt, arg1) DEBUG1(SUFF, fmt, arg1)
-#define SUFF_DEBUG2(fmt, arg1, arg2) DEBUG2(SUFF, fmt, arg1, arg2)
+typedef List SuffixList;
+typedef ListNode SuffixListNode;
-typedef List SuffList;
-typedef ListNode SuffListNode;
+typedef List CandidateList;
+typedef ListNode CandidateListNode;
-typedef List SrcList;
-typedef ListNode SrcListNode;
-
-static SuffList *sufflist; /* List of suffixes */
+/* The defined suffixes, such as '.c', '.o', '.l'. */
+static SuffixList sufflist = LST_INIT;
#ifdef CLEANUP
-static SuffList *suffClean; /* List of suffixes to be cleaned */
+/* The suffixes to be cleaned up at the end. */
+static SuffixList suffClean = LST_INIT;
#endif
-static SrcList *srclist; /* List of sources */
-
-/* List of transformation rules, such as ".c.o" */
-static GNodeList *transforms;
-
-static int sNum = 0; /* Counter for assigning suffix numbers */
-
-typedef enum SuffFlags {
- SUFF_INCLUDE = 0x01, /* One which is #include'd */
- SUFF_LIBRARY = 0x02, /* One which contains a library */
- SUFF_NULL = 0x04 /* The empty suffix */
- /* XXX: Why is SUFF_NULL needed? Wouldn't nameLen == 0 mean the same? */
-} SuffFlags;
-
-ENUM_FLAGS_RTTI_3(SuffFlags,
- SUFF_INCLUDE, SUFF_LIBRARY, SUFF_NULL);
-
-typedef List SuffListList;
-
-typedef struct Suff {
- /* The suffix itself, such as ".c" */
- char *name;
- /* Length of the name, to avoid strlen calls */
- size_t nameLen;
- /* Type of suffix */
- SuffFlags flags;
- /* The path along which files of this suffix may be found */
- SearchPath *searchPath;
- /* The suffix number; TODO: document the purpose of this number */
- int sNum;
- /* Reference count of list membership and several other places */
- int refCount;
- /* Suffixes we have a transformation to */
- SuffList *parents;
- /* Suffixes we have a transformation from */
- SuffList *children;
-
- /* Lists in which this suffix is referenced.
- * XXX: These lists are used nowhere, they are just appended to, for no
- * apparent reason. They do have the side effect of increasing refCount
- * though. */
- SuffListList *ref;
-} Suff;
/*
- * Structure used in the search for implied sources.
+ * The transformation rules, such as '.c.o' to transform '.c' into '.o',
+ * or simply '.c' to transform 'file.c' into 'file'.
+ */
+static GNodeList transforms = LST_INIT;
+
+/*
+ * Counter for assigning suffix numbers.
+ * TODO: What are these suffix numbers used for?
+ */
+static int sNum = 0;
+
+typedef enum SuffixFlags {
+ SUFF_NONE = 0,
+
+ /*
+ * This suffix marks include files. Their search path ends up in the
+ * undocumented special variable '.INCLUDES'.
+ */
+ SUFF_INCLUDE = 1 << 0,
+
+ /*
+ * This suffix marks library files. Their search path ends up in the
+ * undocumented special variable '.LIBS'.
+ */
+ SUFF_LIBRARY = 1 << 1,
+
+ /*
+ * The empty suffix.
+ *
+ * XXX: What is the difference between the empty suffix and the null
+ * suffix?
+ *
+ * XXX: Why is SUFF_NULL needed at all? Wouldn't nameLen == 0 mean
+ * the same?
+ */
+ SUFF_NULL = 1 << 2
+
+} SuffixFlags;
+
+ENUM_FLAGS_RTTI_3(SuffixFlags,
+ SUFF_INCLUDE, SUFF_LIBRARY, SUFF_NULL);
+
+typedef List SuffixListList;
+
+/*
+ * A suffix such as ".c" or ".o" that is used in suffix transformation rules
+ * such as ".c.o:".
*/
-typedef struct Src {
- char *file; /* The file to look for */
- char *pref; /* Prefix from which file was formed */
- Suff *suff; /* The suffix on the file */
- struct Src *parent; /* The Src for which this is a source */
- GNode *node; /* The node describing the file */
- int numChildren; /* Count of existing children (so we don't free
- * this thing too early or never nuke it) */
+typedef struct Suffix {
+ /* The suffix itself, such as ".c" */
+ char *name;
+ /* Length of the name, to avoid strlen calls */
+ size_t nameLen;
+ /* Type of suffix */
+ SuffixFlags flags;
+ /* The path along which files of this suffix may be found */
+ SearchPath *searchPath;
+ /* The suffix number; TODO: document the purpose of this number */
+ int sNum;
+ /* Reference count of list membership and several other places */
+ int refCount;
+ /* Suffixes we have a transformation to */
+ SuffixList parents;
+ /* Suffixes we have a transformation from */
+ SuffixList children;
+
+ /* Lists in which this suffix is referenced.
+ *
+ * XXX: These lists are used nowhere, they are just appended to, for
+ * no apparent reason. They do have the side effect of increasing
+ * refCount though. */
+ SuffixListList ref;
+} Suffix;
+
+/*
+ * A candidate when searching for implied sources.
+ *
+ * For example, when "src.o" is to be made, a typical candidate is "src.c"
+ * via the transformation rule ".c.o". If that doesn't exist, maybe there is
+ * another transformation rule ".pas.c" that would make "src.pas" an indirect
+ * candidate as well. The first such chain that leads to an existing file or
+ * node is finally chosen to be made.
+ */
+typedef struct Candidate {
+ /* The file or node to look for. */
+ char *file;
+ /* The prefix from which file was formed.
+ * Its memory is shared among all candidates. */
+ char *prefix;
+ /* The suffix on the file. */
+ Suffix *suff;
+
+ /* The candidate that can be made from this,
+ * or NULL for the top-level candidate. */
+ struct Candidate *parent;
+ /* The node describing the file. */
+ GNode *node;
+
+ /* Count of existing children, only used for memory management, so we
+ * don't free this candidate too early or too late. */
+ int numChildren;
#ifdef DEBUG_SRC
- SrcList *childrenList;
+ CandidateList childrenList;
#endif
-} Src;
+} Candidate;
+
+typedef struct CandidateSearcher {
+
+ CandidateList list;
+
+ /*
+ * TODO: Add HashSet for seen entries, to avoid endless loops such as
+ * in suff-transform-endless.mk.
+ */
+
+} CandidateSearcher;
+
-/* TODO: Document the difference between suffNull and emptySuff. */
+/* TODO: Document the difference between nullSuff and emptySuff. */
/* The NULL suffix for this run */
-static Suff *suffNull;
+static Suffix *nullSuff;
/* The empty suffix required for POSIX single-suffix transformation rules */
-static Suff *emptySuff;
+static Suffix *emptySuff;
-static void SuffFindDeps(GNode *, SrcList *);
-static void SuffExpandWildcards(GNodeListNode *, GNode *);
+static Suffix *
+Suffix_Ref(Suffix *suff)
+{
+ suff->refCount++;
+ return suff;
+}
+
+/* Change the value of a Suffix variable, adjusting the reference counts. */
+static void
+Suffix_Reassign(Suffix **var, Suffix *suff)
+{
+ if (*var != NULL)
+ (*var)->refCount--;
+ *var = suff;
+ suff->refCount++;
+}
+
+/* Set a Suffix variable to NULL, adjusting the reference count. */
+static void
+Suffix_Unassign(Suffix **var)
+{
+ if (*var != NULL)
+ (*var)->refCount--;
+ *var = NULL;
+}
/*
* See if pref is a prefix of str.
* Return NULL if it ain't, pointer to character in str after prefix if so.
*/
static const char *
-SuffStrIsPrefix(const char *pref, const char *str)
+StrTrimPrefix(const char *pref, const char *str)
{
- while (*str && *pref == *str) {
- pref++;
- str++;
- }
+ while (*str != '\0' && *pref == *str) {
+ pref++;
+ str++;
+ }
- return *pref != '\0' ? NULL : str;
+ return *pref != '\0' ? NULL : str;
}
/*
- * See if suff is a suffix of name.
- * Return NULL if it ain't, pointer to the start of suffix in name if it is.
+ * See if suff is a suffix of str, and if so, return the pointer to the suffix
+ * in str, which at the same time marks the end of the prefix.
*/
static const char *
-SuffSuffGetSuffix(const Suff *s, size_t nameLen, const char *nameEnd)
+StrTrimSuffix(const char *str, size_t strLen, const char *suff, size_t suffLen)
{
- const char *p1; /* Pointer into suffix name */
- const char *p2; /* Pointer into string being examined */
+ const char *suffInStr;
+ size_t i;
- if (nameLen < s->nameLen)
- return NULL; /* this string is shorter than the suffix */
+ if (strLen < suffLen)
+ return NULL;
- p1 = s->name + s->nameLen;
- p2 = nameEnd;
+ suffInStr = str + strLen - suffLen;
+ for (i = 0; i < suffLen; i++)
+ if (suff[i] != suffInStr[i])
+ return NULL;
- while (p1 >= s->name && *p1 == *p2) {
- p1--;
- p2--;
- }
+ return suffInStr;
+}
- /* XXX: s->name - 1 invokes undefined behavior */
- return p1 == s->name - 1 ? p2 + 1 : NULL;
+/*
+ * See if suff is a suffix of name, and if so, return the end of the prefix
+ * in name.
+ */
+static const char *
+Suffix_TrimSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd)
+{
+ return StrTrimSuffix(nameEnd - nameLen, nameLen,
+ suff->name, suff->nameLen);
}
static Boolean
-SuffSuffIsSuffix(const Suff *suff, size_t nameLen, const char *nameEnd)
+Suffix_IsSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd)
{
- return SuffSuffGetSuffix(suff, nameLen, nameEnd) != NULL;
+ return Suffix_TrimSuffix(suff, nameLen, nameEnd) != NULL;
}
-static Suff *
-FindSuffByNameLen(const char *name, size_t nameLen)
+static Suffix *
+FindSuffixByNameLen(const char *name, size_t nameLen)
{
- SuffListNode *ln;
+ SuffixListNode *ln;
- for (ln = sufflist->first; ln != NULL; ln = ln->next) {
- Suff *suff = ln->datum;
- if (suff->nameLen == nameLen && memcmp(suff->name, name, nameLen) == 0)
- return suff;
- }
- return NULL;
+ for (ln = sufflist.first; ln != NULL; ln = ln->next) {
+ Suffix *suff = ln->datum;
+ if (suff->nameLen == nameLen &&
+ memcmp(suff->name, name, nameLen) == 0)
+ return suff;
+ }
+ return NULL;
}
-static Suff *
-FindSuffByName(const char *name)
+static Suffix *
+FindSuffixByName(const char *name)
{
- return FindSuffByNameLen(name, strlen(name));
+ return FindSuffixByNameLen(name, strlen(name));
}
static GNode *
FindTransformByName(const char *name)
{
- GNodeListNode *ln;
- for (ln = transforms->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
- if (strcmp(gn->name, name) == 0)
- return gn;
- }
- return NULL;
+ GNodeListNode *ln;
+
+ for (ln = transforms.first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
+ if (strcmp(gn->name, name) == 0)
+ return gn;
+ }
+ return NULL;
}
static void
-SuffList_Unref(SuffList *list, Suff *suff)
+SuffixList_Unref(SuffixList *list, Suffix *suff)
{
- SuffListNode *ln = Lst_FindDatum(list, suff);
- if (ln != NULL) {
- Lst_Remove(list, ln);
- suff->refCount--;
- }
+ SuffixListNode *ln = Lst_FindDatum(list, suff);
+ if (ln != NULL) {
+ Lst_Remove(list, ln);
+ suff->refCount--;
+ }
}
/* Free up all memory associated with the given suffix structure. */
static void
-SuffFree(void *sp)
+Suffix_Free(Suffix *suff)
{
- Suff *suff = sp;
- if (suff == suffNull)
- suffNull = NULL;
+ if (suff == nullSuff)
+ nullSuff = NULL;
- if (suff == emptySuff)
- emptySuff = NULL;
+ if (suff == emptySuff)
+ emptySuff = NULL;
#if 0
- /* We don't delete suffixes in order, so we cannot use this */
- if (suff->refCount != 0)
- Punt("Internal error deleting suffix `%s' with refcount = %d",
- suff->name, suff->refCount);
+ /* We don't delete suffixes in order, so we cannot use this */
+ if (suff->refCount != 0)
+ Punt("Internal error deleting suffix `%s' with refcount = %d",
+ suff->name, suff->refCount);
#endif
- Lst_Free(suff->ref);
- Lst_Free(suff->children);
- Lst_Free(suff->parents);
- Lst_Destroy(suff->searchPath, Dir_Destroy);
+ Lst_Done(&suff->ref);
+ Lst_Done(&suff->children);
+ Lst_Done(&suff->parents);
+ SearchPath_Free(suff->searchPath);
- free(suff->name);
- free(suff);
+ free(suff->name);
+ free(suff);
+}
+
+static void
+SuffFree(void *p)
+{
+ Suffix_Free(p);
}
/* Remove the suffix from the list, and free if it is otherwise unused. */
static void
-SuffList_Remove(SuffList *list, Suff *suff)
+SuffixList_Remove(SuffixList *list, Suffix *suff)
{
- SuffList_Unref(list, suff);
- if (suff->refCount == 0) {
- /* XXX: can lead to suff->refCount == -1 */
- SuffList_Unref(sufflist, suff);
- SuffFree(suff);
- }
+ SuffixList_Unref(list, suff);
+ if (suff->refCount == 0) {
+ /* XXX: can lead to suff->refCount == -1 */
+ SuffixList_Unref(&sufflist, suff);
+ DEBUG1(SUFF, "Removing suffix \"%s\"\n", suff->name);
+ SuffFree(suff);
+ }
}
-/* Insert the suffix into the list, keeping the list ordered by suffix
- * number. */
+/*
+ * Insert the suffix into the list, keeping the list ordered by suffix
+ * number.
+ */
static void
-SuffList_Insert(SuffList *list, Suff *suff)
+SuffixList_Insert(SuffixList *list, Suffix *suff)
{
- SuffListNode *ln;
- Suff *listSuff = NULL;
+ SuffixListNode *ln;
+ Suffix *listSuff = NULL;
- for (ln = list->first; ln != NULL; ln = ln->next) {
- listSuff = ln->datum;
- if (listSuff->sNum >= suff->sNum)
- break;
- }
+ for (ln = list->first; ln != NULL; ln = ln->next) {
+ listSuff = ln->datum;
+ if (listSuff->sNum >= suff->sNum)
+ break;
+ }
- if (ln == NULL) {
- SUFF_DEBUG2("inserting \"%s\" (%d) at end of list\n",
+ if (ln == NULL) {
+ DEBUG2(SUFF, "inserting \"%s\" (%d) at end of list\n",
suff->name, suff->sNum);
- Lst_Append(list, suff);
- suff->refCount++;
- Lst_Append(suff->ref, list);
- } else if (listSuff->sNum != suff->sNum) {
- DEBUG4(SUFF, "inserting \"%s\" (%d) before \"%s\" (%d)\n",
- suff->name, suff->sNum, listSuff->name, listSuff->sNum);
- Lst_InsertBefore(list, ln, suff);
- suff->refCount++;
- Lst_Append(suff->ref, list);
- } else {
- SUFF_DEBUG2("\"%s\" (%d) is already there\n", suff->name, suff->sNum);
- }
+ Lst_Append(list, Suffix_Ref(suff));
+ Lst_Append(&suff->ref, list);
+ } else if (listSuff->sNum != suff->sNum) {
+ DEBUG4(SUFF, "inserting \"%s\" (%d) before \"%s\" (%d)\n",
+ suff->name, suff->sNum, listSuff->name, listSuff->sNum);
+ Lst_InsertBefore(list, ln, Suffix_Ref(suff));
+ Lst_Append(&suff->ref, list);
+ } else {
+ DEBUG2(SUFF, "\"%s\" (%d) is already there\n",
+ suff->name, suff->sNum);
+ }
}
static void
-SuffRelate(Suff *srcSuff, Suff *targSuff)
+Relate(Suffix *srcSuff, Suffix *targSuff)
{
- SuffList_Insert(targSuff->children, srcSuff);
- SuffList_Insert(srcSuff->parents, targSuff);
+ SuffixList_Insert(&targSuff->children, srcSuff);
+ SuffixList_Insert(&srcSuff->parents, targSuff);
}
-static Suff *
-SuffNew(const char *name)
+static Suffix *
+Suffix_New(const char *name)
{
- Suff *suff = bmake_malloc(sizeof *suff);
-
- suff->name = bmake_strdup(name);
- suff->nameLen = strlen(suff->name);
- suff->searchPath = Lst_New();
- suff->children = Lst_New();
- suff->parents = Lst_New();
- suff->ref = Lst_New();
- suff->sNum = sNum++;
- suff->flags = 0;
- suff->refCount = 1; /* XXX: why 1? It's not assigned anywhere yet. */
-
- return suff;
+ Suffix *suff = bmake_malloc(sizeof *suff);
+
+ suff->name = bmake_strdup(name);
+ suff->nameLen = strlen(suff->name);
+ suff->searchPath = SearchPath_New();
+ Lst_Init(&suff->children);
+ Lst_Init(&suff->parents);
+ Lst_Init(&suff->ref);
+ suff->sNum = sNum++;
+ suff->flags = SUFF_NONE;
+ suff->refCount = 1; /* XXX: why 1? It's not assigned anywhere yet. */
+
+ return suff;
}
/*
@@ -394,83 +491,89 @@ void
Suff_ClearSuffixes(void)
{
#ifdef CLEANUP
- Lst_MoveAll(suffClean, sufflist);
+ Lst_MoveAll(&suffClean, &sufflist);
#endif
- sufflist = Lst_New();
- sNum = 0;
- if (suffNull)
- SuffFree(suffNull);
- emptySuff = suffNull = SuffNew("");
-
- Dir_Concat(suffNull->searchPath, dirSearchPath);
- suffNull->flags = SUFF_NULL;
+ DEBUG0(SUFF, "Clearing all suffixes\n");
+ Lst_Init(&sufflist);
+ sNum = 0;
+ if (nullSuff != NULL)
+ SuffFree(nullSuff);
+ emptySuff = nullSuff = Suffix_New("");
+
+ SearchPath_AddAll(nullSuff->searchPath, &dirSearchPath);
+ nullSuff->flags = SUFF_NULL;
}
-/* Parse a transformation string such as ".c.o" to find its two component
+/*
+ * Parse a transformation string such as ".c.o" to find its two component
* suffixes (the source ".c" and the target ".o"). If there are no such
* suffixes, try a single-suffix transformation as well.
*
* Return TRUE if the string is a valid transformation.
*/
static Boolean
-SuffParseTransform(const char *str, Suff **out_src, Suff **out_targ)
+ParseTransform(const char *str, Suffix **out_src, Suffix **out_targ)
{
- SuffListNode *ln;
- Suff *singleSrc = NULL;
+ SuffixListNode *ln;
+ Suffix *single = NULL;
- /*
- * Loop looking first for a suffix that matches the start of the
- * string and then for one that exactly matches the rest of it. If
- * we can find two that meet these criteria, we've successfully
- * parsed the string.
- */
- for (ln = sufflist->first; ln != NULL; ln = ln->next) {
- Suff *src = ln->datum;
+ /*
+ * Loop looking first for a suffix that matches the start of the
+ * string and then for one that exactly matches the rest of it. If
+ * we can find two that meet these criteria, we've successfully
+ * parsed the string.
+ */
+ for (ln = sufflist.first; ln != NULL; ln = ln->next) {
+ Suffix *src = ln->datum;
- if (SuffStrIsPrefix(src->name, str) == NULL)
- continue;
+ if (StrTrimPrefix(src->name, str) == NULL)
+ continue;
- if (str[src->nameLen] == '\0') {
- singleSrc = src;
- } else {
- Suff *targ = FindSuffByName(str + src->nameLen);
- if (targ != NULL) {
- *out_src = src;
- *out_targ = targ;
- return TRUE;
- }
+ if (str[src->nameLen] == '\0') {
+ single = src;
+ } else {
+ Suffix *targ = FindSuffixByName(str + src->nameLen);
+ if (targ != NULL) {
+ *out_src = src;
+ *out_targ = targ;
+ return TRUE;
+ }
+ }
}
- }
- if (singleSrc != NULL) {
- /*
- * Not so fast Mr. Smith! There was a suffix that encompassed
- * the entire string, so we assume it was a transformation
- * to the null suffix (thank you POSIX). We still prefer to
- * find a double rule over a singleton, hence we leave this
- * check until the end.
- *
- * XXX: Use emptySuff over suffNull?
- */
- *out_src = singleSrc;
- *out_targ = suffNull;
- return TRUE;
- }
- return FALSE;
+ if (single != NULL) {
+ /*
+ * There was a suffix that encompassed the entire string, so we
+ * assume it was a transformation to the null suffix (thank you
+ * POSIX; search for "single suffix" or "single-suffix").
+ *
+ * We still prefer to find a double rule over a singleton,
+ * hence we leave this check until the end.
+ *
+ * XXX: Use emptySuff over nullSuff?
+ */
+ *out_src = single;
+ *out_targ = nullSuff;
+ return TRUE;
+ }
+ return FALSE;
}
-/* Return TRUE if the given string is a transformation rule, that is, a
+/*
+ * Return TRUE if the given string is a transformation rule, that is, a
* concatenation of two known suffixes such as ".c.o" or a single suffix
- * such as ".o". */
+ * such as ".o".
+ */
Boolean
Suff_IsTransform(const char *str)
{
- Suff *src, *targ;
+ Suffix *src, *targ;
- return SuffParseTransform(str, &src, &targ);
+ return ParseTransform(str, &src, &targ);
}
-/* Add the transformation rule to the list of rules and place the
+/*
+ * Add the transformation rule to the list of rules and place the
* transformation itself in the graph.
*
* The transformation is linked to the two suffixes mentioned in the name.
@@ -484,49 +587,50 @@ Suff_IsTransform(const char *str)
GNode *
Suff_AddTransform(const char *name)
{
- Suff *srcSuff;
- Suff *targSuff;
+ Suffix *srcSuff;
+ Suffix *targSuff;
- GNode *gn = FindTransformByName(name);
- if (gn == NULL) {
- /*
- * Make a new graph node for the transformation. It will be filled in
- * by the Parse module.
- */
- gn = GNode_New(name);
- Lst_Append(transforms, gn);
- } else {
- /*
- * New specification for transformation rule. Just nuke the old list
- * of commands so they can be filled in again... We don't actually
- * free the commands themselves, because a given command can be
- * attached to several different transformations.
- */
- Lst_Free(gn->commands);
- Lst_Free(gn->children);
- gn->commands = Lst_New();
- gn->children = Lst_New();
- }
+ GNode *gn = FindTransformByName(name);
+ if (gn == NULL) {
+ /*
+ * Make a new graph node for the transformation. It will be
+ * filled in by the Parse module.
+ */
+ gn = GNode_New(name);
+ Lst_Append(&transforms, gn);
+ } else {
+ /*
+ * New specification for transformation rule. Just nuke the
+ * old list of commands so they can be filled in again. We
+ * don't actually free the commands themselves, because a
+ * given command can be attached to several different
+ * transformations.
+ */
+ Lst_Done(&gn->commands);
+ Lst_Init(&gn->commands);
+ Lst_Done(&gn->children);
+ Lst_Init(&gn->children);
+ }
- gn->type = OP_TRANSFORM;
+ gn->type = OP_TRANSFORM;
- {
- Boolean ok = SuffParseTransform(name, &srcSuff, &targSuff);
- assert(ok);
- (void)ok;
- }
+ {
+ /* TODO: Avoid the redundant parsing here. */
+ Boolean ok = ParseTransform(name, &srcSuff, &targSuff);
+ assert(ok);
+ (void)ok;
+ }
- /*
- * link the two together in the proper relationship and order
- */
- SUFF_DEBUG2("defining transformation from `%s' to `%s'\n",
- srcSuff->name, targSuff->name);
- SuffRelate(srcSuff, targSuff);
+ /* Link the two together in the proper relationship and order. */
+ DEBUG2(SUFF, "defining transformation from `%s' to `%s'\n",
+ srcSuff->name, targSuff->name);
+ Relate(srcSuff, targSuff);
- return gn;
+ return gn;
}
-/* Handle the finish of a transformation definition, removing the
+/*
+ * Handle the finish of a transformation definition, removing the
* transformation from the graph if it has neither commands nor sources.
*
* If the node has no commands or children, the children and parents lists
@@ -538,38 +642,41 @@ Suff_AddTransform(const char *name)
void
Suff_EndTransform(GNode *gn)
{
- if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(gn->cohorts))
- gn = gn->cohorts->last->datum;
+ Suffix *srcSuff, *targSuff;
+ SuffixList *srcSuffParents;
+
+ if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(&gn->cohorts))
+ gn = gn->cohorts.last->datum;
- if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) &&
- Lst_IsEmpty(gn->children))
- {
- Suff *srcSuff, *targSuff;
+ if (!(gn->type & OP_TRANSFORM))
+ return;
+
+ if (!Lst_IsEmpty(&gn->commands) || !Lst_IsEmpty(&gn->children)) {
+ DEBUG1(SUFF, "transformation %s complete\n", gn->name);
+ return;
+ }
/*
* SuffParseTransform() may fail for special rules which are not
* actual transformation rules. (e.g. .DEFAULT)
*/
- if (SuffParseTransform(gn->name, &srcSuff, &targSuff)) {
-
- /*
- * Remember parents since srcSuff could be deleted in
- * SuffList_Remove
- */
- SuffList *srcSuffParents = srcSuff->parents;
+ if (!ParseTransform(gn->name, &srcSuff, &targSuff))
+ return;
- SUFF_DEBUG2("deleting transformation from `%s' to `%s'\n",
- srcSuff->name, targSuff->name);
+ DEBUG2(SUFF, "deleting incomplete transformation from `%s' to `%s'\n",
+ srcSuff->name, targSuff->name);
- SuffList_Remove(targSuff->children, srcSuff);
- SuffList_Remove(srcSuffParents, targSuff);
- }
- } else if (gn->type & OP_TRANSFORM) {
- SUFF_DEBUG1("transformation %s complete\n", gn->name);
- }
+ /*
+ * Remember the parents since srcSuff could be deleted in
+ * SuffixList_Remove.
+ */
+ srcSuffParents = &srcSuff->parents;
+ SuffixList_Remove(&targSuff->children, srcSuff);
+ SuffixList_Remove(srcSuffParents, targSuff);
}
-/* Called from Suff_AddSuffix to search through the list of
+/*
+ * Called from Suff_AddSuffix to search through the list of
* existing transformation rules and rebuild the transformation graph when
* it has been destroyed by Suff_ClearSuffixes. If the given rule is a
* transformation involving this suffix and another, existing suffix, the
@@ -583,37 +690,34 @@ Suff_EndTransform(GNode *gn)
* suff Suffix to rebuild
*/
static void
-SuffRebuildGraph(GNode *transform, Suff *suff)
-{
- const char *name = transform->name;
- size_t nameLen = strlen(name);
- const char *toName;
-
- /*
- * First see if it is a transformation from this suffix.
- */
- toName = SuffStrIsPrefix(suff->name, name);
- if (toName != NULL) {
- Suff *to = FindSuffByName(toName);
- if (to != NULL) {
- /* Link in and return, since it can't be anything else. */
- SuffRelate(suff, to);
- return;
+RebuildGraph(GNode *transform, Suffix *suff)
+{
+ const char *name = transform->name;
+ size_t nameLen = strlen(name);
+ const char *toName;
+
+ /* See if it is a transformation from this suffix to another suffix. */
+ toName = StrTrimPrefix(suff->name, name);
+ if (toName != NULL) {
+ Suffix *to = FindSuffixByName(toName);
+ if (to != NULL) {
+ Relate(suff, to);
+ return;
+ }
}
- }
- /*
- * Not from, maybe to?
- */
- toName = SuffSuffGetSuffix(suff, nameLen, name + nameLen);
- if (toName != NULL) {
- Suff *from = FindSuffByNameLen(name, (size_t)(toName - name));
- if (from != NULL)
- SuffRelate(from, suff);
- }
+ /* See if it is a transformation from another suffix to this suffix. */
+ toName = Suffix_TrimSuffix(suff, nameLen, name + nameLen);
+ if (toName != NULL) {
+ Suffix *from = FindSuffixByNameLen(name,
+ (size_t)(toName - name));
+ if (from != NULL)
+ Relate(from, suff);
+ }
}
-/* During Suff_AddSuffix, search through the list of existing targets and find
+/*
+ * During Suff_AddSuffix, search through the list of existing targets and find
* if any of the existing targets can be turned into a transformation rule.
*
* If such a target is found and the target is the current main target, the
@@ -624,65 +728,103 @@ SuffRebuildGraph(GNode *transform, Suff *suff)
* TRUE iff a new main target has been selected.
*/
static Boolean
-SuffScanTargets(GNode *target, GNode **inout_main, Suff *gs_s, Boolean *gs_r)
+UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff,
+ Boolean *inout_removedMain)
{
- Suff *srcSuff, *targSuff;
- char *ptr;
+ Suffix *srcSuff, *targSuff;
+ char *ptr;
- if (*inout_main == NULL && *gs_r && !(target->type & OP_NOTARGET)) {
- *inout_main = target;
- Targ_SetMain(target);
- return TRUE;
- }
+ if (*inout_main == NULL && *inout_removedMain &&
+ !(target->type & OP_NOTARGET)) {
+ DEBUG1(MAKE, "Setting main node to \"%s\"\n", target->name);
+ *inout_main = target;
+ Targ_SetMain(target);
+ /*
+ * XXX: Why could it be a good idea to return TRUE here?
+ * The main task of this function is to turn ordinary nodes
+ * into transformations, no matter whether or not a new .MAIN
+ * node has been found.
+ */
+ /*
+ * XXX: Even when changing this to FALSE, none of the existing
+ * unit tests fails.
+ */
+ return TRUE;
+ }
- if (target->type == OP_TRANSFORM)
- return FALSE;
+ if (target->type == OP_TRANSFORM)
+ return FALSE;
- if ((ptr = strstr(target->name, gs_s->name)) == NULL ||
- ptr == target->name)
- return FALSE;
+ /*
+ * XXX: What about a transformation ".cpp.c"? If ".c" is added as
+ * a new suffix, it seems wrong that this transformation would be
+ * skipped just because ".c" happens to be a prefix of ".cpp".
+ */
+ ptr = strstr(target->name, suff->name);
+ if (ptr == NULL)
+ return FALSE;
- if (SuffParseTransform(target->name, &srcSuff, &targSuff)) {
- if (*inout_main == target) {
- *gs_r = TRUE;
- *inout_main = NULL;
- Targ_SetMain(NULL);
- }
- Lst_Free(target->children);
- target->children = Lst_New();
- target->type = OP_TRANSFORM;
/*
- * link the two together in the proper relationship and order
+ * XXX: In suff-rebuild.mk, in the line '.SUFFIXES: .c .b .a', this
+ * condition prevents the rule '.b.c' from being added again during
+ * Suff_AddSuffix(".b").
+ *
+ * XXX: Removing this paragraph makes suff-add-later.mk use massive
+ * amounts of memory.
*/
- SUFF_DEBUG2("defining transformation from `%s' to `%s'\n",
+ if (ptr == target->name)
+ return FALSE;
+
+ if (ParseTransform(target->name, &srcSuff, &targSuff)) {
+ if (*inout_main == target) {
+ DEBUG1(MAKE,
+ "Setting main node from \"%s\" back to null\n",
+ target->name);
+ *inout_removedMain = TRUE;
+ *inout_main = NULL;
+ Targ_SetMain(NULL);
+ }
+ Lst_Done(&target->children);
+ Lst_Init(&target->children);
+ target->type = OP_TRANSFORM;
+
+ /*
+ * Link the two together in the proper relationship and order.
+ */
+ DEBUG2(SUFF, "defining transformation from `%s' to `%s'\n",
srcSuff->name, targSuff->name);
- SuffRelate(srcSuff, targSuff);
- }
- return FALSE;
+ Relate(srcSuff, targSuff);
+ }
+ return FALSE;
}
-/* Look at all existing targets to see if adding this suffix will make one
+/*
+ * Look at all existing targets to see if adding this suffix will make one
* of the current targets mutate into a suffix rule.
*
* This is ugly, but other makes treat all targets that start with a '.' as
- * suffix rules. */
+ * suffix rules.
+ */
static void
-UpdateTargets(GNode **inout_main, Suff *s)
+UpdateTargets(GNode **inout_main, Suffix *suff)
{
- Boolean r = FALSE;
- GNodeListNode *ln;
- for (ln = Targ_List()->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
- if (SuffScanTargets(gn, inout_main, s, &r))
- break;
- }
+ Boolean removedMain = FALSE;
+ GNodeListNode *ln;
+
+ for (ln = Targ_List()->first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
+ if (UpdateTarget(gn, inout_main, suff, &removedMain))
+ break;
+ }
}
-/* Add the suffix to the end of the list of known suffixes.
- * Should we restructure the suffix graph? Make doesn't...
+/*
+ * Add the suffix to the end of the list of known suffixes.
+ * Should we restructure the suffix graph? Make doesn't.
*
- * A GNode is created for the suffix and a Suff structure is created and
- * added to the suffixes list unless the suffix was already known.
+ * A GNode is created for the suffix (XXX: this sounds completely wrong) and
+ * a Suffix structure is created and added to the suffixes list unless the
+ * suffix was already known.
* The mainNode passed can be modified if a target mutated into a
* transform and that target happened to be the main target.
*
@@ -692,31 +834,32 @@ UpdateTargets(GNode **inout_main, Suff *s)
void
Suff_AddSuffix(const char *name, GNode **inout_main)
{
- GNodeListNode *ln;
+ GNodeListNode *ln;
- Suff *s = FindSuffByName(name);
- if (s != NULL)
- return;
+ Suffix *suff = FindSuffixByName(name);
+ if (suff != NULL)
+ return;
- s = SuffNew(name);
- Lst_Append(sufflist, s);
+ suff = Suffix_New(name);
+ Lst_Append(&sufflist, suff);
+ DEBUG1(SUFF, "Adding suffix \"%s\"\n", suff->name);
- UpdateTargets(inout_main, s);
+ UpdateTargets(inout_main, suff);
- /*
- * Look for any existing transformations from or to this suffix.
- * XXX: Only do this after a Suff_ClearSuffixes?
- */
- for (ln = transforms->first; ln != NULL; ln = ln->next)
- SuffRebuildGraph(ln->datum, s);
+ /*
+ * Look for any existing transformations from or to this suffix.
+ * XXX: Only do this after a Suff_ClearSuffixes?
+ */
+ for (ln = transforms.first; ln != NULL; ln = ln->next)
+ RebuildGraph(ln->datum, suff);
}
/* Return the search path for the given suffix, or NULL. */
SearchPath *
Suff_GetPath(const char *sname)
{
- Suff *s = FindSuffByName(sname);
- return s != NULL ? s->searchPath : NULL;
+ Suffix *suff = FindSuffixByName(sname);
+ return suff != NULL ? suff->searchPath : NULL;
}
/*
@@ -735,540 +878,572 @@ Suff_GetPath(const char *sname)
void
Suff_DoPaths(void)
{
- SuffListNode *ln;
- char *ptr;
- SearchPath *inIncludes; /* Cumulative .INCLUDES path */
- SearchPath *inLibs; /* Cumulative .LIBS path */
-
- inIncludes = Lst_New();
- inLibs = Lst_New();
-
- for (ln = sufflist->first; ln != NULL; ln = ln->next) {
- Suff *s = ln->datum;
- if (!Lst_IsEmpty(s->searchPath)) {
+ SuffixListNode *ln;
+ char *flags;
+ SearchPath *includesPath = SearchPath_New();
+ SearchPath *libsPath = SearchPath_New();
+
+ for (ln = sufflist.first; ln != NULL; ln = ln->next) {
+ Suffix *suff = ln->datum;
+ if (!Lst_IsEmpty(suff->searchPath)) {
#ifdef INCLUDES
- if (s->flags & SUFF_INCLUDE)
- Dir_Concat(inIncludes, s->searchPath);
+ if (suff->flags & SUFF_INCLUDE)
+ SearchPath_AddAll(includesPath,
+ suff->searchPath);
#endif
#ifdef LIBRARIES
- if (s->flags & SUFF_LIBRARY)
- Dir_Concat(inLibs, s->searchPath);
+ if (suff->flags & SUFF_LIBRARY)
+ SearchPath_AddAll(libsPath, suff->searchPath);
#endif
- Dir_Concat(s->searchPath, dirSearchPath);
- } else {
- Lst_Destroy(s->searchPath, Dir_Destroy);
- s->searchPath = Dir_CopyDirSearchPath();
+ SearchPath_AddAll(suff->searchPath, &dirSearchPath);
+ } else {
+ SearchPath_Free(suff->searchPath);
+ suff->searchPath = Dir_CopyDirSearchPath();
+ }
}
- }
- Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL);
- free(ptr);
- Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL);
- free(ptr);
+ flags = SearchPath_ToFlags("-I", includesPath);
+ Var_Set(".INCLUDES", flags, VAR_GLOBAL);
+ free(flags);
+
+ flags = SearchPath_ToFlags("-L", libsPath);
+ Var_Set(".LIBS", flags, VAR_GLOBAL);
+ free(flags);
- Lst_Destroy(inIncludes, Dir_Destroy);
- Lst_Destroy(inLibs, Dir_Destroy);
+ SearchPath_Free(includesPath);
+ SearchPath_Free(libsPath);
}
-/* Add the given suffix as a type of file which gets included.
- * Called from the parse module when a .INCLUDES line is parsed.
- * The suffix must have already been defined.
- * The SUFF_INCLUDE bit is set in the suffix's flags field.
- *
- * Input:
- * sname Name of the suffix to mark
+/*
+ * Add the given suffix as a type of file which gets included.
+ * Called when a '.INCLUDES: .h' line is parsed.
+ * To have an effect, the suffix must already exist.
+ * This affects the magic variable '.INCLUDES'.
*/
void
-Suff_AddInclude(const char *sname)
+Suff_AddInclude(const char *suffName)
{
- Suff *suff = FindSuffByName(sname);
- if (suff != NULL)
- suff->flags |= SUFF_INCLUDE;
+ Suffix *suff = FindSuffixByName(suffName);
+ if (suff != NULL)
+ suff->flags |= SUFF_INCLUDE;
}
-/* Add the given suffix as a type of file which is a library.
- * Called from the parse module when parsing a .LIBS line.
- * The suffix must have been defined via .SUFFIXES before this is called.
- * The SUFF_LIBRARY bit is set in the suffix's flags field.
- *
- * Input:
- * sname Name of the suffix to mark
+/*
+ * Add the given suffix as a type of file which is a library.
+ * Called when a '.LIBS: .a' line is parsed.
+ * To have an effect, the suffix must already exist.
+ * This affects the magic variable '.LIBS'.
*/
void
-Suff_AddLib(const char *sname)
+Suff_AddLib(const char *suffName)
+{
+ Suffix *suff = FindSuffixByName(suffName);
+ if (suff != NULL)
+ suff->flags |= SUFF_LIBRARY;
+}
+
+/********** Implicit Source Search Functions *********/
+
+static void
+CandidateSearcher_Init(CandidateSearcher *cs)
{
- Suff *suff = FindSuffByName(sname);
- if (suff != NULL)
- suff->flags |= SUFF_LIBRARY;
+ Lst_Init(&cs->list);
+}
+
+static void
+CandidateSearcher_Done(CandidateSearcher *cs)
+{
+ Lst_Done(&cs->list);
+}
+
+static void
+CandidateSearcher_Add(CandidateSearcher *cs, Candidate *cand)
+{
+ /* TODO: filter duplicates */
+ Lst_Append(&cs->list, cand);
+}
+
+static void
+CandidateSearcher_AddIfNew(CandidateSearcher *cs, Candidate *cand)
+{
+ /* TODO: filter duplicates */
+ if (Lst_FindDatum(&cs->list, cand) == NULL)
+ Lst_Append(&cs->list, cand);
+}
+
+static void
+CandidateSearcher_MoveAll(CandidateSearcher *cs, CandidateList *list)
+{
+ /* TODO: filter duplicates */
+ Lst_MoveAll(&cs->list, list);
}
- /********** Implicit Source Search Functions *********/
#ifdef DEBUG_SRC
static void
-SrcList_PrintAddrs(SrcList *srcList)
+CandidateList_PrintAddrs(CandidateList *list)
{
- SrcListNode *ln;
- for (ln = srcList->first; ln != NULL; ln = ln->next)
- debug_printf(" %p", ln->datum);
- debug_printf("\n");
+ CandidateListNode *ln;
+
+ for (ln = list->first; ln != NULL; ln = ln->next) {
+ Candidate *cand = ln->datum;
+ debug_printf(" %p:%s", cand, cand->file);
+ }
+ debug_printf("\n");
}
#endif
-static Src *
-SrcNew(char *name, char *pref, Suff *suff, Src *parent, GNode *gn)
+static Candidate *
+Candidate_New(char *name, char *prefix, Suffix *suff, Candidate *parent,
+ GNode *gn)
{
- Src *src = bmake_malloc(sizeof *src);
-
- src->file = name;
- src->pref = pref;
- src->suff = suff;
- src->parent = parent;
- src->node = gn;
- src->numChildren = 0;
+ Candidate *cand = bmake_malloc(sizeof *cand);
+
+ cand->file = name;
+ cand->prefix = prefix;
+ cand->suff = Suffix_Ref(suff);
+ cand->parent = parent;
+ cand->node = gn;
+ cand->numChildren = 0;
#ifdef DEBUG_SRC
- src->childrenList = Lst_New();
+ Lst_Init(&cand->childrenList);
#endif
- return src;
+ return cand;
}
+/* Add a new candidate to the list. */
+/*ARGSUSED*/
static void
-SuffAddSrc(Suff *suff, SrcList *srcList, Src *targ, char *srcName,
- const char *debug_tag MAKE_ATTR_UNUSED)
+CandidateList_Add(CandidateList *list, char *srcName, Candidate *targ,
+ Suffix *suff, const char *debug_tag)
{
- Src *s2 = SrcNew(srcName, targ->pref, suff, targ, NULL);
- suff->refCount++;
- targ->numChildren++;
- Lst_Append(srcList, s2);
+ Candidate *cand = Candidate_New(srcName, targ->prefix, suff, targ,
+ NULL);
+ targ->numChildren++;
+ Lst_Append(list, cand);
+
#ifdef DEBUG_SRC
- Lst_Append(targ->childrenList, s2);
- debug_printf("%s add suff %p src %p to list %p:",
- debug_tag, targ, s2, srcList);
- SrcList_PrintAddrs(srcList);
+ Lst_Append(&targ->childrenList, cand);
+ debug_printf("%s add suff %p:%s candidate %p:%s to list %p:",
+ debug_tag, targ, targ->file, cand, cand->file, list);
+ CandidateList_PrintAddrs(list);
#endif
}
-/* Add a suffix as a Src structure to the given list with its parent
- * being the given Src structure. If the suffix is the null suffix,
- * the prefix is used unaltered as the filename in the Src structure.
- *
- * Input:
- * suff suffix for which to create a Src structure
- * srcList list for the new Src
- * targ parent for the new Src
+/*
+ * Add all candidates to the list that can be formed by applying a suffix to
+ * the candidate.
*/
static void
-SuffAddSources(Suff *suff, SrcList *srcList, Src *targ)
+CandidateList_AddCandidatesFor(CandidateList *list, Candidate *cand)
{
- if ((suff->flags & SUFF_NULL) && suff->name[0] != '\0') {
- /*
- * If the suffix has been marked as the NULL suffix, also create a Src
- * structure for a file with no suffix attached. Two birds, and all
- * that...
- */
- SuffAddSrc(suff, srcList, targ, bmake_strdup(targ->pref), "1");
- }
- SuffAddSrc(suff, srcList, targ, str_concat2(targ->pref, suff->name), "2");
-}
+ SuffixListNode *ln;
+ for (ln = cand->suff->children.first; ln != NULL; ln = ln->next) {
+ Suffix *suff = ln->datum;
+
+ if ((suff->flags & SUFF_NULL) && suff->name[0] != '\0') {
+ /*
+ * If the suffix has been marked as the NULL suffix,
+ * also create a candidate for a file with no suffix
+ * attached.
+ */
+ CandidateList_Add(list, bmake_strdup(cand->prefix),
+ cand, suff, "1");
+ }
-/* Add all the children of targ to the list. */
-static void
-SuffAddLevel(SrcList *srcs, Src *targ)
-{
- SrcListNode *ln;
- for (ln = targ->suff->children->first; ln != NULL; ln = ln->next) {
- Suff *childSuff = ln->datum;
- SuffAddSources(childSuff, srcs, targ);
- }
+ CandidateList_Add(list, str_concat2(cand->prefix, suff->name),
+ cand, suff, "2");
+ }
}
-/* Free the first Src in the list that is not referenced anymore.
- * Return whether a Src was removed. */
+/*
+ * Free the first candidate in the list that is not referenced anymore.
+ * Return whether a candidate was removed.
+ */
static Boolean
-SuffRemoveSrc(SrcList *l)
+RemoveCandidate(CandidateList *srcs)
{
- SrcListNode *ln;
+ CandidateListNode *ln;
#ifdef DEBUG_SRC
- debug_printf("cleaning list %p:", l);
- SrcList_PrintAddrs(l);
+ debug_printf("cleaning list %p:", srcs);
+ CandidateList_PrintAddrs(srcs);
#endif
- for (ln = l->first; ln != NULL; ln = ln->next) {
- Src *src = ln->datum;
+ for (ln = srcs->first; ln != NULL; ln = ln->next) {
+ Candidate *src = ln->datum;
- if (src->numChildren == 0) {
- free(src->file);
- if (src->parent == NULL)
- free(src->pref);
- else {
+ if (src->numChildren == 0) {
+ if (src->parent == NULL)
+ free(src->prefix);
+ else {
#ifdef DEBUG_SRC
- SrcListNode *ln2 = Lst_FindDatum(src->parent->childrenList, src);
- if (ln2 != NULL)
- Lst_Remove(src->parent->childrenList, ln2);
+ /* XXX: Lst_RemoveDatum */
+ CandidateListNode *ln2;
+ ln2 = Lst_FindDatum(&src->parent->childrenList,
+ src);
+ if (ln2 != NULL)
+ Lst_Remove(&src->parent->childrenList,
+ ln2);
#endif
- src->parent->numChildren--;
- }
+ src->parent->numChildren--;
+ }
#ifdef DEBUG_SRC
- debug_printf("free: list %p src %p children %d\n",
- l, src, src->children);
- Lst_Free(src->childrenList);
+ debug_printf("free: list %p src %p:%s children %d\n",
+ srcs, src, src->file, src->numChildren);
+ Lst_Done(&src->childrenList);
#endif
- Lst_Remove(l, ln);
- free(src);
- return TRUE;
- }
+ Lst_Remove(srcs, ln);
+ free(src->file);
+ free(src);
+ return TRUE;
+ }
#ifdef DEBUG_SRC
- else {
- debug_printf("keep: list %p src %p children %d:",
- l, src, src->children);
- SrcList_PrintAddrs(src->childrenList);
- }
+ else {
+ debug_printf("keep: list %p src %p:%s children %d:",
+ srcs, src, src->file, src->numChildren);
+ CandidateList_PrintAddrs(&src->childrenList);
+ }
#endif
- }
+ }
- return FALSE;
+ return FALSE;
}
/* Find the first existing file/target in srcs. */
-static Src *
-SuffFindThem(SrcList *srcs, SrcList *slst)
+static Candidate *
+FindThem(CandidateList *srcs, CandidateSearcher *cs)
{
- Src *retsrc = NULL;
+ HashSet seen;
- while (!Lst_IsEmpty(srcs)) {
- Src *src = Lst_Dequeue(srcs);
+ HashSet_Init(&seen);
- SUFF_DEBUG1("\ttrying %s...", src->file);
+ while (!Lst_IsEmpty(srcs)) {
+ Candidate *src = Lst_Dequeue(srcs);
- /*
- * A file is considered to exist if either a node exists in the
- * graph for it or the file actually exists.
- */
- if (Targ_FindNode(src->file) != NULL) {
#ifdef DEBUG_SRC
- debug_printf("remove from list %p src %p\n", srcs, src);
+ debug_printf("remove from list %p src %p:%s\n",
+ srcs, src, src->file);
#endif
- retsrc = src;
- break;
- }
+ DEBUG1(SUFF, "\ttrying %s...", src->file);
- {
- char *file = Dir_FindFile(src->file, src->suff->searchPath);
- if (file != NULL) {
- retsrc = src;
-#ifdef DEBUG_SRC
- debug_printf("remove from list %p src %p\n", srcs, src);
-#endif
- free(file);
- break;
- }
- }
+ /*
+ * A file is considered to exist if either a node exists in the
+ * graph for it or the file actually exists.
+ */
+ if (Targ_FindNode(src->file) != NULL) {
+ found:
+ HashSet_Done(&seen);
+ DEBUG0(SUFF, "got it\n");
+ return src;
+ }
- SUFF_DEBUG0("not there\n");
+ {
+ char *file = Dir_FindFile(src->file,
+ src->suff->searchPath);
+ if (file != NULL) {
+ free(file);
+ goto found;
+ }
+ }
- SuffAddLevel(srcs, src);
- Lst_Append(slst, src);
- }
+ DEBUG0(SUFF, "not there\n");
- if (retsrc) {
- SUFF_DEBUG0("got it\n");
- }
- return retsrc;
+ if (HashSet_Add(&seen, src->file))
+ CandidateList_AddCandidatesFor(srcs, src);
+ else {
+ DEBUG1(SUFF, "FindThem: skipping duplicate \"%s\"\n",
+ src->file);
+ }
+
+ CandidateSearcher_Add(cs, src);
+ }
+
+ HashSet_Done(&seen);
+ return NULL;
}
-/* See if any of the children of the target in the Src structure is one from
- * which the target can be transformed. If there is one, a Src structure is
- * put together for it and returned.
- *
- * Input:
- * targ Src to play with
- *
- * Results:
- * The Src of the "winning" child, or NULL.
+/*
+ * See if any of the children of the candidate's GNode is one from which the
+ * target can be transformed. If there is one, a candidate is put together
+ * for it and returned.
*/
-static Src *
-SuffFindCmds(Src *targ, SrcList *slst)
-{
- GNodeListNode *gln;
- GNode *tgn; /* Target GNode */
- GNode *sgn; /* Source GNode */
- size_t prefLen; /* The length of the defined prefix */
- Suff *suff; /* Suffix on matching beastie */
- Src *ret; /* Return value */
- char *cp;
-
- tgn = targ->node;
- prefLen = strlen(targ->pref);
-
- for (gln = tgn->children->first; gln != NULL; gln = gln->next) {
- sgn = gln->datum;
-
- if (sgn->type & OP_OPTIONAL && Lst_IsEmpty(tgn->commands)) {
- /*
- * We haven't looked to see if .OPTIONAL files exist yet, so
- * don't use one as the implicit source.
- * This allows us to use .OPTIONAL in .depend files so make won't
- * complain "don't know how to make xxx.h' when a dependent file
- * has been moved/deleted.
- */
- continue;
- }
+static Candidate *
+FindCmds(Candidate *targ, CandidateSearcher *cs)
+{
+ GNodeListNode *gln;
+ GNode *tgn; /* Target GNode */
+ GNode *sgn; /* Source GNode */
+ size_t prefLen; /* The length of the defined prefix */
+ Suffix *suff; /* Suffix on matching beastie */
+ Candidate *ret; /* Return value */
+
+ tgn = targ->node;
+ prefLen = strlen(targ->prefix);
+
+ for (gln = tgn->children.first; gln != NULL; gln = gln->next) {
+ const char *cp;
+
+ sgn = gln->datum;
+
+ if (sgn->type & OP_OPTIONAL && Lst_IsEmpty(&tgn->commands)) {
+ /*
+ * We haven't looked to see if .OPTIONAL files exist
+ * yet, so don't use one as the implicit source.
+ * This allows us to use .OPTIONAL in .depend files so
+ * make won't complain "don't know how to make xxx.h"
+ * when a dependent file has been moved/deleted.
+ */
+ continue;
+ }
- cp = strrchr(sgn->name, '/');
- if (cp == NULL) {
- cp = sgn->name;
- } else {
- cp++;
+ cp = str_basename(sgn->name);
+ if (strncmp(cp, targ->prefix, prefLen) != 0)
+ continue;
+ /* The node matches the prefix, see if it has a known suffix. */
+ suff = FindSuffixByName(cp + prefLen);
+ if (suff == NULL)
+ continue;
+
+ /*
+ * It even has a known suffix, see if there's a transformation
+ * defined between the node's suffix and the target's suffix.
+ *
+ * XXX: Handle multi-stage transformations here, too.
+ */
+
+ if (Lst_FindDatum(&suff->parents, targ->suff) != NULL)
+ break;
}
- if (strncmp(cp, targ->pref, prefLen) != 0)
- continue;
- /* The node matches the prefix ok, see if it has a known suffix. */
- suff = FindSuffByName(cp + prefLen);
- if (suff == NULL)
- continue;
+
+ if (gln == NULL)
+ return NULL;
+
+ ret = Candidate_New(bmake_strdup(sgn->name), targ->prefix, suff, targ,
+ sgn);
+ targ->numChildren++;
+#ifdef DEBUG_SRC
+ debug_printf("3 add targ %p:%s ret %p:%s\n",
+ targ, targ->file, ret, ret->file);
+ Lst_Append(&targ->childrenList, ret);
+#endif
+ CandidateSearcher_Add(cs, ret);
+ DEBUG1(SUFF, "\tusing existing source %s\n", sgn->name);
+ return ret;
+}
+
+static void
+ExpandWildcards(GNodeListNode *cln, GNode *pgn)
+{
+ GNode *cgn = cln->datum;
+ StringList expansions;
+
+ if (!Dir_HasWildcards(cgn->name))
+ return;
/*
- * It even has a known suffix, see if there's a transformation
- * defined between the node's suffix and the target's suffix.
- *
- * XXX: Handle multi-stage transformations here, too.
+ * Expand the word along the chosen path
*/
+ Lst_Init(&expansions);
+ Dir_Expand(cgn->name, Suff_FindPath(cgn), &expansions);
- /* XXX: Can targ->suff be NULL here? */
- if (targ->suff != NULL &&
- Lst_FindDatum(suff->parents, targ->suff) != NULL)
- break;
- }
+ while (!Lst_IsEmpty(&expansions)) {
+ GNode *gn;
+ /*
+ * Fetch next expansion off the list and find its GNode
+ */
+ char *cp = Lst_Dequeue(&expansions);
- if (gln == NULL)
- return NULL;
+ DEBUG1(SUFF, "%s...", cp);
+ gn = Targ_GetNode(cp);
- /*
- * Hot Damn! Create a new Src structure to describe
- * this transformation (making sure to duplicate the
- * source node's name so Suff_FindDeps can free it
- * again (ick)), and return the new structure.
- */
- ret = SrcNew(bmake_strdup(sgn->name), targ->pref, suff, targ, sgn);
- suff->refCount++;
- targ->numChildren++;
-#ifdef DEBUG_SRC
- debug_printf("3 add targ %p ret %p\n", targ, ret);
- Lst_Append(targ->childrenList, ret);
-#endif
- Lst_Append(slst, ret);
- SUFF_DEBUG1("\tusing existing source %s\n", sgn->name);
- return ret;
+ /* Add gn to the parents child list before the original child */
+ Lst_InsertBefore(&pgn->children, cln, gn);
+ Lst_Append(&gn->parents, pgn);
+ pgn->unmade++;
+ }
+
+ Lst_Done(&expansions);
+
+ DEBUG0(SUFF, "\n");
+
+ /*
+ * Now the source is expanded, remove it from the list of children to
+ * keep it from being processed.
+ */
+ pgn->unmade--;
+ Lst_Remove(&pgn->children, cln);
+ Lst_Remove(&cgn->parents, Lst_FindDatum(&cgn->parents, pgn));
}
-/* Expand the names of any children of a given node that contain variable
- * expressions or file wildcards into actual targets.
- *
- * The expanded node is removed from the parent's list of children, and the
- * parent's unmade counter is decremented, but other nodes may be added.
+/*
+ * Break the result into a vector of strings whose nodes we can find, then
+ * add those nodes to the members list.
*
- * Input:
- * cln Child to examine
- * pgn Parent node being processed
+ * Unfortunately, we can't use Str_Words because it doesn't understand about
+ * variable specifications with spaces in them.
*/
static void
-SuffExpandChildren(GNodeListNode *cln, GNode *pgn)
-{
- GNode *cgn = cln->datum;
- GNode *gn; /* New source 8) */
- char *cp; /* Expanded value */
-
- if (!Lst_IsEmpty(cgn->order_pred) || !Lst_IsEmpty(cgn->order_succ))
- /* It is all too hard to process the result of .ORDER */
- return;
-
- if (cgn->type & OP_WAIT)
- /* Ignore these (& OP_PHONY ?) */
- return;
-
- /*
- * First do variable expansion -- this takes precedence over
- * wildcard expansion. If the result contains wildcards, they'll be gotten
- * to later since the resulting words are tacked on to the end of
- * the children list.
- */
- if (strchr(cgn->name, '$') == NULL) {
- SuffExpandWildcards(cln, pgn);
- return;
- }
-
- SUFF_DEBUG1("Expanding \"%s\"...", cgn->name);
- (void)Var_Subst(cgn->name, pgn, VARE_WANTRES | VARE_UNDEFERR, &cp);
- /* TODO: handle errors */
-
- {
- GNodeList *members = Lst_New();
-
- if (cgn->type & OP_ARCHV) {
- /*
- * Node was an archive(member) target, so we want to call
- * on the Arch module to find the nodes for us, expanding
- * variables in the parent's context.
- */
- char *sacrifice = cp;
-
- (void)Arch_ParseArchive(&sacrifice, members, pgn);
- } else {
- /*
- * Break the result into a vector of strings whose nodes
- * we can find, then add those nodes to the members list.
- * Unfortunately, we can't use Str_Words because it
- * doesn't understand about variable specifications with
- * spaces in them...
- */
- char *start;
- char *initcp = cp; /* For freeing... */
-
- start = cp;
- pp_skip_hspace(&start);
- cp = start;
- while (*cp != '\0') {
+ExpandChildrenRegular(char *cp, GNode *pgn, GNodeList *members)
+{
+ char *start;
+
+ pp_skip_hspace(&cp);
+ start = cp;
+ while (*cp != '\0') {
if (*cp == ' ' || *cp == '\t') {
- /*
- * White-space -- terminate element, find the node,
- * add it, skip any further spaces.
- */
- *cp++ = '\0';
- gn = Targ_GetNode(start);
- Lst_Append(members, gn);
- pp_skip_hspace(&cp);
- start = cp; /* Continue at the next non-space. */
+ GNode *gn;
+ /*
+ * White-space -- terminate element, find the node,
+ * add it, skip any further spaces.
+ */
+ *cp++ = '\0';
+ gn = Targ_GetNode(start);
+ Lst_Append(members, gn);
+ pp_skip_hspace(&cp);
+ /* Continue at the next non-space. */
+ start = cp;
} else if (*cp == '$') {
- /*
- * Start of a variable spec -- contact variable module
- * to find the end so we can skip over it.
- */
- const char *nested_p = cp;
- const char *junk;
- void *freeIt;
-
- /* XXX: Why VARE_WANTRES when the result is not used? */
- (void)Var_Parse(&nested_p, pgn,
- VARE_WANTRES | VARE_UNDEFERR,
- &junk, &freeIt);
- /* TODO: handle errors */
- if (junk == var_Error) {
- Parse_Error(PARSE_FATAL,
+ /* Skip over the variable expression. */
+ const char *nested_p = cp;
+ FStr junk;
+
+ (void)Var_Parse(&nested_p, pgn, VARE_NONE, &junk);
+ /* TODO: handle errors */
+ if (junk.str == var_Error) {
+ Parse_Error(PARSE_FATAL,
"Malformed variable expression at \"%s\"",
cp);
- cp++;
- } else {
- cp += nested_p - cp;
- }
+ cp++;
+ } else {
+ cp += nested_p - cp;
+ }
- free(freeIt);
+ FStr_Done(&junk);
} else if (cp[0] == '\\' && cp[1] != '\0') {
- /*
- * Escaped something -- skip over it
- */
- /* XXX: In other places, escaping at this syntactical
- * position is done by a '$', not a '\'. The '\' is only
- * used in variable modifiers. */
- cp += 2;
+ /* Escaped something -- skip over it. */
+ /*
+ * XXX: In other places, escaping at this syntactical
+ * position is done by a '$', not a '\'. The '\' is
+ * only used in variable modifiers.
+ */
+ cp += 2;
} else {
- cp++;
+ cp++;
}
- }
+ }
- if (cp != start) {
+ if (cp != start) {
/*
* Stuff left over -- add it to the list too
*/
- gn = Targ_GetNode(start);
+ GNode *gn = Targ_GetNode(start);
Lst_Append(members, gn);
- }
- /*
- * Point cp back at the beginning again so the variable value
- * can be freed.
- */
- cp = initcp;
- }
-
- /*
- * Add all elements of the members list to the parent node.
- */
- while(!Lst_IsEmpty(members)) {
- gn = Lst_Dequeue(members);
-
- SUFF_DEBUG1("%s...", gn->name);
- /* Add gn to the parents child list before the original child */
- Lst_InsertBefore(pgn->children, cln, gn);
- Lst_Append(gn->parents, pgn);
- pgn->unmade++;
- /* Expand wildcards on new node */
- SuffExpandWildcards(cln->prev, pgn);
}
- Lst_Free(members);
-
- /*
- * Free the result
- */
- free(cp);
- }
-
- SUFF_DEBUG0("\n");
-
- /*
- * Now the source is expanded, remove it from the list of children to
- * keep it from being processed.
- */
- pgn->unmade--;
- Lst_Remove(pgn->children, cln);
- Lst_Remove(cgn->parents, Lst_FindDatum(cgn->parents, pgn));
}
+/*
+ * Expand the names of any children of a given node that contain variable
+ * expressions or file wildcards into actual targets.
+ *
+ * The expanded node is removed from the parent's list of children, and the
+ * parent's unmade counter is decremented, but other nodes may be added.
+ *
+ * Input:
+ * cln Child to examine
+ * pgn Parent node being processed
+ */
static void
-SuffExpandWildcards(GNodeListNode *cln, GNode *pgn)
+ExpandChildren(GNodeListNode *cln, GNode *pgn)
{
- GNode *cgn = cln->datum;
- StringList *expansions;
+ GNode *cgn = cln->datum;
+ char *cp; /* Expanded value */
- if (!Dir_HasWildcards(cgn->name))
- return;
+ if (!Lst_IsEmpty(&cgn->order_pred) || !Lst_IsEmpty(&cgn->order_succ))
+ /* It is all too hard to process the result of .ORDER */
+ return;
- /*
- * Expand the word along the chosen path
- */
- expansions = Lst_New();
- Dir_Expand(cgn->name, Suff_FindPath(cgn), expansions);
+ if (cgn->type & OP_WAIT)
+ /* Ignore these (& OP_PHONY ?) */
+ return;
- while (!Lst_IsEmpty(expansions)) {
- GNode *gn;
/*
- * Fetch next expansion off the list and find its GNode
+ * First do variable expansion -- this takes precedence over wildcard
+ * expansion. If the result contains wildcards, they'll be gotten to
+ * later since the resulting words are tacked on to the end of the
+ * children list.
*/
- char *cp = Lst_Dequeue(expansions);
+ if (strchr(cgn->name, '$') == NULL) {
+ ExpandWildcards(cln, pgn);
+ return;
+ }
- SUFF_DEBUG1("%s...", cp);
- gn = Targ_GetNode(cp);
+ DEBUG1(SUFF, "Expanding \"%s\"...", cgn->name);
+ (void)Var_Subst(cgn->name, pgn, VARE_WANTRES | VARE_UNDEFERR, &cp);
+ /* TODO: handle errors */
- /* Add gn to the parents child list before the original child */
- Lst_InsertBefore(pgn->children, cln, gn);
- Lst_Append(gn->parents, pgn);
- pgn->unmade++;
- }
+ {
+ GNodeList members = LST_INIT;
+
+ if (cgn->type & OP_ARCHV) {
+ /*
+ * Node was an archive(member) target, so we want to
+ * call on the Arch module to find the nodes for us,
+ * expanding variables in the parent's context.
+ */
+ char *p = cp;
+ (void)Arch_ParseArchive(&p, &members, pgn);
+ } else {
+ ExpandChildrenRegular(cp, pgn, &members);
+ }
- Lst_Free(expansions);
+ /*
+ * Add all elements of the members list to the parent node.
+ */
+ while (!Lst_IsEmpty(&members)) {
+ GNode *gn = Lst_Dequeue(&members);
+
+ DEBUG1(SUFF, "%s...", gn->name);
+ /*
+ * Add gn to the parents child list before the
+ * original child.
+ */
+ Lst_InsertBefore(&pgn->children, cln, gn);
+ Lst_Append(&gn->parents, pgn);
+ pgn->unmade++;
+ /* Expand wildcards on new node */
+ ExpandWildcards(cln->prev, pgn);
+ }
+ Lst_Done(&members);
- SUFF_DEBUG0("\n");
+ free(cp);
+ }
+
+ DEBUG0(SUFF, "\n");
- /*
- * Now the source is expanded, remove it from the list of children to
- * keep it from being processed.
- */
- pgn->unmade--;
- Lst_Remove(pgn->children, cln);
- Lst_Remove(cgn->parents, Lst_FindDatum(cgn->parents, pgn));
+ /*
+ * Now the source is expanded, remove it from the list of children to
+ * keep it from being processed.
+ */
+ pgn->unmade--;
+ Lst_Remove(&pgn->children, cln);
+ Lst_Remove(&cgn->parents, Lst_FindDatum(&cgn->parents, pgn));
+}
+
+static void
+ExpandAllChildren(GNode *gn)
+{
+ GNodeListNode *ln, *nln;
+
+ for (ln = gn->children.first; ln != NULL; ln = nln) {
+ nln = ln->next;
+ ExpandChildren(ln, gn);
+ }
}
-/* Find a path along which to expand the node.
+/*
+ * Find a path along which to expand the node.
*
* If the node has a known suffix, use that path.
* If it has no known suffix, use the default system search path.
@@ -1280,34 +1455,38 @@ SuffExpandWildcards(GNodeListNode *cln, GNode *pgn)
* The appropriate path to search for the GNode.
*/
SearchPath *
-Suff_FindPath(GNode* gn)
-{
- Suff *suff = gn->suffix;
-
- if (suff == NULL) {
- char *name = gn->name;
- size_t nameLen = strlen(gn->name);
- SuffListNode *ln;
- for (ln = sufflist->first; ln != NULL; ln = ln->next)
- if (SuffSuffIsSuffix(ln->datum, nameLen, name + nameLen))
- break;
-
- SUFF_DEBUG1("Wildcard expanding \"%s\"...", gn->name);
- if (ln != NULL)
- suff = ln->datum;
- /* XXX: Here we can save the suffix so we don't have to do this again */
- }
-
- if (suff != NULL) {
- SUFF_DEBUG1("suffix is \"%s\"...\n", suff->name);
- return suff->searchPath;
- } else {
- SUFF_DEBUG0("\n");
- return dirSearchPath; /* Use default search path */
- }
-}
-
-/* Apply a transformation rule, given the source and target nodes and
+Suff_FindPath(GNode *gn)
+{
+ Suffix *suff = gn->suffix;
+
+ if (suff == NULL) {
+ char *name = gn->name;
+ size_t nameLen = strlen(gn->name);
+ SuffixListNode *ln;
+ for (ln = sufflist.first; ln != NULL; ln = ln->next)
+ if (Suffix_IsSuffix(ln->datum, nameLen, name + nameLen))
+ break;
+
+ DEBUG1(SUFF, "Wildcard expanding \"%s\"...", gn->name);
+ if (ln != NULL)
+ suff = ln->datum;
+ /*
+ * XXX: Here we can save the suffix so we don't have to do
+ * this again.
+ */
+ }
+
+ if (suff != NULL) {
+ DEBUG1(SUFF, "suffix is \"%s\"...\n", suff->name);
+ return suff->searchPath;
+ } else {
+ DEBUG0(SUFF, "\n");
+ return &dirSearchPath; /* Use default search path */
+ }
+}
+
+/*
+ * Apply a transformation rule, given the source and target nodes and
* suffixes.
*
* The source and target are linked and the commands from the transformation
@@ -1318,59 +1497,84 @@ Suff_FindPath(GNode* gn)
* TRUE if successful, FALSE if not.
*/
static Boolean
-SuffApplyTransform(GNode *tgn, GNode *sgn, Suff *tsuff, Suff *ssuff)
-{
- GNodeListNode *ln;
- char *tname; /* Name of transformation rule */
- GNode *gn; /* Node for same */
-
- /*
- * Form the proper links between the target and source.
- */
- Lst_Append(tgn->children, sgn);
- Lst_Append(sgn->parents, tgn);
- tgn->unmade++;
-
- /*
- * Locate the transformation rule itself
- */
- tname = str_concat2(ssuff->name, tsuff->name);
- gn = FindTransformByName(tname);
- free(tname);
-
- if (gn == NULL) {
+ApplyTransform(GNode *tgn, GNode *sgn, Suffix *tsuff, Suffix *ssuff)
+{
+ GNodeListNode *ln;
+ char *tname; /* Name of transformation rule */
+ GNode *gn; /* Node for the transformation rule */
+
+ /* Form the proper links between the target and source. */
+ Lst_Append(&tgn->children, sgn);
+ Lst_Append(&sgn->parents, tgn);
+ tgn->unmade++;
+
+ /* Locate the transformation rule itself. */
+ tname = str_concat2(ssuff->name, tsuff->name);
+ gn = FindTransformByName(tname);
+ free(tname);
+
/* This can happen when linking an OP_MEMBER and OP_ARCHV node. */
- return FALSE;
- }
+ if (gn == NULL)
+ return FALSE;
- DEBUG3(SUFF,"\tapplying %s -> %s to \"%s\"\n",
- ssuff->name, tsuff->name, tgn->name);
+ DEBUG3(SUFF, "\tapplying %s -> %s to \"%s\"\n",
+ ssuff->name, tsuff->name, tgn->name);
- /* Record last child; Make_HandleUse may add child nodes. */
- ln = tgn->children->last;
+ /* Record last child; Make_HandleUse may add child nodes. */
+ ln = tgn->children.last;
- /* Apply the rule. */
- Make_HandleUse(gn, tgn);
+ /* Apply the rule. */
+ Make_HandleUse(gn, tgn);
- /* Deal with wildcards and variables in any acquired sources. */
- ln = ln != NULL ? ln->next : NULL;
- while (ln != NULL) {
- GNodeListNode *nln = ln->next;
- SuffExpandChildren(ln, tgn);
- ln = nln;
- }
+ /* Deal with wildcards and variables in any acquired sources. */
+ ln = ln != NULL ? ln->next : NULL;
+ while (ln != NULL) {
+ GNodeListNode *nln = ln->next;
+ ExpandChildren(ln, tgn);
+ ln = nln;
+ }
- /*
- * Keep track of another parent to which this node is transformed so
- * the .IMPSRC variable can be set correctly for the parent.
- */
- Lst_Append(sgn->implicitParents, tgn);
+ /*
+ * Keep track of another parent to which this node is transformed so
+ * the .IMPSRC variable can be set correctly for the parent.
+ */
+ Lst_Append(&sgn->implicitParents, tgn);
- return TRUE;
+ return TRUE;
}
+/*
+ * Member has a known suffix, so look for a transformation rule from
+ * it to a possible suffix of the archive.
+ *
+ * Rather than searching through the entire list, we just look at
+ * suffixes to which the member's suffix may be transformed.
+ */
+static void
+ExpandMember(GNode *gn, const char *eoarch, GNode *mem, Suffix *memSuff)
+{
+ GNodeListNode *ln;
+ size_t nameLen = (size_t)(eoarch - gn->name);
+
+ /* Use first matching suffix... */
+ for (ln = memSuff->parents.first; ln != NULL; ln = ln->next)
+ if (Suffix_IsSuffix(ln->datum, nameLen, eoarch))
+ break;
-/* Locate dependencies for an OP_ARCHV node.
+ if (ln != NULL) {
+ /* Got one -- apply it */
+ Suffix *suff = ln->datum;
+ if (!ApplyTransform(gn, mem, suff, memSuff)) {
+ DEBUG2(SUFF, "\tNo transformation from %s -> %s\n",
+ memSuff->name, suff->name);
+ }
+ }
+}
+
+static void FindDeps(GNode *, CandidateSearcher *);
+
+/*
+ * Locate dependencies for an OP_ARCHV node.
*
* Input:
* gn Node for which to locate dependencies
@@ -1379,258 +1583,229 @@ SuffApplyTransform(GNode *tgn, GNode *sgn, Suff *tsuff, Suff *ssuff)
* Same as Suff_FindDeps
*/
static void
-SuffFindArchiveDeps(GNode *gn, SrcList *slst)
-{
- char *eoarch; /* End of archive portion */
- char *eoname; /* End of member portion */
- GNode *mem; /* Node for member */
- SuffListNode *ln, *nln; /* Next suffix node to check */
- Suff *ms; /* Suffix descriptor for member */
- char *name; /* Start of member's name */
-
- /*
- * The node is an archive(member) pair. so we must find a
- * suffix for both of them.
- */
- eoarch = strchr(gn->name, '(');
- eoname = strchr(eoarch, ')');
-
- /*
- * Caller guarantees the format `libname(member)', via
- * Arch_ParseArchive.
- */
- assert(eoarch != NULL);
- assert(eoname != NULL);
-
- *eoname = '\0'; /* Nuke parentheses during suffix search */
- *eoarch = '\0'; /* So a suffix can be found */
-
- name = eoarch + 1;
-
- /*
- * To simplify things, call Suff_FindDeps recursively on the member now,
- * so we can simply compare the member's .PREFIX and .TARGET variables
- * to locate its suffix. This allows us to figure out the suffix to
- * use for the archive without having to do a quadratic search over the
- * suffix list, backtracking for each one...
- */
- mem = Targ_GetNode(name);
- SuffFindDeps(mem, slst);
-
- /*
- * Create the link between the two nodes right off
- */
- Lst_Append(gn->children, mem);
- Lst_Append(mem->parents, gn);
- gn->unmade++;
-
- /*
- * Copy in the variables from the member node to this one.
- */
- Var_Set(PREFIX, GNode_VarPrefix(mem), gn);
- Var_Set(TARGET, GNode_VarTarget(mem), gn);
-
- ms = mem->suffix;
- if (ms == NULL) { /* Didn't know what it was. */
- SUFF_DEBUG0("using null suffix\n");
- ms = suffNull;
- }
-
-
- /*
- * Set the other two local variables required for this target.
- */
- Var_Set(MEMBER, name, gn);
- Var_Set(ARCHIVE, gn->name, gn);
-
- /*
- * Set $@ for compatibility with other makes
- */
- Var_Set(TARGET, gn->name, gn);
-
- /*
- * Now we've got the important local variables set, expand any sources
- * that still contain variables or wildcards in their names.
- */
- for (ln = gn->children->first; ln != NULL; ln = nln) {
- nln = ln->next;
- SuffExpandChildren(ln, gn);
- }
-
- if (ms != NULL) {
+FindDepsArchive(GNode *gn, CandidateSearcher *cs)
+{
+ char *eoarch; /* End of archive portion */
+ char *eoname; /* End of member portion */
+ GNode *mem; /* Node for member */
+ Suffix *memSuff;
+ const char *name; /* Start of member's name */
+
/*
- * Member has a known suffix, so look for a transformation rule from
- * it to a possible suffix of the archive. Rather than searching
- * through the entire list, we just look at suffixes to which the
- * member's suffix may be transformed...
+ * The node is an archive(member) pair. so we must find a
+ * suffix for both of them.
*/
- size_t nameLen = (size_t)(eoarch - gn->name);
+ eoarch = strchr(gn->name, '(');
+ eoname = strchr(eoarch, ')');
- /* Use first matching suffix... */
- for (ln = ms->parents->first; ln != NULL; ln = ln->next)
- if (SuffSuffIsSuffix(ln->datum, nameLen, eoarch))
- break;
+ /*
+ * Caller guarantees the format `libname(member)', via
+ * Arch_ParseArchive.
+ */
+ assert(eoarch != NULL);
+ assert(eoname != NULL);
- if (ln != NULL) {
- /*
- * Got one -- apply it
- */
- Suff *suff = ln->datum;
- if (!SuffApplyTransform(gn, mem, suff, ms)) {
- SUFF_DEBUG2("\tNo transformation from %s -> %s\n",
- ms->name, suff->name);
- }
+ *eoname = '\0'; /* Nuke parentheses during suffix search */
+ *eoarch = '\0'; /* So a suffix can be found */
+
+ name = eoarch + 1;
+
+ /*
+ * To simplify things, call Suff_FindDeps recursively on the member
+ * now, so we can simply compare the member's .PREFIX and .TARGET
+ * variables to locate its suffix. This allows us to figure out the
+ * suffix to use for the archive without having to do a quadratic
+ * search over the suffix list, backtracking for each one.
+ */
+ mem = Targ_GetNode(name);
+ FindDeps(mem, cs);
+
+ /* Create the link between the two nodes right off. */
+ Lst_Append(&gn->children, mem);
+ Lst_Append(&mem->parents, gn);
+ gn->unmade++;
+
+ /* Copy in the variables from the member node to this one. */
+ Var_Set(PREFIX, GNode_VarPrefix(mem), gn);
+ Var_Set(TARGET, GNode_VarTarget(mem), gn);
+
+ memSuff = mem->suffix;
+ if (memSuff == NULL) { /* Didn't know what it was. */
+ DEBUG0(SUFF, "using null suffix\n");
+ memSuff = nullSuff;
}
- }
-
- /*
- * Replace the opening and closing parens now we've no need of the separate
- * pieces.
- */
- *eoarch = '(';
- *eoname = ')';
-
- /*
- * Pretend gn appeared to the left of a dependency operator so
- * the user needn't provide a transformation from the member to the
- * archive.
- */
- if (!GNode_IsTarget(gn))
- gn->type |= OP_DEPENDS;
-
- /*
- * Flag the member as such so we remember to look in the archive for
- * its modification time. The OP_JOIN | OP_MADE is needed because this
- * target should never get made.
- */
- mem->type |= OP_MEMBER | OP_JOIN | OP_MADE;
-}
-static void
-SuffFindNormalDepsKnown(const char *name, size_t nameLen, GNode *gn,
- SrcList *srcs, SrcList *targs)
-{
- SuffListNode *ln;
- Src *targ;
- char *pref;
- for (ln = sufflist->first; ln != NULL; ln = ln->next) {
- Suff *suff = ln->datum;
- if (!SuffSuffIsSuffix(suff, nameLen, name + nameLen))
- continue;
+ /* Set the other two local variables required for this target. */
+ Var_Set(MEMBER, name, gn);
+ Var_Set(ARCHIVE, gn->name, gn);
+ /* Set $@ for compatibility with other makes. */
+ Var_Set(TARGET, gn->name, gn);
- pref = bmake_strldup(name, (size_t)(nameLen - suff->nameLen));
- targ = SrcNew(bmake_strdup(gn->name), pref, suff, NULL, gn);
- suff->refCount++;
+ /*
+ * Now we've got the important local variables set, expand any sources
+ * that still contain variables or wildcards in their names.
+ */
+ ExpandAllChildren(gn);
+
+ if (memSuff != NULL)
+ ExpandMember(gn, eoarch, mem, memSuff);
/*
- * Add nodes from which the target can be made
+ * Replace the opening and closing parens now we've no need of the
+ * separate pieces.
*/
- SuffAddLevel(srcs, targ);
+ *eoarch = '(';
+ *eoname = ')';
/*
- * Record the target so we can nuke it
+ * Pretend gn appeared to the left of a dependency operator so the
+ * user needn't provide a transformation from the member to the
+ * archive.
*/
- Lst_Append(targs, targ);
- }
+ if (!GNode_IsTarget(gn))
+ gn->type |= OP_DEPENDS;
+
+ /*
+ * Flag the member as such so we remember to look in the archive for
+ * its modification time. The OP_JOIN | OP_MADE is needed because
+ * this target should never get made.
+ */
+ mem->type |= OP_MEMBER | OP_JOIN | OP_MADE;
}
+/*
+ * If the node is a library, it is the arch module's job to find it
+ * and set the TARGET variable accordingly. We merely provide the
+ * search path, assuming all libraries end in ".a" (if the suffix
+ * hasn't been defined, there's nothing we can do for it, so we just
+ * set the TARGET variable to the node's name in order to give it a
+ * value).
+ */
static void
-SuffFindNormalDepsUnknown(GNode *gn, const char *sopref,
- SrcList *srcs, SrcList *targs)
+FindDepsLib(GNode *gn)
{
- Src *targ;
+ Suffix *suff = FindSuffixByName(LIBSUFF);
+ if (suff != NULL) {
+ Suffix_Reassign(&gn->suffix, suff);
+ Arch_FindLib(gn, suff->searchPath);
+ } else {
+ Suffix_Unassign(&gn->suffix);
+ Var_Set(TARGET, gn->name, gn);
+ }
- if (!Lst_IsEmpty(targs) || suffNull == NULL)
- return;
+ /*
+ * Because a library (-lfoo) target doesn't follow the standard
+ * filesystem conventions, we don't set the regular variables for
+ * the thing. .PREFIX is simply made empty.
+ */
+ Var_Set(PREFIX, "", gn);
+}
- SUFF_DEBUG1("\tNo known suffix on %s. Using .NULL suffix\n", gn->name);
+static void
+FindDepsRegularKnown(const char *name, size_t nameLen, GNode *gn,
+ CandidateList *srcs, CandidateList *targs)
+{
+ SuffixListNode *ln;
+ Candidate *targ;
+ char *pref;
- targ = SrcNew(bmake_strdup(gn->name), bmake_strdup(sopref),
- suffNull, NULL, gn);
- targ->suff->refCount++;
+ for (ln = sufflist.first; ln != NULL; ln = ln->next) {
+ Suffix *suff = ln->datum;
+ if (!Suffix_IsSuffix(suff, nameLen, name + nameLen))
+ continue;
- /*
- * Only use the default suffix rules if we don't have commands
- * defined for this gnode; traditional make programs used to
- * not define suffix rules if the gnode had children but we
- * don't do this anymore.
- */
- if (Lst_IsEmpty(gn->commands))
- SuffAddLevel(srcs, targ);
- else {
- SUFF_DEBUG0("not ");
- }
+ pref = bmake_strldup(name, (size_t)(nameLen - suff->nameLen));
+ targ = Candidate_New(bmake_strdup(gn->name), pref, suff, NULL,
+ gn);
- SUFF_DEBUG0("adding suffix rules\n");
+ CandidateList_AddCandidatesFor(srcs, targ);
- Lst_Append(targs, targ);
+ /* Record the target so we can nuke it. */
+ Lst_Append(targs, targ);
+ }
}
-/*
- * Deal with finding the thing on the default search path. We
- * always do that, not only if the node is only a source (not
- * on the lhs of a dependency operator or [XXX] it has neither
- * children or commands) as the old pmake did.
- */
static void
-SuffFindNormalDepsPath(GNode *gn, Src *targ)
+FindDepsRegularUnknown(GNode *gn, const char *sopref,
+ CandidateList *srcs, CandidateList *targs)
{
- if (gn->type & (OP_PHONY | OP_NOPATH))
- return;
+ Candidate *targ;
+
+ if (!Lst_IsEmpty(targs) || nullSuff == NULL)
+ return;
- free(gn->path);
- gn->path = Dir_FindFile(gn->name,
- (targ == NULL ? dirSearchPath :
- targ->suff->searchPath));
- if (gn->path == NULL)
- return;
+ DEBUG1(SUFF, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name);
- Var_Set(TARGET, gn->path, gn);
+ targ = Candidate_New(bmake_strdup(gn->name), bmake_strdup(sopref),
+ nullSuff, NULL, gn);
- if (targ != NULL) {
/*
- * Suffix known for the thing -- trim the suffix off
- * the path to form the proper .PREFIX variable.
+ * Only use the default suffix rules if we don't have commands
+ * defined for this gnode; traditional make programs used to not
+ * define suffix rules if the gnode had children but we don't do
+ * this anymore.
*/
- size_t savep = strlen(gn->path) - targ->suff->nameLen;
- char savec;
- char *ptr;
+ if (Lst_IsEmpty(&gn->commands))
+ CandidateList_AddCandidatesFor(srcs, targ);
+ else {
+ DEBUG0(SUFF, "not ");
+ }
- if (gn->suffix)
- gn->suffix->refCount--;
- gn->suffix = targ->suff;
- gn->suffix->refCount++;
+ DEBUG0(SUFF, "adding suffix rules\n");
- savec = gn->path[savep];
- gn->path[savep] = '\0';
+ Lst_Append(targs, targ);
+}
- if ((ptr = strrchr(gn->path, '/')) != NULL)
- ptr++;
- else
- ptr = gn->path;
+/*
+ * Deal with finding the thing on the default search path. We always do
+ * that, not only if the node is only a source (not on the lhs of a
+ * dependency operator or [XXX] it has neither children or commands) as
+ * the old pmake did.
+ */
+static void
+FindDepsRegularPath(GNode *gn, Candidate *targ)
+{
+ if (gn->type & (OP_PHONY | OP_NOPATH))
+ return;
- Var_Set(PREFIX, ptr, gn);
+ free(gn->path);
+ gn->path = Dir_FindFile(gn->name,
+ (targ == NULL ? &dirSearchPath :
+ targ->suff->searchPath));
+ if (gn->path == NULL)
+ return;
- gn->path[savep] = savec;
- } else {
- char *ptr;
+ Var_Set(TARGET, gn->path, gn);
- /* The .PREFIX gets the full path if the target has no known suffix. */
- if (gn->suffix)
- gn->suffix->refCount--;
- gn->suffix = NULL;
+ if (targ != NULL) {
+ /*
+ * Suffix known for the thing -- trim the suffix off
+ * the path to form the proper .PREFIX variable.
+ */
+ size_t savep = strlen(gn->path) - targ->suff->nameLen;
+ char savec;
- if ((ptr = strrchr(gn->path, '/')) != NULL)
- ptr++;
- else
- ptr = gn->path;
+ Suffix_Reassign(&gn->suffix, targ->suff);
+
+ savec = gn->path[savep];
+ gn->path[savep] = '\0';
- Var_Set(PREFIX, ptr, gn);
- }
+ Var_Set(PREFIX, str_basename(gn->path), gn);
+
+ gn->path[savep] = savec;
+ } else {
+ /*
+ * The .PREFIX gets the full path if the target has no
+ * known suffix.
+ */
+ Suffix_Unassign(&gn->suffix);
+ Var_Set(PREFIX, str_basename(gn->path), gn);
+ }
}
-/* Locate implicit dependencies for regular targets.
+/*
+ * Locate implicit dependencies for regular targets.
*
* Input:
* gn Node for which to find sources
@@ -1639,211 +1814,213 @@ SuffFindNormalDepsPath(GNode *gn, Src *targ)
* Same as Suff_FindDeps
*/
static void
-SuffFindNormalDeps(GNode *gn, SrcList *slst)
-{
- SrcList *srcs; /* List of sources at which to look */
- SrcList *targs; /* List of targets to which things can be
- * transformed. They all have the same file,
- * but different suff and pref fields */
- Src *bottom; /* Start of found transformation path */
- Src *src; /* General Src pointer */
- char *pref; /* Prefix to use */
- Src *targ; /* General Src target pointer */
-
- const char *name = gn->name;
- size_t nameLen = strlen(name);
-
- /*
- * Begin at the beginning...
- */
- srcs = Lst_New();
- targs = Lst_New();
-
- /*
- * We're caught in a catch-22 here. On the one hand, we want to use any
- * transformation implied by the target's sources, but we can't examine
- * the sources until we've expanded any variables/wildcards they may hold,
- * and we can't do that until we've set up the target's local variables
- * and we can't do that until we know what the proper suffix for the
- * target is (in case there are two suffixes one of which is a suffix of
- * the other) and we can't know that until we've found its implied
- * source, which we may not want to use if there's an existing source
- * that implies a different transformation.
- *
- * In an attempt to get around this, which may not work all the time,
- * but should work most of the time, we look for implied sources first,
- * checking transformations to all possible suffixes of the target,
- * use what we find to set the target's local variables, expand the
- * children, then look for any overriding transformations they imply.
- * Should we find one, we discard the one we found before.
- */
- bottom = NULL;
- targ = NULL;
-
- if (!(gn->type & OP_PHONY)) {
-
- SuffFindNormalDepsKnown(name, nameLen, gn, srcs, targs);
-
- /* Handle target of unknown suffix... */
- SuffFindNormalDepsUnknown(gn, name, srcs, targs);
+FindDepsRegular(GNode *gn, CandidateSearcher *cs)
+{
+ /* List of sources at which to look */
+ CandidateList srcs = LST_INIT;
+ /*
+ * List of targets to which things can be transformed.
+ * They all have the same file, but different suff and prefix fields.
+ */
+ CandidateList targs = LST_INIT;
+ Candidate *bottom; /* Start of found transformation path */
+ Candidate *src;
+ Candidate *targ;
+
+ const char *name = gn->name;
+ size_t nameLen = strlen(name);
+
+#ifdef DEBUG_SRC
+ DEBUG1(SUFF, "FindDepsRegular \"%s\"\n", gn->name);
+#endif
/*
- * Using the list of possible sources built up from the target
- * suffix(es), try and find an existing file/target that matches.
+ * We're caught in a catch-22 here. On the one hand, we want to use
+ * any transformation implied by the target's sources, but we can't
+ * examine the sources until we've expanded any variables/wildcards
+ * they may hold, and we can't do that until we've set up the
+ * target's local variables and we can't do that until we know what
+ * the proper suffix for the target is (in case there are two
+ * suffixes one of which is a suffix of the other) and we can't know
+ * that until we've found its implied source, which we may not want
+ * to use if there's an existing source that implies a different
+ * transformation.
+ *
+ * In an attempt to get around this, which may not work all the time,
+ * but should work most of the time, we look for implied sources
+ * first, checking transformations to all possible suffixes of the
+ * target, use what we find to set the target's local variables,
+ * expand the children, then look for any overriding transformations
+ * they imply. Should we find one, we discard the one we found before.
*/
- bottom = SuffFindThem(srcs, slst);
+ bottom = NULL;
+ targ = NULL;
- if (bottom == NULL) {
- /*
- * No known transformations -- use the first suffix found
- * for setting the local variables.
- */
- if (targs->first != NULL)
- targ = targs->first->datum;
- else
- targ = NULL;
- } else {
- /*
- * Work up the transformation path to find the suffix of the
- * target to which the transformation was made.
- */
- for (targ = bottom; targ->parent != NULL; targ = targ->parent)
- continue;
+ if (!(gn->type & OP_PHONY)) {
+
+ FindDepsRegularKnown(name, nameLen, gn, &srcs, &targs);
+
+ /* Handle target of unknown suffix... */
+ FindDepsRegularUnknown(gn, name, &srcs, &targs);
+
+ /*
+ * Using the list of possible sources built up from the target
+ * suffix(es), try and find an existing file/target that
+ * matches.
+ */
+ bottom = FindThem(&srcs, cs);
+
+ if (bottom == NULL) {
+ /*
+ * No known transformations -- use the first suffix
+ * found for setting the local variables.
+ */
+ if (targs.first != NULL)
+ targ = targs.first->datum;
+ else
+ targ = NULL;
+ } else {
+ /*
+ * Work up the transformation path to find the suffix
+ * of the target to which the transformation was made.
+ */
+ for (targ = bottom;
+ targ->parent != NULL; targ = targ->parent)
+ continue;
+ }
}
- }
-
- Var_Set(TARGET, GNode_Path(gn), gn);
-
- pref = targ != NULL ? targ->pref : gn->name;
- Var_Set(PREFIX, pref, gn);
-
- /*
- * Now we've got the important local variables set, expand any sources
- * that still contain variables or wildcards in their names.
- */
- {
- SuffListNode *ln, *nln;
- for (ln = gn->children->first; ln != NULL; ln = nln) {
- nln = ln->next;
- SuffExpandChildren(ln, gn);
+
+ Var_Set(TARGET, GNode_Path(gn), gn);
+ Var_Set(PREFIX, targ != NULL ? targ->prefix : gn->name, gn);
+
+ /*
+ * Now we've got the important local variables set, expand any sources
+ * that still contain variables or wildcards in their names.
+ */
+ {
+ GNodeListNode *ln, *nln;
+ for (ln = gn->children.first; ln != NULL; ln = nln) {
+ nln = ln->next;
+ ExpandChildren(ln, gn);
+ }
+ }
+
+ if (targ == NULL) {
+ DEBUG1(SUFF, "\tNo valid suffix on %s\n", gn->name);
+
+ sfnd_abort:
+ FindDepsRegularPath(gn, targ);
+ goto sfnd_return;
+ }
+
+ /*
+ * If the suffix indicates that the target is a library, mark that in
+ * the node's type field.
+ */
+ if (targ->suff->flags & SUFF_LIBRARY)
+ gn->type |= OP_LIB;
+
+ /*
+ * Check for overriding transformation rule implied by sources
+ */
+ if (!Lst_IsEmpty(&gn->children)) {
+ src = FindCmds(targ, cs);
+
+ if (src != NULL) {
+ /*
+ * Free up all the candidates in the transformation
+ * path, up to but not including the parent node.
+ */
+ while (bottom != NULL && bottom->parent != NULL) {
+ CandidateSearcher_AddIfNew(cs, bottom);
+ bottom = bottom->parent;
+ }
+ bottom = src;
+ }
}
- }
-
- if (targ == NULL) {
- SUFF_DEBUG1("\tNo valid suffix on %s\n", gn->name);
-
-sfnd_abort:
- SuffFindNormalDepsPath(gn, targ);
- goto sfnd_return;
- }
-
- /*
- * If the suffix indicates that the target is a library, mark that in
- * the node's type field.
- */
- if (targ->suff->flags & SUFF_LIBRARY)
- gn->type |= OP_LIB;
-
- /*
- * Check for overriding transformation rule implied by sources
- */
- if (!Lst_IsEmpty(gn->children)) {
- src = SuffFindCmds(targ, slst);
-
- if (src != NULL) {
- /*
- * Free up all the Src structures in the transformation path
- * up to, but not including, the parent node.
- */
- while (bottom != NULL && bottom->parent != NULL) {
- if (Lst_FindDatum(slst, bottom) == NULL)
- Lst_Append(slst, bottom);
- bottom = bottom->parent;
- }
- bottom = src;
+
+ if (bottom == NULL) {
+ /* No idea from where it can come -- return now. */
+ goto sfnd_abort;
}
- }
- if (bottom == NULL) {
/*
- * No idea from where it can come -- return now.
+ * We now have a list of candidates headed by 'bottom' and linked via
+ * their 'parent' pointers. What we do next is create links between
+ * source and target nodes (which may or may not have been created)
+ * and set the necessary local variables in each target.
+ *
+ * The commands for each target are set from the commands of the
+ * transformation rule used to get from the src suffix to the targ
+ * suffix. Note that this causes the commands list of the original
+ * node, gn, to be replaced with the commands of the final
+ * transformation rule.
*/
- goto sfnd_abort;
- }
-
- /*
- * We now have a list of Src structures headed by 'bottom' and linked via
- * their 'parent' pointers. What we do next is create links between
- * source and target nodes (which may or may not have been created)
- * and set the necessary local variables in each target. The
- * commands for each target are set from the commands of the
- * transformation rule used to get from the src suffix to the targ
- * suffix. Note that this causes the commands list of the original
- * node, gn, to be replaced by the commands of the final
- * transformation rule. Also, the unmade field of gn is incremented.
- * Etc.
- */
- if (bottom->node == NULL)
- bottom->node = Targ_GetNode(bottom->file);
-
- for (src = bottom; src->parent != NULL; src = src->parent) {
- targ = src->parent;
-
- if (src->node->suffix)
- src->node->suffix->refCount--;
- src->node->suffix = src->suff;
- src->node->suffix->refCount++;
-
- if (targ->node == NULL)
- targ->node = Targ_GetNode(targ->file);
-
- SuffApplyTransform(targ->node, src->node,
- targ->suff, src->suff);
-
- if (targ->node != gn) {
- /*
- * Finish off the dependency-search process for any nodes
- * between bottom and gn (no point in questing around the
- * filesystem for their implicit source when it's already
- * known). Note that the node can't have any sources that
- * need expanding, since SuffFindThem will stop on an existing
- * node, so all we need to do is set the standard variables.
- */
- targ->node->type |= OP_DEPS_FOUND;
- Var_Set(PREFIX, targ->pref, targ->node);
- Var_Set(TARGET, targ->node->name, targ->node);
+ if (bottom->node == NULL)
+ bottom->node = Targ_GetNode(bottom->file);
+
+ for (src = bottom; src->parent != NULL; src = src->parent) {
+ targ = src->parent;
+
+ Suffix_Reassign(&src->node->suffix, src->suff);
+
+ if (targ->node == NULL)
+ targ->node = Targ_GetNode(targ->file);
+
+ ApplyTransform(targ->node, src->node,
+ targ->suff, src->suff);
+
+ if (targ->node != gn) {
+ /*
+ * Finish off the dependency-search process for any
+ * nodes between bottom and gn (no point in questing
+ * around the filesystem for their implicit source
+ * when it's already known). Note that the node
+ * can't have any sources that need expanding, since
+ * SuffFindThem will stop on an existing node, so all
+ * we need to do is set the standard variables.
+ */
+ targ->node->type |= OP_DEPS_FOUND;
+ Var_Set(PREFIX, targ->prefix, targ->node);
+ Var_Set(TARGET, targ->node->name, targ->node);
+ }
}
- }
- if (gn->suffix != NULL)
- gn->suffix->refCount--;
- gn->suffix = src->suff;
- gn->suffix->refCount++;
+ Suffix_Reassign(&gn->suffix, src->suff);
- /*
- * Nuke the transformation path and the Src structures left over in the
- * two lists.
- */
+ /*
+ * Nuke the transformation path and the candidates left over in the
+ * two lists.
+ */
sfnd_return:
- if (bottom != NULL && Lst_FindDatum(slst, bottom) == NULL)
- Lst_Append(slst, bottom);
+ if (bottom != NULL)
+ CandidateSearcher_AddIfNew(cs, bottom);
- while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs))
- continue;
+ while (RemoveCandidate(&srcs) || RemoveCandidate(&targs))
+ continue;
- Lst_MoveAll(slst, srcs);
- Lst_MoveAll(slst, targs);
+ CandidateSearcher_MoveAll(cs, &srcs);
+ CandidateSearcher_MoveAll(cs, &targs);
}
+static void
+CandidateSearcher_CleanUp(CandidateSearcher *cs)
+{
+ while (RemoveCandidate(&cs->list))
+ continue;
+ assert(Lst_IsEmpty(&cs->list));
+}
-/* Find implicit sources for the target.
+
+/*
+ * Find implicit sources for the target.
*
- * Nodes are added to the graph below the passed-in node. The nodes are
- * marked to have their IMPSRC variable filled in. The PREFIX variable is set
- * for the given node and all its implied children.
+ * Nodes are added to the graph as children of the passed-in node. The nodes
+ * are marked to have their IMPSRC variable filled in. The PREFIX variable
+ * is set for the given node and all its implied children.
*
* The path found by this target is the shortest path in the transformation
- * graph, which may pass through non-existent targets, to an existing target.
+ * graph, which may pass through nonexistent targets, to an existing target.
* The search continues on all paths from the root suffix until a file is
* found. I.e. if there's a path .o -> .c -> .l -> .l,v from the root and the
* .l,v file exists but the .c and .l files don't, the search will branch out
@@ -1853,61 +2030,39 @@ sfnd_return:
void
Suff_FindDeps(GNode *gn)
{
+ CandidateSearcher cs;
+
+ CandidateSearcher_Init(&cs);
+
+ FindDeps(gn, &cs);
- SuffFindDeps(gn, srclist);
- while (SuffRemoveSrc(srclist))
- continue;
+ CandidateSearcher_CleanUp(&cs);
+ CandidateSearcher_Done(&cs);
}
static void
-SuffFindDeps(GNode *gn, SrcList *slst)
+FindDeps(GNode *gn, CandidateSearcher *cs)
{
- if (gn->type & OP_DEPS_FOUND)
- return;
- gn->type |= OP_DEPS_FOUND;
+ if (gn->type & OP_DEPS_FOUND)
+ return;
+ gn->type |= OP_DEPS_FOUND;
- /*
- * Make sure we have these set, may get revised below.
- */
- Var_Set(TARGET, GNode_Path(gn), gn);
- Var_Set(PREFIX, gn->name, gn);
+ /* Make sure we have these set, may get revised below. */
+ Var_Set(TARGET, GNode_Path(gn), gn);
+ Var_Set(PREFIX, gn->name, gn);
- SUFF_DEBUG1("SuffFindDeps (%s)\n", gn->name);
+ DEBUG1(SUFF, "SuffFindDeps \"%s\"\n", gn->name);
- if (gn->type & OP_ARCHV) {
- SuffFindArchiveDeps(gn, slst);
- } else if (gn->type & OP_LIB) {
- /*
- * If the node is a library, it is the arch module's job to find it
- * and set the TARGET variable accordingly. We merely provide the
- * search path, assuming all libraries end in ".a" (if the suffix
- * hasn't been defined, there's nothing we can do for it, so we just
- * set the TARGET variable to the node's name in order to give it a
- * value).
- */
- Suff *s = FindSuffByName(LIBSUFF);
- if (gn->suffix)
- gn->suffix->refCount--;
- if (s != NULL) {
- gn->suffix = s;
- gn->suffix->refCount++;
- Arch_FindLib(gn, s->searchPath);
- } else {
- gn->suffix = NULL;
- Var_Set(TARGET, gn->name, gn);
- }
- /*
- * Because a library (-lfoo) target doesn't follow the standard
- * filesystem conventions, we don't set the regular variables for
- * the thing. .PREFIX is simply made empty...
- */
- Var_Set(PREFIX, "", gn);
- } else {
- SuffFindNormalDeps(gn, slst);
- }
+ if (gn->type & OP_ARCHV)
+ FindDepsArchive(gn, cs);
+ else if (gn->type & OP_LIB)
+ FindDepsLib(gn);
+ else
+ FindDepsRegular(gn, cs);
}
-/* Define which suffix is the null suffix.
+/*
+ * Define which suffix is the null suffix.
*
* Need to handle the changing of the null suffix gracefully so the old
* transformation rules don't just go away.
@@ -1918,39 +2073,31 @@ SuffFindDeps(GNode *gn, SrcList *slst)
void
Suff_SetNull(const char *name)
{
- Suff *suff = FindSuffByName(name);
- if (suff == NULL) {
- Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.",
+ Suffix *suff = FindSuffixByName(name);
+ if (suff == NULL) {
+ Parse_Error(PARSE_WARNING,
+ "Desired null suffix %s not defined.",
name);
- return;
- }
+ return;
+ }
- if (suffNull != NULL)
- suffNull->flags &= ~(unsigned)SUFF_NULL;
- suff->flags |= SUFF_NULL;
- /*
- * XXX: Here's where the transformation mangling would take place
- */
- suffNull = suff;
+ if (nullSuff != NULL)
+ nullSuff->flags &= ~(unsigned)SUFF_NULL;
+ suff->flags |= SUFF_NULL;
+ /* XXX: Here's where the transformation mangling would take place. */
+ nullSuff = suff;
}
/* Initialize the suffixes module. */
void
Suff_Init(void)
{
-#ifdef CLEANUP
- suffClean = Lst_New();
- sufflist = Lst_New();
-#endif
- srclist = Lst_New();
- transforms = Lst_New();
-
- /*
- * Create null suffix for single-suffix rules (POSIX). The thing doesn't
- * actually go on the suffix list or everyone will think that's its
- * suffix.
- */
- Suff_ClearSuffixes();
+ /*
+ * Create null suffix for single-suffix rules (POSIX). The thing
+ * doesn't actually go on the suffix list or everyone will think
+ * that's its suffix.
+ */
+ Suff_ClearSuffixes();
}
@@ -1959,75 +2106,75 @@ void
Suff_End(void)
{
#ifdef CLEANUP
- Lst_Destroy(sufflist, SuffFree);
- Lst_Destroy(suffClean, SuffFree);
- if (suffNull)
- SuffFree(suffNull);
- Lst_Free(srclist);
- Lst_Free(transforms);
+ Lst_DoneCall(&sufflist, SuffFree);
+ Lst_DoneCall(&suffClean, SuffFree);
+ if (nullSuff != NULL)
+ SuffFree(nullSuff);
+ Lst_Done(&transforms);
#endif
}
static void
-PrintSuffNames(const char *prefix, SuffList *suffs)
+PrintSuffNames(const char *prefix, SuffixList *suffs)
{
- SuffListNode *ln;
+ SuffixListNode *ln;
- debug_printf("#\t%s: ", prefix);
- for (ln = suffs->first; ln != NULL; ln = ln->next) {
- Suff *suff = ln->datum;
- debug_printf("%s ", suff->name);
- }
- debug_printf("\n");
+ debug_printf("#\t%s: ", prefix);
+ for (ln = suffs->first; ln != NULL; ln = ln->next) {
+ Suffix *suff = ln->datum;
+ debug_printf("%s ", suff->name);
+ }
+ debug_printf("\n");
}
static void
-PrintSuff(Suff *suff)
+Suffix_Print(Suffix *suff)
{
- debug_printf("# \"%s\" (num %d, ref %d)",
- suff->name, suff->sNum, suff->refCount);
- if (suff->flags != 0) {
- char flags_buf[SuffFlags_ToStringSize];
-
- debug_printf(" (%s)",
- Enum_FlagsToString(flags_buf, sizeof flags_buf,
- suff->flags, SuffFlags_ToStringSpecs));
- }
- debug_printf("\n");
+ debug_printf("# \"%s\" (num %d, ref %d)",
+ suff->name, suff->sNum, suff->refCount);
+ if (suff->flags != 0) {
+ char flags_buf[SuffixFlags_ToStringSize];
+
+ debug_printf(" (%s)",
+ Enum_FlagsToString(flags_buf, sizeof flags_buf,
+ suff->flags,
+ SuffixFlags_ToStringSpecs));
+ }
+ debug_printf("\n");
- PrintSuffNames("To", suff->parents);
- PrintSuffNames("From", suff->children);
+ PrintSuffNames("To", &suff->parents);
+ PrintSuffNames("From", &suff->children);
- debug_printf("#\tSearch Path: ");
- Dir_PrintPath(suff->searchPath);
- debug_printf("\n");
+ debug_printf("#\tSearch Path: ");
+ SearchPath_Print(suff->searchPath);
+ debug_printf("\n");
}
static void
PrintTransformation(GNode *t)
{
- debug_printf("%-16s:", t->name);
- Targ_PrintType(t->type);
- debug_printf("\n");
- Targ_PrintCmds(t);
- debug_printf("\n");
+ debug_printf("%-16s:", t->name);
+ Targ_PrintType(t->type);
+ debug_printf("\n");
+ Targ_PrintCmds(t);
+ debug_printf("\n");
}
void
Suff_PrintAll(void)
{
- debug_printf("#*** Suffixes:\n");
- {
- SuffListNode *ln;
- for (ln = sufflist->first; ln != NULL; ln = ln->next)
- PrintSuff(ln->datum);
- }
+ debug_printf("#*** Suffixes:\n");
+ {
+ SuffixListNode *ln;
+ for (ln = sufflist.first; ln != NULL; ln = ln->next)
+ Suffix_Print(ln->datum);
+ }
- debug_printf("#*** Transformations:\n");
- {
- GNodeListNode *ln;
- for (ln = transforms->first; ln != NULL; ln = ln->next)
- PrintTransformation(ln->datum);
- }
+ debug_printf("#*** Transformations:\n");
+ {
+ GNodeListNode *ln;
+ for (ln = transforms.first; ln != NULL; ln = ln->next)
+ PrintTransformation(ln->datum);
+ }
}
diff --git a/targ.c b/targ.c
index 43489c9d922b..a5194f95381f 100644
--- a/targ.c
+++ b/targ.c
@@ -1,4 +1,4 @@
-/* $NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig Exp $ */
+/* $NetBSD: targ.c,v 1.160 2021/01/10 23:59:53 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -93,12 +93,6 @@
* Targ_FindList Given a list of names, find nodes for all
* of them, creating them as necessary.
*
- * Targ_Ignore Return TRUE if errors should be ignored when
- * creating the given target.
- *
- * Targ_Silent Return TRUE if we should be silent when
- * creating the given target.
- *
* Targ_Precious Return TRUE if the target is precious and
* should not be removed if we are interrupted.
*
@@ -108,7 +102,7 @@
*
* Debugging:
* Targ_PrintGraph
- * Print out the entire graphm all variables and
+ * Print out the entire graph, all variables and
* statistics for the directory cache. Should print
* something for suffixes, too, but...
*/
@@ -119,14 +113,17 @@
#include "dir.h"
/* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig Exp $");
+MAKE_RCSID("$NetBSD: targ.c,v 1.160 2021/01/10 23:59:53 rillig Exp $");
-/* All target nodes found so far, but not the source nodes. */
-static GNodeList *allTargets;
+/*
+ * All target nodes that appeared on the left-hand side of one of the
+ * dependency operators ':', '::', '!'.
+ */
+static GNodeList allTargets = LST_INIT;
static HashTable allTargetsByName;
#ifdef CLEANUP
-static GNodeList *allNodes;
+static GNodeList allNodes = LST_INIT;
static void GNode_Free(void *);
#endif
@@ -134,28 +131,24 @@ static void GNode_Free(void *);
void
Targ_Init(void)
{
- allTargets = Lst_New();
- HashTable_Init(&allTargetsByName);
-#ifdef CLEANUP
- allNodes = Lst_New();
-#endif
+ HashTable_Init(&allTargetsByName);
}
void
Targ_End(void)
{
- Targ_Stats();
+ Targ_Stats();
#ifdef CLEANUP
- Lst_Free(allTargets);
- HashTable_Done(&allTargetsByName);
- Lst_Destroy(allNodes, GNode_Free);
+ Lst_Done(&allTargets);
+ HashTable_Done(&allTargetsByName);
+ Lst_DoneCall(&allNodes, GNode_Free);
#endif
}
void
Targ_Stats(void)
{
- HashTable_DebugStats(&allTargetsByName, "targets");
+ HashTable_DebugStats(&allTargetsByName, "targets");
}
/*
@@ -166,10 +159,11 @@ Targ_Stats(void)
GNodeList *
Targ_List(void)
{
- return allTargets;
+ return &allTargets;
}
-/* Create a new graph node, but don't register it anywhere.
+/*
+ * Create a new graph node, but don't register it anywhere.
*
* Graph nodes that appear on the left-hand side of a dependency line such
* as "target: source" are called targets. XXX: In some cases (like the
@@ -186,68 +180,95 @@ Targ_List(void)
GNode *
GNode_New(const char *name)
{
- GNode *gn;
-
- gn = bmake_malloc(sizeof *gn);
- gn->name = bmake_strdup(name);
- gn->uname = NULL;
- gn->path = NULL;
- gn->type = name[0] == '-' && name[1] == 'l' ? OP_LIB : 0;
- gn->flags = 0;
- gn->made = UNMADE;
- gn->unmade = 0;
- gn->mtime = 0;
- gn->youngestChild = NULL;
- gn->implicitParents = Lst_New();
- gn->parents = Lst_New();
- gn->children = Lst_New();
- gn->order_pred = Lst_New();
- gn->order_succ = Lst_New();
- gn->cohorts = Lst_New();
- gn->cohort_num[0] = '\0';
- gn->unmade_cohorts = 0;
- gn->centurion = NULL;
- gn->checked_seqno = 0;
- HashTable_Init(&gn->context);
- gn->commands = Lst_New();
- gn->suffix = NULL;
- gn->fname = NULL;
- gn->lineno = 0;
+ GNode *gn;
+
+ gn = bmake_malloc(sizeof *gn);
+ gn->name = bmake_strdup(name);
+ gn->uname = NULL;
+ gn->path = NULL;
+ gn->type = name[0] == '-' && name[1] == 'l' ? OP_LIB : OP_NONE;
+ gn->flags = GNF_NONE;
+ gn->made = UNMADE;
+ gn->unmade = 0;
+ gn->mtime = 0;
+ gn->youngestChild = NULL;
+ Lst_Init(&gn->implicitParents);
+ Lst_Init(&gn->parents);
+ Lst_Init(&gn->children);
+ Lst_Init(&gn->order_pred);
+ Lst_Init(&gn->order_succ);
+ Lst_Init(&gn->cohorts);
+ gn->cohort_num[0] = '\0';
+ gn->unmade_cohorts = 0;
+ gn->centurion = NULL;
+ gn->checked_seqno = 0;
+ HashTable_Init(&gn->vars);
+ Lst_Init(&gn->commands);
+ gn->suffix = NULL;
+ gn->fname = NULL;
+ gn->lineno = 0;
#ifdef CLEANUP
- Lst_Append(allNodes, gn);
+ Lst_Append(&allNodes, gn);
#endif
- return gn;
+ return gn;
}
#ifdef CLEANUP
static void
GNode_Free(void *gnp)
{
- GNode *gn = gnp;
-
- free(gn->name);
- free(gn->uname);
- free(gn->path);
- /* gn->youngestChild is not owned by this node. */
- Lst_Free(gn->implicitParents); /* ... but not the nodes themselves, */
- Lst_Free(gn->parents); /* as they are not owned by this node. */
- Lst_Free(gn->children); /* likewise */
- Lst_Free(gn->order_pred); /* likewise */
- Lst_Free(gn->order_succ); /* likewise */
- Lst_Free(gn->cohorts); /* likewise */
- HashTable_Done(&gn->context); /* ... but not the variables themselves,
- * even though they are owned by this node.
- * XXX: they should probably be freed. */
- Lst_Free(gn->commands); /* ... but not the commands themselves,
- * as they may be shared with other nodes. */
- /* gn->suffix is not owned by this node. */
- /* XXX: gn->suffix should be unreferenced here. This requires a thorough
- * check that the reference counting is done correctly in all places,
- * otherwise a suffix might be freed too early. */
-
- free(gn);
+ GNode *gn = gnp;
+
+ free(gn->name);
+ free(gn->uname);
+ free(gn->path);
+
+ /* Don't free gn->youngestChild since it is not owned by this node. */
+
+ /*
+ * In the following lists, only free the list nodes, but not the
+ * GNodes in them since these are not owned by this node.
+ */
+ Lst_Done(&gn->implicitParents);
+ Lst_Done(&gn->parents);
+ Lst_Done(&gn->children);
+ Lst_Done(&gn->order_pred);
+ Lst_Done(&gn->order_succ);
+ Lst_Done(&gn->cohorts);
+
+ /*
+ * Do not free the variables themselves, even though they are owned
+ * by this node.
+ *
+ * XXX: For the nodes that represent targets or sources (and not
+ * VAR_GLOBAL), it should be safe to free the variables as well,
+ * since each node manages the memory for all its variables itself.
+ *
+ * XXX: The GNodes that are only used as variable contexts (VAR_CMD,
+ * VAR_GLOBAL, VAR_INTERNAL) are not freed at all (see Var_End, where
+ * they are not mentioned). These might be freed at all, if their
+ * variable values are indeed not used anywhere else (see Trace_Init
+ * for the only suspicious use).
+ */
+ HashTable_Done(&gn->vars);
+
+ /*
+ * Do not free the commands themselves, as they may be shared with
+ * other nodes.
+ */
+ Lst_Done(&gn->commands);
+
+ /*
+ * gn->suffix is not owned by this node.
+ *
+ * XXX: gn->suffix should be unreferenced here. This requires a
+ * thorough check that the reference counting is done correctly in
+ * all places, otherwise a suffix might be freed too early.
+ */
+
+ free(gn);
}
#endif
@@ -255,23 +276,23 @@ GNode_Free(void *gnp)
GNode *
Targ_FindNode(const char *name)
{
- return HashTable_FindValue(&allTargetsByName, name);
+ return HashTable_FindValue(&allTargetsByName, name);
}
/* Get the existing global node, or create it. */
GNode *
Targ_GetNode(const char *name)
{
- Boolean isNew;
- HashEntry *he = HashTable_CreateEntry(&allTargetsByName, name, &isNew);
- if (!isNew)
- return HashEntry_Get(he);
-
- {
- GNode *gn = Targ_NewInternalNode(name);
- HashEntry_Set(he, gn);
- return gn;
- }
+ Boolean isNew;
+ HashEntry *he = HashTable_CreateEntry(&allTargetsByName, name, &isNew);
+ if (!isNew)
+ return HashEntry_Get(he);
+
+ {
+ GNode *gn = Targ_NewInternalNode(name);
+ HashEntry_Set(he, gn);
+ return gn;
+ }
}
/*
@@ -283,63 +304,54 @@ Targ_GetNode(const char *name)
GNode *
Targ_NewInternalNode(const char *name)
{
- GNode *gn = GNode_New(name);
- Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
- Lst_Append(allTargets, gn);
- if (doing_depend)
- gn->flags |= FROM_DEPEND;
- return gn;
+ GNode *gn = GNode_New(name);
+ Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
+ Lst_Append(&allTargets, gn);
+ DEBUG1(TARG, "Adding \"%s\" to all targets.\n", gn->name);
+ if (doing_depend)
+ gn->flags |= FROM_DEPEND;
+ return gn;
}
/*
* Return the .END node, which contains the commands to be run when
* everything else has been made.
*/
-GNode *Targ_GetEndNode(void)
-{
- /* Save the node locally to avoid having to search for it all the time. */
- static GNode *endNode = NULL;
- if (endNode == NULL) {
- endNode = Targ_GetNode(".END");
- endNode->type = OP_SPECIAL;
- }
- return endNode;
-}
-
-/* Return the named nodes, creating them as necessary. */
-GNodeList *
-Targ_FindList(StringList *names)
+GNode *
+Targ_GetEndNode(void)
{
- StringListNode *ln;
- GNodeList *nodes = Lst_New();
- for (ln = names->first; ln != NULL; ln = ln->next) {
- const char *name = ln->datum;
- GNode *gn = Targ_GetNode(name);
- Lst_Append(nodes, gn);
- }
- return nodes;
+ /*
+ * Save the node locally to avoid having to search for it all
+ * the time.
+ */
+ static GNode *endNode = NULL;
+
+ if (endNode == NULL) {
+ endNode = Targ_GetNode(".END");
+ endNode->type = OP_SPECIAL;
+ }
+ return endNode;
}
-/* Return true if should ignore errors when creating gn. */
-Boolean
-Targ_Ignore(const GNode *gn)
+/* Add the named nodes to the list, creating them as necessary. */
+void
+Targ_FindList(GNodeList *gns, StringList *names)
{
- return opts.ignoreErrors || gn->type & OP_IGNORE;
-}
+ StringListNode *ln;
-/* Return true if be silent when creating gn. */
-Boolean
-Targ_Silent(const GNode *gn)
-{
- return opts.beSilent || gn->type & OP_SILENT;
+ for (ln = names->first; ln != NULL; ln = ln->next) {
+ const char *name = ln->datum;
+ GNode *gn = Targ_GetNode(name);
+ Lst_Append(gns, gn);
+ }
}
/* See if the given target is precious. */
Boolean
Targ_Precious(const GNode *gn)
{
- /* XXX: Why are '::' targets precious? */
- return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP);
+ /* XXX: Why are '::' targets precious? */
+ return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP);
}
/*
@@ -352,198 +364,202 @@ static GNode *mainTarg;
void
Targ_SetMain(GNode *gn)
{
- mainTarg = gn;
+ mainTarg = gn;
}
static void
PrintNodeNames(GNodeList *gnodes)
{
- GNodeListNode *node;
+ GNodeListNode *ln;
- for (node = gnodes->first; node != NULL; node = node->next) {
- GNode *gn = node->datum;
- debug_printf(" %s%s", gn->name, gn->cohort_num);
- }
+ for (ln = gnodes->first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
+ debug_printf(" %s%s", gn->name, gn->cohort_num);
+ }
}
static void
PrintNodeNamesLine(const char *label, GNodeList *gnodes)
{
- if (Lst_IsEmpty(gnodes))
- return;
- debug_printf("# %s:", label);
- PrintNodeNames(gnodes);
- debug_printf("\n");
+ if (Lst_IsEmpty(gnodes))
+ return;
+ debug_printf("# %s:", label);
+ PrintNodeNames(gnodes);
+ debug_printf("\n");
}
void
Targ_PrintCmds(GNode *gn)
{
- StringListNode *ln;
- for (ln = gn->commands->first; ln != NULL; ln = ln->next) {
- const char *cmd = ln->datum;
- debug_printf("\t%s\n", cmd);
- }
+ StringListNode *ln;
+
+ for (ln = gn->commands.first; ln != NULL; ln = ln->next) {
+ const char *cmd = ln->datum;
+ debug_printf("\t%s\n", cmd);
+ }
}
-/* Format a modification time in some reasonable way and return it.
- * The time is placed in a static area, so it is overwritten with each call. */
-char *
+/*
+ * Format a modification time in some reasonable way and return it.
+ * The formatted time is placed in a static area, so it is overwritten
+ * with each call.
+ */
+const char *
Targ_FmtTime(time_t tm)
{
- struct tm *parts;
- static char buf[128];
+ static char buf[128];
- parts = localtime(&tm);
- (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts);
- return buf;
+ struct tm *parts = localtime(&tm);
+ (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts);
+ return buf;
}
/* Print out a type field giving only those attributes the user can set. */
void
Targ_PrintType(int type)
{
- int tbit;
-
-#define PRINTBIT(attr) case CONCAT(OP_,attr): debug_printf(" ." #attr); break
-#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))debug_printf(" ." #attr); break
-
- type &= ~OP_OPMASK;
-
- while (type) {
- tbit = 1 << (ffs(type) - 1);
- type &= ~tbit;
-
- switch(tbit) {
- PRINTBIT(OPTIONAL);
- PRINTBIT(USE);
- PRINTBIT(EXEC);
- PRINTBIT(IGNORE);
- PRINTBIT(PRECIOUS);
- PRINTBIT(SILENT);
- PRINTBIT(MAKE);
- PRINTBIT(JOIN);
- PRINTBIT(INVISIBLE);
- PRINTBIT(NOTMAIN);
- PRINTDBIT(LIB);
- /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */
- case OP_MEMBER: if (DEBUG(TARG))debug_printf(" .MEMBER"); break;
- PRINTDBIT(ARCHV);
- PRINTDBIT(MADE);
- PRINTDBIT(PHONY);
+ int tbit;
+
+ type &= ~OP_OPMASK;
+
+ while (type != 0) {
+ tbit = 1 << (ffs(type) - 1);
+ type &= ~tbit;
+
+ switch (tbit) {
+#define PRINTBIT(bit, attr) case bit: debug_printf(" " attr); break
+#define PRINTDBIT(bit, attr) case bit: DEBUG0(TARG, " " attr); break
+ PRINTBIT(OP_OPTIONAL, ".OPTIONAL");
+ PRINTBIT(OP_USE, ".USE");
+ PRINTBIT(OP_EXEC, ".EXEC");
+ PRINTBIT(OP_IGNORE, ".IGNORE");
+ PRINTBIT(OP_PRECIOUS, ".PRECIOUS");
+ PRINTBIT(OP_SILENT, ".SILENT");
+ PRINTBIT(OP_MAKE, ".MAKE");
+ PRINTBIT(OP_JOIN, ".JOIN");
+ PRINTBIT(OP_INVISIBLE, ".INVISIBLE");
+ PRINTBIT(OP_NOTMAIN, ".NOTMAIN");
+ PRINTDBIT(OP_LIB, ".LIB");
+ PRINTDBIT(OP_MEMBER, ".MEMBER");
+ PRINTDBIT(OP_ARCHV, ".ARCHV");
+ PRINTDBIT(OP_MADE, ".MADE");
+ PRINTDBIT(OP_PHONY, ".PHONY");
+#undef PRINTBIT
+#undef PRINTDBIT
+ }
}
- }
}
static const char *
made_name(GNodeMade made)
{
- switch (made) {
- case UNMADE: return "unmade";
- case DEFERRED: return "deferred";
- case REQUESTED: return "requested";
- case BEINGMADE: return "being made";
- case MADE: return "made";
- case UPTODATE: return "up-to-date";
- case ERROR: return "error when made";
- case ABORTED: return "aborted";
- default: return "unknown enum_made value";
- }
+ switch (made) {
+ case UNMADE: return "unmade";
+ case DEFERRED: return "deferred";
+ case REQUESTED: return "requested";
+ case BEINGMADE: return "being made";
+ case MADE: return "made";
+ case UPTODATE: return "up-to-date";
+ case ERROR: return "error when made";
+ case ABORTED: return "aborted";
+ default: return "unknown enum_made value";
+ }
}
static const char *
GNode_OpName(const GNode *gn)
{
- switch (gn->type & OP_OPMASK) {
- case OP_DEPENDS:
- return ":";
- case OP_FORCE:
- return "!";
- case OP_DOUBLEDEP:
- return "::";
- }
- return "";
+ switch (gn->type & OP_OPMASK) {
+ case OP_DEPENDS:
+ return ":";
+ case OP_FORCE:
+ return "!";
+ case OP_DOUBLEDEP:
+ return "::";
+ }
+ return "";
}
/* Print the contents of a node. */
void
Targ_PrintNode(GNode *gn, int pass)
{
- debug_printf("# %s%s", gn->name, gn->cohort_num);
- GNode_FprintDetails(opts.debug_file, ", ", gn, "\n");
- if (gn->flags == 0)
- return;
+ debug_printf("# %s%s", gn->name, gn->cohort_num);
+ GNode_FprintDetails(opts.debug_file, ", ", gn, "\n");
+ if (gn->flags == 0)
+ return;
+
+ if (!GNode_IsTarget(gn))
+ return;
- if (GNode_IsTarget(gn)) {
debug_printf("#\n");
- if (gn == mainTarg) {
- debug_printf("# *** MAIN TARGET ***\n");
- }
+ if (gn == mainTarg)
+ debug_printf("# *** MAIN TARGET ***\n");
+
if (pass >= 2) {
- if (gn->unmade > 0) {
- debug_printf("# %d unmade children\n", gn->unmade);
- } else {
- debug_printf("# No unmade children\n");
- }
- if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
- if (gn->mtime != 0) {
- debug_printf("# last modified %s: %s\n",
- Targ_FmtTime(gn->mtime),
- made_name(gn->made));
- } else if (gn->made != UNMADE) {
- debug_printf("# non-existent (maybe): %s\n",
- made_name(gn->made));
- } else {
- debug_printf("# unmade\n");
+ if (gn->unmade > 0)
+ debug_printf("# %d unmade children\n", gn->unmade);
+ else
+ debug_printf("# No unmade children\n");
+ if (!(gn->type & (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC))) {
+ if (gn->mtime != 0) {
+ debug_printf("# last modified %s: %s\n",
+ Targ_FmtTime(gn->mtime),
+ made_name(gn->made));
+ } else if (gn->made != UNMADE) {
+ debug_printf("# nonexistent (maybe): %s\n",
+ made_name(gn->made));
+ } else
+ debug_printf("# unmade\n");
}
- }
- PrintNodeNamesLine("implicit parents", gn->implicitParents);
+ PrintNodeNamesLine("implicit parents", &gn->implicitParents);
} else {
- if (gn->unmade)
- debug_printf("# %d unmade children\n", gn->unmade);
+ if (gn->unmade != 0)
+ debug_printf("# %d unmade children\n", gn->unmade);
}
- PrintNodeNamesLine("parents", gn->parents);
- PrintNodeNamesLine("order_pred", gn->order_pred);
- PrintNodeNamesLine("order_succ", gn->order_succ);
+
+ PrintNodeNamesLine("parents", &gn->parents);
+ PrintNodeNamesLine("order_pred", &gn->order_pred);
+ PrintNodeNamesLine("order_succ", &gn->order_succ);
debug_printf("%-16s%s", gn->name, GNode_OpName(gn));
Targ_PrintType(gn->type);
- PrintNodeNames(gn->children);
+ PrintNodeNames(&gn->children);
debug_printf("\n");
Targ_PrintCmds(gn);
debug_printf("\n\n");
- if (gn->type & OP_DOUBLEDEP) {
- Targ_PrintNodes(gn->cohorts, pass);
- }
- }
+ if (gn->type & OP_DOUBLEDEP)
+ Targ_PrintNodes(&gn->cohorts, pass);
}
void
Targ_PrintNodes(GNodeList *gnodes, int pass)
{
- GNodeListNode *ln;
- for (ln = gnodes->first; ln != NULL; ln = ln->next)
- Targ_PrintNode(ln->datum, pass);
+ GNodeListNode *ln;
+
+ for (ln = gnodes->first; ln != NULL; ln = ln->next)
+ Targ_PrintNode(ln->datum, pass);
}
/* Print only those targets that are just a source. */
static void
PrintOnlySources(void)
{
- GNodeListNode *ln;
+ GNodeListNode *ln;
- for (ln = allTargets->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
- if (GNode_IsTarget(gn))
- continue;
+ for (ln = allTargets.first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
+ if (GNode_IsTarget(gn))
+ continue;
- debug_printf("#\t%s [%s]", gn->name, GNode_Path(gn));
- Targ_PrintType(gn->type);
- debug_printf("\n");
- }
+ debug_printf("#\t%s [%s]", gn->name, GNode_Path(gn));
+ Targ_PrintType(gn->type);
+ debug_printf("\n");
+ }
}
-/* Input:
+/*
+ * Input:
* pass 1 => before processing
* 2 => after processing
* 3 => after processing, an error occurred
@@ -551,49 +567,51 @@ PrintOnlySources(void)
void
Targ_PrintGraph(int pass)
{
- debug_printf("#*** Input graph:\n");
- Targ_PrintNodes(allTargets, pass);
- debug_printf("\n");
- debug_printf("\n");
+ debug_printf("#*** Input graph:\n");
+ Targ_PrintNodes(&allTargets, pass);
+ debug_printf("\n");
+ debug_printf("\n");
- debug_printf("#\n");
- debug_printf("# Files that are only sources:\n");
- PrintOnlySources();
+ debug_printf("#\n");
+ debug_printf("# Files that are only sources:\n");
+ PrintOnlySources();
- debug_printf("#*** Global Variables:\n");
- Var_Dump(VAR_GLOBAL);
+ debug_printf("#*** Global Variables:\n");
+ Var_Dump(VAR_GLOBAL);
- debug_printf("#*** Command-line Variables:\n");
- Var_Dump(VAR_CMDLINE);
+ debug_printf("#*** Command-line Variables:\n");
+ Var_Dump(VAR_CMDLINE);
- debug_printf("\n");
- Dir_PrintDirectories();
- debug_printf("\n");
+ debug_printf("\n");
+ Dir_PrintDirectories();
+ debug_printf("\n");
- Suff_PrintAll();
+ Suff_PrintAll();
}
-/* Propagate some type information to cohort nodes (those from the '::'
+/*
+ * Propagate some type information to cohort nodes (those from the '::'
* dependency operator).
*
* Should be called after the makefiles are parsed but before any action is
- * taken. */
+ * taken.
+ */
void
Targ_Propagate(void)
{
- GNodeListNode *ln, *cln;
+ GNodeListNode *ln, *cln;
- for (ln = allTargets->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
- GNodeType type = gn->type;
+ for (ln = allTargets.first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
+ GNodeType type = gn->type;
- if (!(type & OP_DOUBLEDEP))
- continue;
+ if (!(type & OP_DOUBLEDEP))
+ continue;
- for (cln = gn->cohorts->first; cln != NULL; cln = cln->next) {
- GNode *cohort = cln->datum;
+ for (cln = gn->cohorts.first; cln != NULL; cln = cln->next) {
+ GNode *cohort = cln->datum;
- cohort->type |= type & ~OP_OPMASK;
+ cohort->type |= type & ~OP_OPMASK;
+ }
}
- }
}
diff --git a/trace.c b/trace.c
index a129406f2f3f..7506adf51727 100644
--- a/trace.c
+++ b/trace.c
@@ -1,4 +1,4 @@
-/* $NetBSD: trace.c,v 1.21 2020/10/31 22:05:56 rillig Exp $ */
+/* $NetBSD: trace.c,v 1.25 2020/12/20 14:32:13 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.21 2020/10/31 22:05:56 rillig Exp $");
+MAKE_RCSID("$NetBSD: trace.c,v 1.25 2020/12/20 14:32:13 rillig Exp $");
static FILE *trfile;
static pid_t trpid;
@@ -67,11 +67,12 @@ void
Trace_Init(const char *pathname)
{
if (pathname != NULL) {
- void *dontFreeIt;
+ FStr curDir;
trpid = getpid();
/* XXX: This variable may get overwritten later, which
* would make trwd point to undefined behavior. */
- trwd = Var_Value(".CURDIR", VAR_GLOBAL, &dontFreeIt);
+ curDir = Var_Value(".CURDIR", VAR_GLOBAL);
+ trwd = curDir.str;
trfile = fopen(pathname, "a");
}
@@ -92,8 +93,11 @@ Trace_Log(TrEvent event, Job *job)
jobTokensRunning,
evname[event], trpid, trwd);
if (job != NULL) {
- fprintf(trfile, " %s %d %x %x", job->node->name,
- job->pid, job->flags, job->node->type);
+ char flags[4];
+
+ Job_FlagsToString(job, flags, sizeof flags);
+ fprintf(trfile, " %s %d %s %x", job->node->name,
+ job->pid, flags, job->node->type);
}
fputc('\n', trfile);
fflush(trfile);
diff --git a/trace.h b/trace.h
index 758184b98b4a..5fd79acab5aa 100644
--- a/trace.h
+++ b/trace.h
@@ -1,4 +1,4 @@
-/* $NetBSD: trace.h,v 1.4 2020/10/18 17:19:54 rillig Exp $ */
+/* $NetBSD: trace.h,v 1.5 2020/11/28 08:41:53 rillig Exp $ */
/*-
* Copyright (c) 2000 The NetBSD Foundation, Inc.
@@ -34,6 +34,9 @@
* Definitions pertaining to the tracing of jobs in parallel mode.
*/
+#ifndef MAKE_TRACE_H
+#define MAKE_TRACE_H
+
typedef enum TrEvent {
MAKESTART,
MAKEEND,
@@ -47,3 +50,4 @@ void Trace_Init(const char *);
void Trace_Log(TrEvent, Job *);
void Trace_End(void);
+#endif
diff --git a/unit-tests/Makefile b/unit-tests/Makefile
index cca63155e868..87ed4ef212d4 100644
--- a/unit-tests/Makefile
+++ b/unit-tests/Makefile
@@ -1,6 +1,6 @@
-# $Id: Makefile,v 1.115 2020/11/18 04:01:07 sjg Exp $
+# $Id: Makefile,v 1.138 2021/01/01 22:55:09 sjg Exp $
#
-# $NetBSD: Makefile,v 1.206 2020/11/18 01:12:00 sjg Exp $
+# $NetBSD: Makefile,v 1.260 2020/12/31 03:05:12 rillig Exp $
#
# Unit tests for make(1)
#
@@ -30,18 +30,24 @@
# src/tests/usr.bin/make/t_make.sh.
#
+# we use these below but we might be an older make
+.MAKE.OS?= ${uname -s:L:sh}
+.MAKE.UID?= ${id -u:L:sh}
+
# Each test is in a sub-makefile.
# Keep the list sorted.
# Any test that is commented out must be ignored in
# src/tests/usr.bin/make/t_make.sh as well.
#TESTS+= archive
-TESTS+= archive-suffix
+#TESTS+= archive-suffix
TESTS+= cmd-errors
+TESTS+= cmd-errors-jobs
TESTS+= cmd-errors-lint
TESTS+= cmd-interrupt
TESTS+= cmdline
TESTS+= cmdline-undefined
TESTS+= comment
+TESTS+= compat-error
TESTS+= cond-cmp-numeric
TESTS+= cond-cmp-numeric-eq
TESTS+= cond-cmp-numeric-ge
@@ -51,12 +57,14 @@ TESTS+= cond-cmp-numeric-lt
TESTS+= cond-cmp-numeric-ne
TESTS+= cond-cmp-string
TESTS+= cond-cmp-unary
+TESTS+= cond-eof
TESTS+= cond-func
TESTS+= cond-func-commands
TESTS+= cond-func-defined
TESTS+= cond-func-empty
TESTS+= cond-func-exists
TESTS+= cond-func-make
+TESTS+= cond-func-make-main
TESTS+= cond-func-target
TESTS+= cond-late
TESTS+= cond-op
@@ -107,9 +115,14 @@ TESTS+= depsrc-usebefore-double-colon
TESTS+= depsrc-wait
TESTS+= deptgt
TESTS+= deptgt-begin
+TESTS+= deptgt-begin-fail
+TESTS+= deptgt-begin-fail-indirect
TESTS+= deptgt-default
TESTS+= deptgt-delete_on_error
TESTS+= deptgt-end
+TESTS+= deptgt-end-fail
+TESTS+= deptgt-end-fail-all
+TESTS+= deptgt-end-fail-indirect
TESTS+= deptgt-end-jobs
TESTS+= deptgt-error
TESTS+= deptgt-ignore
@@ -139,14 +152,20 @@ TESTS+= directive-elifmake
TESTS+= directive-elifndef
TESTS+= directive-elifnmake
TESTS+= directive-else
+TESTS+= directive-endfor
TESTS+= directive-endif
TESTS+= directive-error
TESTS+= directive-export
TESTS+= directive-export-env
+TESTS+= directive-export-impl
TESTS+= directive-export-gmake
TESTS+= directive-export-literal
TESTS+= directive-for
+TESTS+= directive-for-errors
+TESTS+= directive-for-escape
TESTS+= directive-for-generating-endif
+TESTS+= directive-for-lines
+TESTS+= directive-for-null
TESTS+= directive-hyphen-include
TESTS+= directive-if
TESTS+= directive-if-nested
@@ -157,6 +176,7 @@ TESTS+= directive-ifnmake
TESTS+= directive-include
TESTS+= directive-include-fatal
TESTS+= directive-info
+TESTS+= directive-misspellings
TESTS+= directive-sinclude
TESTS+= directive-undef
TESTS+= directive-unexport
@@ -180,14 +200,20 @@ TESTS+= impsrc
TESTS+= include-main
TESTS+= job-flags
#TESTS+= job-output-long-lines
+TESTS+= jobs-error-indirect
+TESTS+= jobs-error-nested
+TESTS+= jobs-error-nested-make
TESTS+= lint
TESTS+= make-exported
+TESTS+= meta-cmd-cmp
TESTS+= moderrs
TESTS+= modmatch
TESTS+= modmisc
TESTS+= modts
TESTS+= modword
+.if ${.MAKE.UID} > 0
TESTS+= objdir-writable
+.endif
TESTS+= opt
TESTS+= opt-backwards
TESTS+= opt-chdir
@@ -223,10 +249,13 @@ TESTS+= opt-ignore
TESTS+= opt-include-dir
TESTS+= opt-jobs
TESTS+= opt-jobs-internal
+TESTS+= opt-jobs-no-action
TESTS+= opt-keep-going
+TESTS+= opt-keep-going-multiple
TESTS+= opt-m-include-dir
TESTS+= opt-no-action
TESTS+= opt-no-action-at-all
+TESTS+= opt-no-action-runflags
TESTS+= opt-query
TESTS+= opt-raw
TESTS+= opt-silent
@@ -243,10 +272,11 @@ TESTS+= parse-var
TESTS+= phony-end
TESTS+= posix
TESTS+= # posix1 # broken by reverting POSIX changes
-TESTS+= qequals
TESTS+= recursive
TESTS+= sh
TESTS+= sh-dots
+TESTS+= sh-errctl
+TESTS+= sh-flags
TESTS+= sh-jobs
TESTS+= sh-jobs-error
TESTS+= sh-leading-at
@@ -264,10 +294,14 @@ TESTS+= shell-sh
TESTS+= suff-add-later
TESTS+= suff-clear-regular
TESTS+= suff-clear-single
+TESTS+= suff-incomplete
TESTS+= suff-lookup
TESTS+= suff-main
+TESTS+= suff-main-several
+TESTS+= suff-phony
TESTS+= suff-rebuild
TESTS+= suff-self
+TESTS+= suff-transform-debug
TESTS+= suff-transform-endless
TESTS+= suff-transform-expand
TESTS+= suff-transform-select
@@ -304,6 +338,7 @@ TESTS+= varmod-gmtime
TESTS+= varmod-hash
TESTS+= varmod-head
TESTS+= varmod-ifelse
+TESTS+= varmod-indirect
TESTS+= varmod-l-name-to-value
TESTS+= varmod-localtime
TESTS+= varmod-loop
@@ -361,6 +396,7 @@ TESTS+= varname-dot-make-path_filemon
TESTS+= varname-dot-make-pid
TESTS+= varname-dot-make-ppid
TESTS+= varname-dot-make-save_dollars
+TESTS+= varname-dot-makeflags
TESTS+= varname-dot-makeoverrides
TESTS+= varname-dot-newline
TESTS+= varname-dot-objdir
@@ -422,21 +458,22 @@ ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2
# Override make flags for some of the tests; default is -k.
# If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of
# settings FLAGS.test=-dv here, since that is closer to the test code.
-FLAGS.cond-func-make= via-cmdline
-FLAGS.directive-ifmake= first second
-FLAGS.doterror= # none, especially not -k
-FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain'
+FLAGS.cond-func-make= via-cmdline
+FLAGS.directive-ifmake= first second
+FLAGS.doterror= # none, especially not -k
+FLAGS.jobs-error-indirect= # none, especially not -k
+FLAGS.jobs-error-nested= # none, especially not -k
+FLAGS.jobs-error-nested-make= # none, especially not -k
+FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain'
# Some tests need extra postprocessing.
-SED_CMDS.export= \
- -e '/^[^=_A-Za-z0-9]*=/d'
-# these all share the same requirement
-.for t in export-all export-env
-SED_CMDS.$t= ${SED_CMDS.export}
-.endfor
-SED_CMDS.directive-export-gmake= \
- ${:D dash is a pain } \
- -e /non-zero/d
+SED_CMDS.dir= ${:D remove output from -DCLEANUP mode }
+SED_CMDS.dir+= -e '/^OpenDirs_Done:/d'
+SED_CMDS.dir+= -e '/^CachedDir /d'
+SED_CMDS.export= -e '/^[^=_A-Za-z0-9]*=/d'
+SED_CMDS.export-all= ${SED_CMDS.export}
+SED_CMDS.export-env= ${SED_CMDS.export}
+SED_CMDS.cmdline= -e 's,uid${.MAKE.UID}/,,'
SED_CMDS.job-output-long-lines= \
${:D Job separators on their own line are ok. } \
-e '/^--- job-[ab] ---$$/d' \
@@ -448,26 +485,34 @@ SED_CMDS.job-output-long-lines= \
${:D marker should always be at the beginning of the line. } \
-e '/^aa*--- job-b ---$$/d' \
-e '/^bb*--- job-a ---$$/d'
-SED_CMDS.objdir-writable= -e 's,${RO_OBJDIR},OBJDIR/roobj,g'
-SED_CMDS.opt-debug-graph1= \
- -e 's,${.CURDIR},CURDIR,'
-SED_CMDS.opt-debug-graph1+= \
- -e '/Global Variables:/,/Suffixes:/d'
-SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,<normalized: ...: not found>,'
+SED_CMDS.objdir-writable= -e 's,${RO_OBJDIR},OBJDIR/roobj,g'
+SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1}
SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(<pid>),'
SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid <pid>,'
SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process <pid>,'
SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: <pid>,'
+SED_CMDS.opt-debug-jobs+= -e 's,Command: ${.SHELL:T},Command: <shell>,'
# The "-q" may be there or not, see jobs.c, variable shells.
-SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: sh\) -q,\1,'
-SED_CMDS.var-op-shell+= -e 's,^${.SHELL:T}: ,,'
-SED_CMDS.var-op-shell+= -e '/command/{ s,^[1-9]: ,,;s,No such.*,not found,; }'
-SED_CMDS.vardebug= \
- ${:D canonicalize .SHELL } \
- -e 's,${.SHELL},</path/to/shell>,'
+SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: <shell>\) -q,\1,'
+SED_CMDS.opt-jobs-no-action= ${STD_SED_CMDS.hide-from-output}
+SED_CMDS.opt-no-action-runflags= ${STD_SED_CMDS.hide-from-output}
+# For Compat_RunCommand, useShell == FALSE.
+SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,<not found: ...>,'
+# For Compat_RunCommand, useShell == TRUE.
+SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)) failed (.*)$$,<not found: \1>,'
+SED_CMDS.sh-dots+= -e 's,^\(\*\*\* Error code \)[1-9][0-9]*,\1<nonzero>,'
+SED_CMDS.sh-errctl= ${STD_SED_CMDS.dj}
+SED_CMDS.sh-flags= ${STD_SED_CMDS.hide-from-output}
+SED_CMDS.suff-main+= ${STD_SED_CMDS.dg1}
+SED_CMDS.suff-main-several+= ${STD_SED_CMDS.dg1}
+SED_CMDS.suff-transform-debug+= ${STD_SED_CMDS.dg1}
+SED_CMDS.var-op-shell+= \
+ -e 's,^${.SHELL:T}: [ 0-9:]*,,' \
+ -e 's,^${.SHELL:T}: ,,' \
+ -e '/command/s,No such.*,not found,'
+SED_CMDS.vardebug+= -e 's,${.SHELL},</path/to/shell>,'
SED_CMDS.varmod-subst-regex+= \
-e 's,\(Regex compilation error:\).*,\1 (details omitted),'
-SED_CMDS.varmod-edge+= -e 's, line [0-9]*:, line omitted:,'
SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g'
@@ -475,10 +520,9 @@ SED_CMDS.varname-dot-shell+= -e 's,"/[^" ]*","(details omitted)",g'
SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g'
# Some tests need an additional round of postprocessing.
-POSTPROC.deptgt-suffixes= \
- ${TOOL_SED} -n -e '/^\#\*\*\* Suffixes/,/^\#\*/p'
-POSTPROC.gnode-submake= awk '/Input graph/, /^$$/'
-POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p'
+POSTPROC.deptgt-suffixes= awk '/^\#\*\*\* Suffixes/,/^never-stop/'
+POSTPROC.gnode-submake= awk '/Input graph/, /^$$/'
+POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p'
# Some tests reuse other tests, which makes them unnecessarily fragile.
export-all.rawout: export.mk
@@ -487,6 +531,35 @@ unexport-env.rawout: export.mk
# End of the configuration section.
+# Some standard sed commands, to be used in the SED_CMDS above.
+
+# Omit details such as process IDs from the output of the -dg1 option.
+STD_SED_CMDS.dg1= -e 's,${.CURDIR}$$,<curdir>,'
+STD_SED_CMDS.dg1+= -e '/\.MAKE.PATH_FILEMON/d'
+STD_SED_CMDS.dg1+= -e '/^MAKE_VERSION/d;/^\#.*\/mk/d'
+STD_SED_CMDS.dg1+= -e 's, ${DEFSYSPATH:U/usr/share/mk}$$, <defsyspath>,'
+STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE *=\) .*,\1 <details omitted>,'
+STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE\.[A-Z_]* *=\) .*,\1 <details omitted>,'
+STD_SED_CMDS.dg1+= -e 's,^\(MACHINE[_ARCH]* *=\) .*,\1 <details omitted>,'
+STD_SED_CMDS.dg1+= -e 's,^\(MAKE *=\) .*,\1 <details omitted>,'
+
+# Omit details such as process IDs from the output of the -dj option.
+STD_SED_CMDS.dj= \
+ -e '/Process/d;/JobFinish:/d' \
+ -e 's,^\(Job_TokenWithdraw\)([0-9]*),\1(<pid>),' \
+ -e 's,^([0-9][0-9]*) \(withdrew token\),(<pid>) \1,' \
+ -e 's, \(pid\) [0-9][0-9]*, \1 <pid>,' \
+ -e 's,^\( Command:\) .*,\1 <shell>,'
+
+# Reduce the noise for tests running with the -n option, since there is no
+# other way to suppress the echoing of the commands.
+STD_SED_CMDS.hide-from-output= \
+ -e '/^echo hide-from-output/d' \
+ -e 's,hide-from-output ,,' \
+ -e 's,hide-from-output,,'
+
+# End of the configuration helpers section.
+
.MAIN: all
.-include "Makefile.inc"
@@ -532,6 +605,11 @@ _MKMSG_TEST= :
MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc
+.if ${.MAKE.OS} == "NetBSD"
+LIMIT_RESOURCES?= ulimit -v 200000
+.endif
+LIMIT_RESOURCES?= :
+
# Each test is run in a sub-make, to keep the tests for interfering with
# each other, and because they use different environment variables and
# command line options.
@@ -539,6 +617,7 @@ MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc
.mk.rawout:
@${_MKMSG_TEST:Uecho '# test '} ${.PREFIX}
@set -eu; \
+ ${LIMIT_RESOURCES}; \
cd ${.OBJDIR}; \
env -i PATH="$$PATH" ${MAKE_TEST_ENV} ${ENV.${.PREFIX:T}} \
${TEST_MAKE} \
@@ -561,6 +640,10 @@ _SED_CMDS+= -e '/stopped/s, /.*, unit-tests,'
# strip ${.CURDIR}/ from the output
_SED_CMDS+= -e 's,${.CURDIR:S,.,\\.,g}/,,g'
_SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g'
+# on AT&T derrived systems; false exits 255 not 1
+.if ${.MAKE.OS:N*BSD} != ""
+_SED_CMDS+= -e 's,\(Error code\) 255,\1 1,'
+.endif
.rawout.out:
@${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.PREFIX:T}} \
@@ -570,10 +653,18 @@ _SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g'
@echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp2
@mv ${.TARGET}.tmp2 ${.TARGET}
+.if empty(DIFF_FLAGS)
+DIFF_ECHO= echo
+.else
+DIFF_ECHO= :
+.endif
+
# Compare all output files
test: ${OUTFILES} .PHONY
@failed= ; \
for test in ${TESTS}; do \
+ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out && continue || \
+ ${DIFF_ECHO} diff ${UNIT_TESTS}/$${test}.exp $${test}.out; \
${TOOL_DIFF} ${DIFF_FLAGS} ${UNIT_TESTS}/$${test}.exp $${test}.out \
|| failed="$${failed}$${failed:+ }$${test}" ; \
done ; \
diff --git a/unit-tests/cmd-errors-jobs.exp b/unit-tests/cmd-errors-jobs.exp
new file mode 100644
index 000000000000..6d9c6bb7f890
--- /dev/null
+++ b/unit-tests/cmd-errors-jobs.exp
@@ -0,0 +1,9 @@
+: undefined eol
+make: Unclosed variable "UNCLOSED"
+: unclosed-variable
+make: Unclosed variable expression (expecting '}') for "UNCLOSED"
+: unclosed-modifier
+make: Unknown modifier 'Z'
+: unknown-modifier eol
+: end eol
+exit status 0
diff --git a/unit-tests/cmd-errors-jobs.mk b/unit-tests/cmd-errors-jobs.mk
new file mode 100644
index 000000000000..8462a2e3497e
--- /dev/null
+++ b/unit-tests/cmd-errors-jobs.mk
@@ -0,0 +1,32 @@
+# $NetBSD: cmd-errors-jobs.mk,v 1.1 2020/12/27 05:11:40 rillig Exp $
+#
+# Demonstrate how errors in variable expansions affect whether the commands
+# are actually executed in jobs mode.
+
+.MAKEFLAGS: -j1
+
+all: undefined unclosed-variable unclosed-modifier unknown-modifier end
+
+# Undefined variables are not an error. They expand to empty strings.
+undefined:
+ : $@ ${UNDEFINED} eol
+
+# XXX: As of 2020-11-01, this command is executed even though it contains
+# parse errors.
+unclosed-variable:
+ : $@ ${UNCLOSED
+
+# XXX: As of 2020-11-01, this command is executed even though it contains
+# parse errors.
+unclosed-modifier:
+ : $@ ${UNCLOSED:
+
+# XXX: As of 2020-11-01, this command is executed even though it contains
+# parse errors.
+unknown-modifier:
+ : $@ ${UNKNOWN:Z} eol
+
+end:
+ : $@ eol
+
+# XXX: As of 2020-11-02, despite the parse errors, the exit status is 0.
diff --git a/unit-tests/cmd-errors.mk b/unit-tests/cmd-errors.mk
index 5ad4be311873..356fe1a3e4a2 100644
--- a/unit-tests/cmd-errors.mk
+++ b/unit-tests/cmd-errors.mk
@@ -1,7 +1,7 @@
-# $NetBSD: cmd-errors.mk,v 1.3 2020/11/09 23:36:34 rillig Exp $
+# $NetBSD: cmd-errors.mk,v 1.4 2020/12/27 05:11:40 rillig Exp $
#
# Demonstrate how errors in variable expansions affect whether the commands
-# are actually executed.
+# are actually executed in compat mode.
all: undefined unclosed-variable unclosed-modifier unknown-modifier end
diff --git a/unit-tests/cmdline.mk b/unit-tests/cmdline.mk
index cd88cead4558..a3e2de984927 100644
--- a/unit-tests/cmdline.mk
+++ b/unit-tests/cmdline.mk
@@ -2,7 +2,7 @@
#
# Tests for command line parsing and related special variables.
-TMPBASE?= /tmp
+TMPBASE?= /tmp/uid${.MAKE.UID}
SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID
SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID
MAKE_CMD= env TMPBASE=${TMPBASE}/${SUB1} ${.MAKE} -f ${MAKEFILE} -r
diff --git a/unit-tests/compat-error.exp b/unit-tests/compat-error.exp
new file mode 100644
index 000000000000..256cb6d4361c
--- /dev/null
+++ b/unit-tests/compat-error.exp
@@ -0,0 +1,15 @@
+: Making success1 out of nothing.
+: Making fail1 out of nothing.
+false 'fail1' '${.TARGET}' '$${.TARGET}'
+*** Error code 1 (continuing)
+: Making success2 out of nothing.
+: Making fail2 out of nothing.
+false 'fail2' '${.TARGET}' '$${.TARGET}'
+*** Error code 1 (continuing)
+: Making success3 out of nothing.
+
+Stop.
+make: stopped in unit-tests
+.ERROR target: <fail1>
+.ERROR command: <>
+exit status 1
diff --git a/unit-tests/compat-error.mk b/unit-tests/compat-error.mk
new file mode 100644
index 000000000000..4cbc48d4b6bb
--- /dev/null
+++ b/unit-tests/compat-error.mk
@@ -0,0 +1,37 @@
+# $NetBSD: compat-error.mk,v 1.3 2020/12/13 19:33:53 rillig Exp $
+#
+# Test detailed error handling in compat mode.
+#
+# Until 2020-12-13, .ERROR_TARGET was success3, which was wrong.
+# Since compat.c 1.215 from 2020-12-13, it is 'fail1', which is the first
+# failed top-level target. XXX: Even better would be if .ERROR_TARGET were
+# the smallest target that caused the build to fail, even if it were a
+# sub-sub-sub-dependency of a top-level target.
+#
+# XXX: As of 2020-12-13, .ERROR_CMD is empty, which is wrong.
+#
+# See also:
+# Compat_Run
+#
+# The commit that added the NULL command to gn->commands:
+# CVS: 1994.06.06.22.45.??
+# Git: 26a8972fd7f982502c5fbfdabd34578b99d77ca5
+# 1994: Lst_Replace (cmdNode, (ClientData) NULL);
+# 2020: LstNode_SetNull(cmdNode);
+#
+# The commit that skipped NULL commands for .ERROR_CMD:
+# CVS: 2016.08.11.19.53.??
+# Git: 58b23478b7353d46457089e726b07a49197388e4
+
+.MAKEFLAGS: success1 fail1 success2 fail2 success3
+
+success1 success2 success3:
+ : Making ${.TARGET} out of nothing.
+
+fail1 fail2:
+ : Making ${.TARGET} out of nothing.
+ false '${.TARGET}' '$${.TARGET}' '$$$${.TARGET}'
+
+.ERROR:
+ @echo ${.TARGET} target: '<'${.ERROR_TARGET:Q}'>'
+ @echo ${.TARGET} command: '<'${.ERROR_CMD:Q}'>'
diff --git a/unit-tests/cond-eof.exp b/unit-tests/cond-eof.exp
new file mode 100644
index 000000000000..3b1e6eb1f056
--- /dev/null
+++ b/unit-tests/cond-eof.exp
@@ -0,0 +1,9 @@
+side effect
+make: "cond-eof.mk" line 15: Malformed conditional (0 ${SIDE_EFFECT} ${SIDE_EFFECT2})
+side effect
+make: "cond-eof.mk" line 17: Malformed conditional (1 ${SIDE_EFFECT} ${SIDE_EFFECT2})
+side effect
+make: "cond-eof.mk" line 19: Malformed conditional ((0) ${SIDE_EFFECT} ${SIDE_EFFECT2})
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/cond-eof.mk b/unit-tests/cond-eof.mk
new file mode 100644
index 000000000000..08f432bc4593
--- /dev/null
+++ b/unit-tests/cond-eof.mk
@@ -0,0 +1,20 @@
+# $NetBSD: cond-eof.mk,v 1.2 2020/12/14 20:28:09 rillig Exp $
+#
+# Tests for parsing conditions, especially the end of such conditions, which
+# are represented as the token TOK_EOF.
+
+SIDE_EFFECT= ${:!echo 'side effect' 1>&2!}
+SIDE_EFFECT2= ${:!echo 'side effect 2' 1>&2!}
+
+# In the following conditions, ${SIDE_EFFECT} is the position of the first
+# parse error. It is always fully evaluated, even if it were not necessary
+# to expand the variable expression. This is because these syntax errors are
+# an edge case that does not occur during normal operation, therefore there
+# is no need to optimize for this case, and it would slow down the common
+# case as well.
+.if 0 ${SIDE_EFFECT} ${SIDE_EFFECT2}
+.endif
+.if 1 ${SIDE_EFFECT} ${SIDE_EFFECT2}
+.endif
+.if (0) ${SIDE_EFFECT} ${SIDE_EFFECT2}
+.endif
diff --git a/unit-tests/cond-func-empty.mk b/unit-tests/cond-func-empty.mk
index f43d99bf92c5..5094924f1c8d 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.10 2020/11/15 14:07:53 rillig Exp $
+# $NetBSD: cond-func-empty.mk,v 1.11 2020/11/28 14:08:37 rillig Exp $
#
# Tests for the empty() function in .if conditions, which tests a variable
# expression for emptiness.
@@ -155,5 +155,30 @@ ${:U WORD }= variable name with spaces
. error
.endif
+# Between 2020-06-28 and var.c 1.226 from 2020-07-02, this paragraph generated
+# a wrong error message "Variable VARNAME is recursive".
+#
+# The bug was that the !empty() condition was evaluated, even though this was
+# not necessary since the defined() condition already evaluated to false.
+#
+# When evaluating the !empty condition, the variable name was parsed as
+# "VARNAME${:U2}", but without expanding any nested variable expression, in
+# this case the ${:U2}. Therefore, the variable name came out as simply
+# "VARNAME". Since this variable name should have been discarded quickly after
+# parsing it, this unrealistic variable name should have done no harm.
+#
+# The variable expression was expanded though, and this was wrong. The
+# expansion was done without the VARE_WANTRES flag (called VARF_WANTRES back
+# then) though. This had the effect that the ${:U1} from the value of VARNAME
+# expanded to an empty string. This in turn created the seemingly recursive
+# definition VARNAME=${VARNAME}, and that definition was never meant to be
+# expanded.
+#
+# This was fixed by expanding nested variable expressions in the variable name
+# only if the flag VARE_WANTRES is given.
+VARNAME= ${VARNAME${:U1}}
+.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
+.endif
+
all:
@:;
diff --git a/unit-tests/cond-func-exists.mk b/unit-tests/cond-func-exists.mk
index 4a80fb1c393d..48d7e727dc3f 100644
--- a/unit-tests/cond-func-exists.mk
+++ b/unit-tests/cond-func-exists.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func-exists.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-func-exists.mk,v 1.6 2020/11/30 20:12:29 rillig Exp $
#
# Tests for the exists() function in .if conditions.
@@ -38,5 +38,14 @@
. error
.endif
+# The exists function does not really look up the file in the file system,
+# instead it uses a cache that is preloaded very early, before parsing the
+# first makefile. At that time, the file did not exist yet.
+_!= > cond-func-exists.just-created
+.if exists(cond-func-exists.just-created)
+. error
+.endif
+_!= rm cond-func-exists.just-created
+
all:
@:;
diff --git a/unit-tests/cond-func-make-main.exp b/unit-tests/cond-func-make-main.exp
new file mode 100644
index 000000000000..3266459f3bea
--- /dev/null
+++ b/unit-tests/cond-func-make-main.exp
@@ -0,0 +1,3 @@
+: Making dot-main-target-1a.
+: Making dot-main-target-1b.
+exit status 0
diff --git a/unit-tests/cond-func-make-main.mk b/unit-tests/cond-func-make-main.mk
new file mode 100644
index 000000000000..31b370afabde
--- /dev/null
+++ b/unit-tests/cond-func-make-main.mk
@@ -0,0 +1,62 @@
+# $NetBSD: cond-func-make-main.mk,v 1.1 2020/11/22 19:37:27 rillig Exp $
+#
+# Test how accurately the make() function in .if conditions reflects
+# what is actually made.
+#
+# There are several ways to specify what is being made:
+#
+# 1. The default main target is the first target in the given makefiles that
+# is not one of the special targets. For example, .PHONY is special when
+# it appears on the left-hand side of the ':'. It is not special on the
+# right-hand side though.
+#
+# 2. Command line arguments that are neither options (-ds or -k) nor variable
+# assignments (VAR=value) are interpreted as targets to be made. These
+# override the default main target from above.
+#
+# 3. All sources of the first '.MAIN: sources' line. Any further .MAIN line
+# is treated as if .MAIN were a regular name.
+#
+# This test only covers items 1 and 3. For item 2, see cond-func-make.mk.
+
+first-main-target:
+ : Making ${.TARGET}.
+
+# Even though the main-target would actually be made at this point, it is
+# ignored by the make() function.
+.if make(first-main-target)
+. error
+.endif
+
+# Declaring a target via the .MAIN dependency adds it to the targets to be
+# created (opts.create), but only that list was empty at the beginning of
+# the line. This implies that several main targets can be set at the name
+# time, but they have to be in the same dependency group.
+#
+# See ParseDoDependencyTargetSpecial, branch SP_MAIN.
+.MAIN: dot-main-target-1a dot-main-target-1b
+
+.if !make(dot-main-target-1a)
+. error
+.endif
+.if !make(dot-main-target-1b)
+. error
+.endif
+
+dot-main-target-{1,2}{a,b}:
+ : Making ${.TARGET}.
+
+# At this point, the list of targets to be made (opts.create) is not empty
+# anymore. ParseDoDependencyTargetSpecial therefore treats the .MAIN as if
+# it were an ordinary target. Since .MAIN is not listed as a dependency
+# anywhere, it is not made.
+.if target(.MAIN)
+. error
+.endif
+.MAIN: dot-main-target-2a dot-main-target-2b
+.if !target(.MAIN)
+. error
+.endif
+.if make(dot-main-target-2a)
+. error
+.endif
diff --git a/unit-tests/cond-short.exp b/unit-tests/cond-short.exp
index fdc38d98a8b7..2865dcb6ef33 100644
--- a/unit-tests/cond-short.exp
+++ b/unit-tests/cond-short.exp
@@ -7,10 +7,10 @@ expected M pattern
expected or
expected or exists
expected or empty
-defined(V42) && 42 > 0: Ok
-defined(V66) && ( "" < 42 ): Ok
-1 || 42 < 42: Ok
-1 || < 42: Ok
-0 || 42 <= 42: Ok
-0 || < 42: Ok
+defined(V42) && ${V42} > 0: Ok
+defined(V66) && ( "${iV2}" < ${V42} ): Ok
+1 || ${iV1} < ${V42}: Ok
+1 || ${iV2:U2} < ${V42}: Ok
+0 || ${iV1} <= ${V42}: Ok
+0 || ${iV2:U2} < ${V42}: Ok
exit status 0
diff --git a/unit-tests/cond-short.mk b/unit-tests/cond-short.mk
index 077684be33fc..46c7ea26a97b 100644
--- a/unit-tests/cond-short.mk
+++ b/unit-tests/cond-short.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-short.mk,v 1.12 2020/11/15 14:58:14 rillig Exp $
+# $NetBSD: cond-short.mk,v 1.15 2020/12/01 19:37:23 rillig Exp $
#
# Demonstrates that in conditions, the right-hand side of an && or ||
# is only evaluated if it can actually influence the result.
@@ -6,9 +6,13 @@
# mode in most programming languages. A notable exception is Ada, which
# distinguishes between the operators 'And', 'And Then', 'Or', 'Or Else'.
#
-# Between 2015-10-11 and 2020-06-28, the right-hand side of an && or ||
-# operator was always evaluated, which was wrong.
-# TODO: Had the evaluation been correct at some time before 2015-11-12?
+# Before 2020-06-28, the right-hand side of an && or || operator was always
+# evaluated, which was wrong. In cond.c 1.69 and var.c 1.197 on 2015-10-11,
+# Var_Parse got a new parameter named 'wantit'. Since then it would have been
+# possible to skip evaluation of irrelevant variable expressions and only
+# parse them. They were still evaluated though, the only difference to
+# relevant variable expressions was that in the irrelevant variable
+# expressions, undefined variables were allowed.
# The && operator.
@@ -128,33 +132,56 @@ x= Ok
.else
x= Fail
.endif
-x!= echo 'defined(V42) && ${V42} > 0: $x' >&2; echo
+x!= echo 'defined(V42) && $${V42} > 0: $x' >&2; echo
-# this one throws both String comparison operator and
-# Malformed conditional with cond.c 1.78
-# indirect iV2 would expand to "" and treated as 0
+# With cond.c 1.76 from 2020-07-03, the following condition triggered a
+# warning: "String comparison operator should be either == or !=".
+# This was because the variable expression ${iV2} was defined, but the
+# contained variable V66 was undefined. The left-hand side of the comparison
+# therefore evaluated to the string "${V66}", which is obviously not a number.
+#
+# This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant
+# comparisons. Instead, they are only parsed and then discarded.
+#
+# At that time, there was not enough debug logging to see the details in the
+# -dA log. To actually see it, add debug logging at the beginning and end of
+# Var_Parse.
.if defined(V66) && ( ${iV2} < ${V42} )
x= Fail
.else
x= Ok
.endif
-x!= echo 'defined(V66) && ( "${iV2}" < ${V42} ): $x' >&2; echo
+# XXX: This condition doesn't match the one above. The quotes are missing
+# above. This is a crucial detail since without quotes, the variable
+# expression ${iV2} evaluates to "${V66}", and with quotes, it evaluates to ""
+# since undefined variables are allowed and expand to an empty string.
+x!= echo 'defined(V66) && ( "$${iV2}" < $${V42} ): $x' >&2; echo
-# next two thow String comparison operator with cond.c 1.78
-# indirect iV1 would expand to 42
.if 1 || ${iV1} < ${V42}
x= Ok
.else
x= Fail
.endif
-x!= echo '1 || ${iV1} < ${V42}: $x' >&2; echo
+x!= echo '1 || $${iV1} < $${V42}: $x' >&2; echo
+# With cond.c 1.76 from 2020-07-03, the following condition triggered a
+# warning: "String comparison operator should be either == or !=".
+# This was because the variable expression ${iV2} was defined, but the
+# contained variable V66 was undefined. The left-hand side of the comparison
+# therefore evaluated to the string "${V66}", which is obviously not a number.
+#
+# This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant
+# comparisons. Instead, they are only parsed and then discarded.
+#
+# At that time, there was not enough debug logging to see the details in the
+# -dA log. To actually see it, add debug logging at the beginning and end of
+# Var_Parse.
.if 1 || ${iV2:U2} < ${V42}
x= Ok
.else
x= Fail
.endif
-x!= echo '1 || ${iV2:U2} < ${V42}: $x' >&2; echo
+x!= echo '1 || $${iV2:U2} < $${V42}: $x' >&2; echo
# the same expressions are fine when the lhs is expanded
# ${iV1} expands to 42
@@ -163,7 +190,7 @@ x= Ok
.else
x= Fail
.endif
-x!= echo '0 || ${iV1} <= ${V42}: $x' >&2; echo
+x!= echo '0 || $${iV1} <= $${V42}: $x' >&2; echo
# ${iV2:U2} expands to 2
.if 0 || ${iV2:U2} < ${V42}
@@ -171,11 +198,12 @@ x= Ok
.else
x= Fail
.endif
-x!= echo '0 || ${iV2:U2} < ${V42}: $x' >&2; echo
+x!= echo '0 || $${iV2:U2} < $${V42}: $x' >&2; echo
-# TODO: Has this always worked? There may have been a time, maybe around
-# 2000, when make would complain about the "Malformed conditional" because
-# UNDEF is not defined.
+# The right-hand side of the '&&' is irrelevant since the left-hand side
+# already evaluates to false. Before cond.c 1.79 from 2020-07-09, it was
+# expanded nevertheless, although with a small modification: undefined
+# variables may be used in these expressions without generating an error.
.if defined(UNDEF) && ${UNDEF} != "undefined"
. error
.endif
diff --git a/unit-tests/cond-token-string.exp b/unit-tests/cond-token-string.exp
index 5df4cc675bb1..83009f420325 100644
--- a/unit-tests/cond-token-string.exp
+++ b/unit-tests/cond-token-string.exp
@@ -1,4 +1,4 @@
-make: Unknown modifier 'Z'
+make: "cond-token-string.mk" line 9: Unknown modifier 'Z'
make: "cond-token-string.mk" line 9: Malformed conditional ("" != "${:Uvalue:Z}")
make: "cond-token-string.mk" line 18: xvalue is not defined.
make: "cond-token-string.mk" line 24: Malformed conditional (x${:Uvalue} == "")
diff --git a/unit-tests/dep-percent.exp b/unit-tests/dep-percent.exp
index 2b5b5bf1fa50..1e6c04d2e167 100644
--- a/unit-tests/dep-percent.exp
+++ b/unit-tests/dep-percent.exp
@@ -1,3 +1,6 @@
make: don't know how to make dep-percent.o (continuing)
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/depsrc-meta.exp b/unit-tests/depsrc-meta.exp
index 39a9383953dd..77e27582f7da 100644
--- a/unit-tests/depsrc-meta.exp
+++ b/unit-tests/depsrc-meta.exp
@@ -1 +1,5 @@
+Skipping meta for actual-test: no commands
+Skipping meta for .END: .SPECIAL
+Targets from meta mode:
+| TARGET depsrc-meta-target
exit status 0
diff --git a/unit-tests/depsrc-meta.mk b/unit-tests/depsrc-meta.mk
index 6adef4baa5de..d41aad9a9c96 100644
--- a/unit-tests/depsrc-meta.mk
+++ b/unit-tests/depsrc-meta.mk
@@ -1,8 +1,31 @@
-# $NetBSD: depsrc-meta.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: depsrc-meta.mk,v 1.4 2020/11/27 08:39:07 rillig Exp $
#
# Tests for the special source .META in dependency declarations.
# TODO: Implementation
+# TODO: Explanation
+
+.if make(actual-test)
+
+.MAKEFLAGS: -dM
+.MAKE.MODE= meta curDirOk=true
+
+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:'
+ @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
+
+.endif
diff --git a/unit-tests/depsrc-optional.exp b/unit-tests/depsrc-optional.exp
index fce85b3cb38e..8ecd28266b5e 100644
--- a/unit-tests/depsrc-optional.exp
+++ b/unit-tests/depsrc-optional.exp
@@ -5,16 +5,16 @@ ExamineLater: need to examine "optional"
ExamineLater: need to examine "optional-cohort"
Make_ExpandUse: examine optional
Make_ExpandUse: examine optional-cohort
-Examining optional...non-existent...up-to-date.
-Examining optional-cohort...non-existent...:: operator and no sources...out-of-date.
+Examining optional...nonexistent...up-to-date.
+Examining optional-cohort...nonexistent...:: operator and no sources...out-of-date.
: A leaf node using '::' is considered out-of-date.
- recheck(optional-cohort): update time from 0:00:00 Jan 01, 1970 to now
-Examining important...non-existent...modified before source "optional-cohort"...out-of-date.
+ recheck(optional-cohort): update time from nonexistent to now
+Examining important...nonexistent...modified before source "optional-cohort"...out-of-date.
: important is made.
- recheck(important): update time from 0:00:00 Jan 01, 1970 to now
-Examining all...non-existent...modified before source "important"...out-of-date.
+ recheck(important): update time from nonexistent to now
+Examining all...nonexistent...modified before source "important"...out-of-date.
: all is made.
- recheck(all): update time from 0:00:00 Jan 01, 1970 to now
-Examining .END...non-existent...non-existent and no sources...out-of-date.
- recheck(.END): update time from 0:00:00 Jan 01, 1970 to now
+ recheck(all): update time from nonexistent to now
+Examining .END...nonexistent...nonexistent and no sources...out-of-date.
+ recheck(.END): update time from nonexistent to now
exit status 0
diff --git a/unit-tests/depsrc.exp b/unit-tests/depsrc.exp
index 39a9383953dd..06165e6f9ac4 100644
--- a/unit-tests/depsrc.exp
+++ b/unit-tests/depsrc.exp
@@ -1 +1,4 @@
+: '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}.'
exit status 0
diff --git a/unit-tests/depsrc.mk b/unit-tests/depsrc.mk
index 15b27286de22..ab9d04c1d3a4 100644
--- a/unit-tests/depsrc.mk
+++ b/unit-tests/depsrc.mk
@@ -1,4 +1,4 @@
-# $NetBSD: depsrc.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: depsrc.mk,v 1.4 2020/12/22 19:38:44 rillig Exp $
#
# Tests for special sources (those starting with a dot, followed by
# uppercase letters) in dependency declarations, such as .PHONY.
@@ -7,5 +7,20 @@
# TODO: Test 'target: ${:U.SILENT}'
+# Demonstrate when exactly undefined variables are expanded in a dependency
+# declaration.
+target: .PHONY source-${DEFINED_LATER}
+#
+DEFINED_LATER= later
+#
+source-: .PHONY
+ : '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
+ : '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:
@:;
diff --git a/unit-tests/deptgt-begin-fail-indirect.exp b/unit-tests/deptgt-begin-fail-indirect.exp
new file mode 100644
index 000000000000..59575e839a4a
--- /dev/null
+++ b/unit-tests/deptgt-begin-fail-indirect.exp
@@ -0,0 +1,6 @@
+false
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/deptgt-begin-fail-indirect.mk b/unit-tests/deptgt-begin-fail-indirect.mk
new file mode 100644
index 000000000000..e50b6207b7a1
--- /dev/null
+++ b/unit-tests/deptgt-begin-fail-indirect.mk
@@ -0,0 +1,16 @@
+# $NetBSD: deptgt-begin-fail-indirect.mk,v 1.1 2020/11/24 19:02:59 rillig Exp $
+#
+# Test for a .BEGIN target whose dependency results in an error.
+# This stops make immediately and does not build the main targets.
+#
+# Between 2005-05-08 and 2020-11-24, a failing dependency of the .BEGIN node
+# would not stop make from running the main targets. In the end, the exit
+# status was even 0.
+
+.BEGIN: failing
+
+failing: .PHONY .NOTMAIN
+ false
+
+all:
+ : This is not made.
diff --git a/unit-tests/deptgt-begin-fail.exp b/unit-tests/deptgt-begin-fail.exp
new file mode 100644
index 000000000000..59575e839a4a
--- /dev/null
+++ b/unit-tests/deptgt-begin-fail.exp
@@ -0,0 +1,6 @@
+false
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/deptgt-begin-fail.mk b/unit-tests/deptgt-begin-fail.mk
new file mode 100644
index 000000000000..80cea25664ec
--- /dev/null
+++ b/unit-tests/deptgt-begin-fail.mk
@@ -0,0 +1,10 @@
+# $NetBSD: deptgt-begin-fail.mk,v 1.1 2020/11/24 19:02:59 rillig Exp $
+#
+# Test for a .BEGIN target whose command results in an error.
+# This stops make immediately and does not build the main targets.
+
+.BEGIN:
+ false
+
+all:
+ : This is not made.
diff --git a/unit-tests/deptgt-end-fail-all.exp b/unit-tests/deptgt-end-fail-all.exp
new file mode 100644
index 000000000000..2e2ee11f481a
--- /dev/null
+++ b/unit-tests/deptgt-end-fail-all.exp
@@ -0,0 +1,7 @@
+: Making all out of nothing.
+false
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/deptgt-end-fail-all.mk b/unit-tests/deptgt-end-fail-all.mk
new file mode 100644
index 000000000000..4bf160d480f6
--- /dev/null
+++ b/unit-tests/deptgt-end-fail-all.mk
@@ -0,0 +1,19 @@
+# $NetBSD: deptgt-end-fail-all.mk,v 1.2 2020/12/07 01:04:07 rillig Exp $
+#
+# Test whether the commands from the .END target are run even if there is
+# an error before. The manual page says "after everything else is done",
+# which leaves room for interpretation.
+#
+# Until 2020-12-07, the .END node was made even if the main nodes had failed.
+# This was not intended since the .END node had already been skipped if a
+# dependency of the main nodes had failed, just not if one of the main nodes
+# themselves had failed. This inconsistency was not worth keeping. To run
+# some commands on error, use the .ERROR target instead, see deptgt-error.mk.
+
+all: .PHONY
+ : Making ${.TARGET} out of nothing.
+ false
+
+.END:
+ : Making ${.TARGET} out of nothing.
+ false
diff --git a/unit-tests/deptgt-end-fail-indirect.exp b/unit-tests/deptgt-end-fail-indirect.exp
new file mode 100644
index 000000000000..17e509600617
--- /dev/null
+++ b/unit-tests/deptgt-end-fail-indirect.exp
@@ -0,0 +1,7 @@
+: all
+false
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/deptgt-end-fail-indirect.mk b/unit-tests/deptgt-end-fail-indirect.mk
new file mode 100644
index 000000000000..29346b8321fe
--- /dev/null
+++ b/unit-tests/deptgt-end-fail-indirect.mk
@@ -0,0 +1,16 @@
+# $NetBSD: deptgt-end-fail-indirect.mk,v 1.2 2020/12/06 21:22:04 rillig Exp $
+#
+# Tests for an error in a dependency of the .END node.
+#
+# Before 2020-11-25, an error in the .END target did not print the "Stop."
+# and exited with status 0. The cause for this was a missing condition in
+# Compat_Run in the handling of the .END node.
+
+all:
+ : $@
+
+.END: failing
+ : Making ${.TARGET} from ${.ALLSRC}.
+
+failing: .PHONY
+ false
diff --git a/unit-tests/deptgt-end-fail.exp b/unit-tests/deptgt-end-fail.exp
new file mode 100644
index 000000000000..9db907c209d5
--- /dev/null
+++ b/unit-tests/deptgt-end-fail.exp
@@ -0,0 +1,163 @@
+Test case all=ok all-dep=ok end=ok end-dep=ok.
+: Making all-dep out of nothing.
+: Making all from all-dep.
+: Making end-dep out of nothing.
+: Making .END from end-dep.
+exit status 0
+
+
+Test case all=ok all-dep=ok end=ok end-dep=ERR.
+: Making all-dep out of nothing.
+: Making all from all-dep.
+: Making end-dep out of nothing.
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ok all-dep=ok end=ERR end-dep=ok.
+: Making all-dep out of nothing.
+: Making all from all-dep.
+: Making end-dep out of nothing.
+: Making .END from end-dep.
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ok all-dep=ok end=ERR end-dep=ERR.
+: Making all-dep out of nothing.
+: Making all from all-dep.
+: Making end-dep out of nothing.
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ok all-dep=ERR end=ok end-dep=ok.
+: Making all-dep out of nothing.
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ok all-dep=ERR end=ok end-dep=ERR.
+: Making all-dep out of nothing.
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ok all-dep=ERR end=ERR end-dep=ok.
+: Making all-dep out of nothing.
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ok all-dep=ERR end=ERR end-dep=ERR.
+: Making all-dep out of nothing.
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ERR all-dep=ok end=ok end-dep=ok.
+: Making all-dep out of nothing.
+: Making all from all-dep.
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ERR all-dep=ok end=ok end-dep=ERR.
+: Making all-dep out of nothing.
+: Making all from all-dep.
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ERR all-dep=ok end=ERR end-dep=ok.
+: Making all-dep out of nothing.
+: Making all from all-dep.
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ERR all-dep=ok end=ERR end-dep=ERR.
+: Making all-dep out of nothing.
+: Making all from all-dep.
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ERR all-dep=ERR end=ok end-dep=ok.
+: Making all-dep out of nothing.
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ERR all-dep=ERR end=ok end-dep=ERR.
+: Making all-dep out of nothing.
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ERR all-dep=ERR end=ERR end-dep=ok.
+: Making all-dep out of nothing.
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+Test case all=ERR all-dep=ERR end=ERR end-dep=ERR.
+: Making all-dep out of nothing.
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
+
+
+exit status 0
diff --git a/unit-tests/deptgt-end-fail.mk b/unit-tests/deptgt-end-fail.mk
new file mode 100644
index 000000000000..57cdc7da8679
--- /dev/null
+++ b/unit-tests/deptgt-end-fail.mk
@@ -0,0 +1,69 @@
+# $NetBSD: deptgt-end-fail.mk,v 1.6 2020/12/07 01:04:07 rillig Exp $
+#
+# Tests for an errors in the main target, its dependencies,
+# the .END node and its dependencies.
+#
+# Before 2020-11-25, an error in the .END target did not print the "Stop.",
+# even though this was intended. The cause for this was a missing condition
+# in Compat_Run, in the code handling the .END node.
+
+test: .PHONY
+
+# The default stop-on-error mode is not as interesting to test since it
+# stops right after the first error.
+.MAKEFLAGS: -k
+
+.for all in ok ERR
+. for all-dep in ok ERR
+. for end in ok ERR
+. for end-dep in ok ERR
+. for target in ${all}-${all-dep}-${end}-${end-dep}
+test: ${target}
+${target}: .PHONY .SILENT
+ echo Test case all=${all} all-dep=${all-dep} end=${end} end-dep=${end-dep}.
+ ${MAKE} -r -f ${MAKEFILE} \
+ all=${all} all-dep=${all-dep} \
+ end=${end} end-dep=${end-dep} \
+ all; \
+ echo "exit status $$?"
+ echo
+ echo
+. endfor
+. endfor
+. endfor
+. endfor
+.endfor
+
+.if make(all)
+
+all all-dep end-dep: .PHONY
+
+CMD.ok= true
+CMD.ERR= false
+
+all: all-dep
+ : Making ${.TARGET} from ${.ALLSRC}.
+ @${CMD.${all}}
+
+all-dep:
+ : Making ${.TARGET} out of nothing.
+ @${CMD.${all-dep}}
+
+.END: end-dep
+ : Making ${.TARGET} from ${.ALLSRC}.
+ @${CMD.${end}}
+
+end-dep:
+ : Making ${.TARGET} out of nothing.
+ @${CMD.${end-dep}}
+
+.endif
+
+# Until 2020-12-07, several of the test cases printed "`all' not remade
+# because of errors.", followed by "exit status 0", which contradicted
+# each other.
+
+# Until 2020-12-07, '.END' was even made if 'all' failed, but if a dependency
+# of 'all' failed, it was skipped. This inconsistency was not needed for
+# anything and thus has been dropped. To run some commands on error, use the
+# .ERROR target instead, see deptgt-error.mk.
diff --git a/unit-tests/deptgt-suffixes.exp b/unit-tests/deptgt-suffixes.exp
index 65dc36cfe001..512e6d44a8be 100644
--- a/unit-tests/deptgt-suffixes.exp
+++ b/unit-tests/deptgt-suffixes.exp
@@ -3,5 +3,31 @@
# To:
# From:
# Search Path: . ..
+# ".src-left" (num 2, ref 2)
+# To: .tgt-right
+# From:
+# Search Path:
+# ".tgt-right" (num 3, ref 2)
+# To:
+# From: .src-left
+# Search Path:
+# ".tgt-left" (num 4, ref 2)
+# To:
+# From: .src-right
+# Search Path:
+# ".src-right" (num 5, ref 2)
+# To: .tgt-left
+# From:
+# Search Path:
#*** Transformations:
+.src-left.tgt-right:
+ : Making ${.TARGET} from ${.IMPSRC}.
+
+.src-right.tgt-left:
+ : Making ${.TARGET} from ${.IMPSRC}.
+
+: Making deptgt-suffixes.src-left out of nothing.
+: Making deptgt-suffixes.tgt-right from deptgt-suffixes.src-left.
+: Making deptgt-suffixes.src-right out of nothing.
+: Making deptgt-suffixes.tgt-left from deptgt-suffixes.src-right.
exit status 0
diff --git a/unit-tests/deptgt-suffixes.mk b/unit-tests/deptgt-suffixes.mk
index 791ff5eb5f03..15b4dc08161c 100644
--- a/unit-tests/deptgt-suffixes.mk
+++ b/unit-tests/deptgt-suffixes.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt-suffixes.mk,v 1.3 2020/08/28 04:05:35 rillig Exp $
+# $NetBSD: deptgt-suffixes.mk,v 1.4 2020/11/21 21:54:42 rillig Exp $
#
# Tests for the special target .SUFFIXES in dependency declarations.
#
@@ -8,11 +8,28 @@
.MAKEFLAGS: -dg1
+.MAIN: all
+
.SUFFIXES: .custom-null
# TODO: What is the effect of this? How is it useful?
.NULL: .custom-null
.PATH.custom-null: . ..
-all:
- @:;
+# The order in which the suffixes are listed doesn't matter.
+# Here, they are listed from source to target, just like in the transformation
+# rule below it.
+.SUFFIXES: .src-left .tgt-right
+deptgt-suffixes.src-left:
+ : Making ${.TARGET} out of nothing.
+.src-left.tgt-right:
+ : Making ${.TARGET} from ${.IMPSRC}.
+all: deptgt-suffixes.tgt-right
+
+# Here, the target is listed earlier than the source.
+.SUFFIXES: .tgt-left .src-right
+deptgt-suffixes.src-right:
+ : Making ${.TARGET} out of nothing.
+.src-right.tgt-left:
+ : Making ${.TARGET} from ${.IMPSRC}.
+all: deptgt-suffixes.tgt-left
diff --git a/unit-tests/deptgt.exp b/unit-tests/deptgt.exp
index fee0563f5b37..b2aeaa5a2850 100644
--- a/unit-tests/deptgt.exp
+++ b/unit-tests/deptgt.exp
@@ -8,6 +8,7 @@ ParseDoDependency(: empty-source)
ParseReadLine (37): ' : command for empty targets list'
ParseReadLine (38): '.MAKEFLAGS: -d0'
ParseDoDependency(.MAKEFLAGS: -d0)
+make: "deptgt.mk" line 46: Unknown modifier 'Z'
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/deptgt.mk b/unit-tests/deptgt.mk
index 83f81b6f58ed..09f381715e6d 100644
--- a/unit-tests/deptgt.mk
+++ b/unit-tests/deptgt.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt.mk,v 1.9 2020/11/15 11:57:00 rillig Exp $
+# $NetBSD: deptgt.mk,v 1.10 2020/12/27 18:20:26 rillig Exp $
#
# Tests for special targets like .BEGIN or .SUFFIXES in dependency
# declarations.
@@ -37,5 +37,13 @@ ${:U}: empty-source
: command for empty targets list
.MAKEFLAGS: -d0
+# Just to show that a malformed expression is only expanded once in
+# ParseDependencyTargetWord. The only way to produce an expression that
+# is well-formed on the first expansion and ill-formed on the second
+# expansion would be to use the variable modifier '::=' to modify the
+# targets. This in turn would be such an extreme and unreliable edge case
+# that nobody uses it.
+$$$$$$$${:U:Z}:
+
all:
@:;
diff --git a/unit-tests/directive-elif.exp b/unit-tests/directive-elif.exp
index 6219b4896795..6856494023d7 100644
--- a/unit-tests/directive-elif.exp
+++ b/unit-tests/directive-elif.exp
@@ -1,17 +1,21 @@
-make: "directive-elif.mk" line 7: begin .elif misspellings tests, part 1
-make: "directive-elif.mk" line 9: 1-then
-make: "directive-elif.mk" line 18: begin .elif misspellings tests, part 2
-make: "directive-elif.mk" line 29: begin .elif misspellings tests, part 3
-make: "directive-elif.mk" line 41: which branch is taken on misspelling after false?
-make: "directive-elif.mk" line 49: else
-make: "directive-elif.mk" line 52: which branch is taken on misspelling after true?
-make: "directive-elif.mk" line 54: 1-then
-make: "directive-elif.mk" line 55: Unknown directive "elsif"
-make: "directive-elif.mk" line 56: 1-elsif
-make: "directive-elif.mk" line 57: Unknown directive "elsif"
-make: "directive-elif.mk" line 58: 2-elsif
-make: "directive-elif.mk" line 64: if-less elif
-make: "directive-elif.mk" line 69: warning: extra elif
+make: "directive-elif.mk" line 47: Unknown directive "elsif"
+make: "directive-elif.mk" line 52: This branch is taken.
+make: "directive-elif.mk" line 60: Unknown directive "elsif"
+make: "directive-elif.mk" line 63: This branch is taken.
+make: "directive-elif.mk" line 69: This branch is taken.
+make: "directive-elif.mk" line 89: Unknown directive "elsif"
+make: "directive-elif.mk" line 90: This misspelling is detected.
+make: "directive-elif.mk" line 91: This branch is taken because of the .else.
+make: "directive-elif.mk" line 109: What happens on misspelling in a skipped branch?
+make: "directive-elif.mk" line 119: else
+make: "directive-elif.mk" line 122: What happens on misspelling in a taken branch?
+make: "directive-elif.mk" line 124: 1-then
+make: "directive-elif.mk" line 125: Unknown directive "elsif"
+make: "directive-elif.mk" line 126: 1-elsif
+make: "directive-elif.mk" line 127: Unknown directive "elsif"
+make: "directive-elif.mk" line 128: 2-elsif
+make: "directive-elif.mk" line 134: if-less elif
+make: "directive-elif.mk" line 139: warning: extra elif
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/directive-elif.mk b/unit-tests/directive-elif.mk
index f9a43abfffc1..e7b89beec4e9 100644
--- a/unit-tests/directive-elif.mk
+++ b/unit-tests/directive-elif.mk
@@ -1,55 +1,125 @@
-# $NetBSD: directive-elif.mk,v 1.6 2020/11/12 19:46:36 rillig Exp $
+# $NetBSD: directive-elif.mk,v 1.7 2020/12/19 19:49:01 rillig Exp $
#
# Tests for the .elif directive.
+#
+# Misspellings of the .elif directive are not always detected. They are only
+# detected if the conditional branch directly above it is taken. In all other
+# cases, make skips over the skipped branch as fast as possible, looking only
+# at the initial '.' of the line and whether the directive is one of the known
+# conditional directives. All other directives are silently ignored, as they
+# could be variable assignments or dependency declarations as well, and
+# deciding this would cost time.
+
# TODO: Implementation
-.info begin .elif misspellings tests, part 1
-.if 1
-. info 1-then
-.elif 1 # ok
-. info 1-elif
-.elsif 1 # oops: misspelled
-. info 1-elsif
-.elseif 1 # oops: misspelled
-. info 1-elseif
+
+# Misspelling '.elsif' below an .if branch that is not taken.
+.if 0
+. info This branch is not taken.
+# As of 2020-12-19, the misspelling is not recognized as a conditional
+# directive and is thus silently skipped.
+#
+# Since the .if condition evaluated to false, this whole branch is not taken.
+.elsif 0
+. info XXX: This misspelling is not detected.
+. info This branch is not taken.
+# Even if the misspelling were detected, the branch would not be taken
+# since the condition of the '.elsif' evaluates to false as well.
.endif
-.info begin .elif misspellings tests, part 2
+
+# Misspelling '.elsif' below an .if branch that is not taken.
.if 0
-. info 0-then
-.elif 0 # ok
-. info 0-elif
-.elsif 0 # oops: misspelled
-. info 0-elsif
-.elseif 0 # oops: misspelled
-. info 0-elseif
+. info This branch is not taken.
+# As of 2020-12-19, the misspelling is not recognized as a conditional
+# directive and is thus silently skipped. Since the .if condition evaluated
+# to false, this whole branch is not taken.
+.elsif 1
+. info XXX: This misspelling is not detected.
+# If the misspelling were detected, this branch would be taken.
+.endif
+
+
+# Misspelling '.elsif' below an .if branch that is taken.
+.if 1
+# This misspelling is in an active branch and is therefore detected.
+.elsif 0
+# The only thing that make detects here is a misspelled directive, make
+# doesn't recognize that it was meant to be a conditional directive.
+# Therefore the branch continues here, even though the '.elsif' condition
+# evaluates to false.
+. info This branch is taken.
+.endif
+
+
+# Misspelling '.elsif' below an .if branch that is taken.
+.if 1
+# As of 2020-12-19, the misspelling is in an active branch and is therefore
+# detected.
+.elsif 1
+# Since both conditions evaluate to true, this branch is taken no matter
+# whether make detects a misspelling or not.
+. info This branch is taken.
.endif
-.info begin .elif misspellings tests, part 3
+
+# Misspelling '.elsif' in a skipped branch below a branch that was taken.
+.if 1
+. info This branch is taken.
+.elif 0
+. info This branch is not taken.
+.elsif 1
+. info XXX: This misspelling is not detected.
+.endif
+
+
+# Misspelling '.elsif' in an .else branch that is not taken.
+.if 1
+.else
+. info This branch is not taken.
+.elsif 1
+. info XXX: This misspelling is not detected.
+.endif
+
+
+# Misspelling '.elsif' in an .else branch that is taken.
.if 0
-. info 0-then
-.elsif 0 # oops: misspelled
-. info 0-elsif
+.else
+.elsif 1
+. info This misspelling is detected.
+. info This branch is taken because of the .else.
.endif
+
+
+# Misspellings for .elif in a .elif branch that is not taken.
.if 0
-. info 0-then
-.elseif 0 # oops: misspelled
-. info 0-elseif
+. info This branch is not taken.
+.elif 0 # ok
+. info This branch is not taken.
+.elsif 0
+. info XXX: This misspelling is not detected.
+. info This branch is not taken.
+.elseif 0
+. info XXX: This misspelling is not detected.
+. info This branch is not taken.
.endif
-.info which branch is taken on misspelling after false?
+
+.info What happens on misspelling in a skipped branch?
.if 0
. info 0-then
.elsif 1
+. info XXX: This misspelling is not detected.
. info 1-elsif
.elsif 2
+. info XXX: This misspelling is not detected.
. info 2-elsif
.else
. info else
.endif
-.info which branch is taken on misspelling after true?
+.info What happens on misspelling in a taken branch?
.if 1
. info 1-then
.elsif 1
@@ -65,7 +135,7 @@
.if 1
.else
-# Expect: "warning: if-less elif"
+# Expect: "warning: extra elif"
.elif
.endif
diff --git a/unit-tests/directive-else.exp b/unit-tests/directive-else.exp
index ca60595745a9..138e893ffa88 100644
--- a/unit-tests/directive-else.exp
+++ b/unit-tests/directive-else.exp
@@ -1,11 +1,11 @@
-make: "directive-else.mk" line 11: The .else directive does not take arguments.
-make: "directive-else.mk" line 12: ok
-make: "directive-else.mk" line 16: ok
-make: "directive-else.mk" line 17: The .else directive does not take arguments.
-make: "directive-else.mk" line 22: if-less else
-make: "directive-else.mk" line 28: ok
-make: "directive-else.mk" line 29: warning: extra else
-make: "directive-else.mk" line 42: The .else directive does not take arguments.
+make: "directive-else.mk" line 14: The .else directive does not take arguments.
+make: "directive-else.mk" line 15: ok
+make: "directive-else.mk" line 19: ok
+make: "directive-else.mk" line 21: The .else directive does not take arguments.
+make: "directive-else.mk" line 26: if-less else
+make: "directive-else.mk" line 32: ok
+make: "directive-else.mk" line 33: warning: extra else
+make: "directive-else.mk" line 45: The .else directive does not take arguments.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/directive-else.mk b/unit-tests/directive-else.mk
index fdd94007a1fa..794057110ef7 100644
--- a/unit-tests/directive-else.mk
+++ b/unit-tests/directive-else.mk
@@ -1,19 +1,23 @@
-# $NetBSD: directive-else.mk,v 1.6 2020/11/13 09:01:59 rillig Exp $
+# $NetBSD: directive-else.mk,v 1.7 2020/12/14 22:17:11 rillig Exp $
#
# Tests for the .else directive.
+#
+# Since 2020-11-13, an '.else' followed by extraneous text generates a parse
+# error in -dL (lint) mode.
+#
+# Since 2020-12-15, an '.else' followed by extraneous text always generates
+# a parse error.
-.MAKEFLAGS: -dL # To enable the check for ".else <cond>"
-
-# The .else directive does not take any arguments.
-# As of 2020-08-29, make doesn't warn about this.
.if 0
. warning must not be reached
+# The .else directive does not take any arguments.
.else 123
. info ok
.endif
.if 1
. info ok
+# The .else directive does not take any arguments.
.else 123
. warning must not be reached
.endif
@@ -37,7 +41,6 @@
.endif
# A variable expression does count as an argument, even if it is empty.
-# XXX: This should be a parse error.
.if 0
.else ${:U}
.endif
diff --git a/unit-tests/directive-endfor.exp b/unit-tests/directive-endfor.exp
new file mode 100644
index 000000000000..7e243a8f67e6
--- /dev/null
+++ b/unit-tests/directive-endfor.exp
@@ -0,0 +1,4 @@
+make: "directive-endfor.mk" line 9: for-less endfor
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-endfor.mk b/unit-tests/directive-endfor.mk
new file mode 100644
index 000000000000..b0c37f388504
--- /dev/null
+++ b/unit-tests/directive-endfor.mk
@@ -0,0 +1,9 @@
+# $NetBSD: directive-endfor.mk,v 1.1 2020/12/30 14:50:08 rillig Exp $
+#
+# Test for the directive .endfor, which ends a .for loop.
+#
+# See also:
+# directive-for.mk
+
+# An .endfor without a corresponding .for is a parse error.
+.endfor
diff --git a/unit-tests/directive-endif.exp b/unit-tests/directive-endif.exp
index 39a9383953dd..286d85244eae 100644
--- a/unit-tests/directive-endif.exp
+++ b/unit-tests/directive-endif.exp
@@ -1 +1,8 @@
-exit status 0
+make: "directive-endif.mk" line 18: The .endif directive does not take arguments.
+make: "directive-endif.mk" line 23: The .endif directive does not take arguments.
+make: "directive-endif.mk" line 33: The .endif directive does not take arguments.
+make: "directive-endif.mk" line 39: The .endif directive does not take arguments.
+make: "directive-endif.mk" line 45: Unknown directive "endifx"
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-endif.mk b/unit-tests/directive-endif.mk
index b0b531af2f06..10dd6ce22ce8 100644
--- a/unit-tests/directive-endif.mk
+++ b/unit-tests/directive-endif.mk
@@ -1,7 +1,10 @@
-# $NetBSD: directive-endif.mk,v 1.3 2020/11/12 22:40:11 rillig Exp $
+# $NetBSD: directive-endif.mk,v 1.5 2020/12/14 21:56:17 rillig Exp $
#
# Tests for the .endif directive.
#
+# Since 2020-12-15, the .endif directive no longer accepts arguments.
+# The manual page had never allowed that, but the code didn't check it.
+#
# See also:
# Cond_EvalLine
@@ -10,18 +13,37 @@
.MAKEFLAGS: -dL
# Error: .endif does not take arguments
-# XXX: Missing error message
.if 0
+# Since 2020-12-15, complain about the extra text after the 'endif'.
.endif 0
# Error: .endif does not take arguments
-# XXX: Missing error message
.if 1
+# Since 2020-12-15, complain about the extra text after the 'endif'.
.endif 1
# Comments are allowed after an '.endif'.
.if 2
.endif # comment
+# Only whitespace and comments are allowed after an '.endif', but nothing
+# else.
+.if 1
+# Since 2020-12-15, complain about the extra text after the 'endif'.
+.endif0
+
+# Only whitespace and comments are allowed after an '.endif', but nothing
+# else.
+.if 1
+# Since 2020-12-15, complain about the extra text after the 'endif'.
+.endif/
+
+# After an '.endif', no other letter must occur. This 'endifx' is not
+# parsed as an 'endif', therefore another '.endif' must follow to balance
+# the directives.
+.if 1
+.endifx
+.endif # to close the preceding '.if'
+
all:
@:;
diff --git a/unit-tests/directive-error.mk b/unit-tests/directive-error.mk
index 3980016221ab..50a0c6c0e84c 100644
--- a/unit-tests/directive-error.mk
+++ b/unit-tests/directive-error.mk
@@ -1,6 +1,8 @@
-# $NetBSD: directive-error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-error.mk,v 1.3 2020/12/13 01:07:54 rillig Exp $
#
-# Tests for the .error directive.
+# Tests for the .error directive, which prints an error message and exits
+# immediately, unlike other "fatal" parse errors, which continue to parse
+# until the end of the current top-level makefile.
# TODO: Implementation
diff --git a/unit-tests/directive-export-env.mk b/unit-tests/directive-export-env.mk
index 82b5e8087c6d..2ef2ceaf788c 100644
--- a/unit-tests/directive-export-env.mk
+++ b/unit-tests/directive-export-env.mk
@@ -1,12 +1,10 @@
-# $NetBSD: directive-export-env.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $
+# $NetBSD: directive-export-env.mk,v 1.4 2020/12/13 01:07:54 rillig Exp $
#
# Tests for the .export-env directive.
# TODO: Implementation
-.export-en # oops: misspelled
.export-env
-.export-environment # oops: misspelled
all:
@:;
diff --git a/unit-tests/directive-export-impl.exp b/unit-tests/directive-export-impl.exp
new file mode 100644
index 000000000000..c3ac940d2df0
--- /dev/null
+++ b/unit-tests/directive-export-impl.exp
@@ -0,0 +1,56 @@
+ParseReadLine (21): 'UT_VAR= <${REF}>'
+Global:UT_VAR = <${REF}>
+ParseReadLine (28): '.export UT_VAR'
+Global:.MAKE.EXPORTED = UT_VAR
+ParseReadLine (32): ': ${UT_VAR:N*}'
+Var_Parse: ${UT_VAR:N*} with VARE_UNDEFERR|VARE_WANTRES
+Var_Parse: ${REF}> with VARE_UNDEFERR|VARE_WANTRES
+Applying ${UT_VAR:N...} to "<>" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none)
+Pattern[UT_VAR] for [<>] is [*]
+ModifyWords: split "<>" into 1 words
+Result of ${UT_VAR:N*} is "" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none)
+ParseDoDependency(: )
+CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>"
+Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" with VARE_UNDEFERR|VARE_WANTRES
+Applying ${:!...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF)
+Modifier part: "echo "$UT_VAR""
+Var_Parse: ${.MAKE.EXPORTED:O:u} with VARE_WANTRES
+Applying ${.MAKE.EXPORTED:O} to "UT_VAR" (VARE_WANTRES, none, none)
+Result of ${.MAKE.EXPORTED:O} is "UT_VAR" (VARE_WANTRES, none, none)
+Applying ${.MAKE.EXPORTED:u} to "UT_VAR" (VARE_WANTRES, none, none)
+Result of ${.MAKE.EXPORTED:u} is "UT_VAR" (VARE_WANTRES, none, none)
+Var_Parse: ${UT_VAR} with VARE_WANTRES
+Var_Parse: ${REF}> with VARE_WANTRES
+Result of ${:!echo "\$UT_VAR"!} is "<>" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF)
+lhs = "<>", rhs = "<>", op = !=
+ParseReadLine (49): ': ${UT_VAR:N*}'
+Var_Parse: ${UT_VAR:N*} with VARE_UNDEFERR|VARE_WANTRES
+Var_Parse: ${REF}> with VARE_UNDEFERR|VARE_WANTRES
+Applying ${UT_VAR:N...} to "<>" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none)
+Pattern[UT_VAR] for [<>] is [*]
+ModifyWords: split "<>" into 1 words
+Result of ${UT_VAR:N*} is "" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none)
+ParseDoDependency(: )
+ParseReadLine (53): 'REF= defined'
+Global:REF = defined
+CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<defined>"
+Var_Parse: ${:!echo "\$UT_VAR"!} != "<defined>" with VARE_UNDEFERR|VARE_WANTRES
+Applying ${:!...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF)
+Modifier part: "echo "$UT_VAR""
+Var_Parse: ${.MAKE.EXPORTED:O:u} with VARE_WANTRES
+Applying ${.MAKE.EXPORTED:O} to "UT_VAR" (VARE_WANTRES, none, none)
+Result of ${.MAKE.EXPORTED:O} is "UT_VAR" (VARE_WANTRES, none, none)
+Applying ${.MAKE.EXPORTED:u} to "UT_VAR" (VARE_WANTRES, none, none)
+Result of ${.MAKE.EXPORTED:u} is "UT_VAR" (VARE_WANTRES, none, none)
+Var_Parse: ${UT_VAR} with VARE_WANTRES
+Var_Parse: ${REF}> with VARE_WANTRES
+Result of ${:!echo "\$UT_VAR"!} is "<defined>" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF)
+lhs = "<defined>", rhs = "<defined>", op = !=
+ParseReadLine (61): 'all:'
+ParseDoDependency(all:)
+Global:.ALLTARGETS = all
+ParseReadLine (62): '.MAKEFLAGS: -d0'
+ParseDoDependency(.MAKEFLAGS: -d0)
+Global:.MAKEFLAGS = -r -k -d cpv -d
+Global:.MAKEFLAGS = -r -k -d cpv -d 0
+exit status 0
diff --git a/unit-tests/directive-export-impl.mk b/unit-tests/directive-export-impl.mk
new file mode 100644
index 000000000000..556e5352d1c3
--- /dev/null
+++ b/unit-tests/directive-export-impl.mk
@@ -0,0 +1,62 @@
+# $NetBSD: directive-export-impl.mk,v 1.1 2020/12/29 01:45:06 rillig Exp $
+#
+# Test for the implementation of exporting variables to child processes.
+# This involves marking variables for export, actually exporting them,
+# or marking them for being re-exported.
+#
+# See also:
+# Var_Export
+# ExportVar
+# VarExportedMode (global)
+# VAR_EXPORTED (per variable)
+# VAR_REEXPORT (per variable)
+# VarExportMode (per call of Var_Export and ExportVar)
+
+: ${:U:sh} # side effect: initialize .SHELL
+
+.MAKEFLAGS: -dcpv
+
+# This is a variable that references another variable. At this point, the
+# other variable is still undefined.
+UT_VAR= <${REF}>
+
+# At this point, ExportVar("UT_VAR", VEM_PLAIN) is called. Since the
+# variable value refers to another variable, ExportVar does not actually
+# export the variable but only marks it as VAR_EXPORTED and VAR_REEXPORT.
+# After that, ExportVars registers the variable name in .MAKE.EXPORTED.
+# That's all for now.
+.export UT_VAR
+
+# Evaluating this expression shows the variable flags in the debug log,
+# which are VAR_EXPORTED|VAR_REEXPORT.
+: ${UT_VAR:N*}
+
+# At the last moment before actually forking off the child process for the
+# :!...! modifier, Cmd_Exec calls Var_ReexportVars to have all relevant
+# variables exported. Since this variable has both of the above-mentioned
+# flags set, it is actually exported to the environment. The variable flags
+# are not modified though, since the next time the :!...! modifier is
+# evaluated, the referenced variables could have changed, therefore the
+# variable will be exported anew for each ':sh' modifier, ':!...!' modifier,
+# '!=' variable assignment.
+.if ${:!echo "\$UT_VAR"!} != "<>"
+. error
+.endif
+
+# Evaluating this expression shows the variable flags in the debug log,
+# which are still VAR_EXPORTED|VAR_REEXPORT, which means that the variable
+# is still marked as being re-exported for each child process.
+: ${UT_VAR:N*}
+
+# Now the referenced variable gets defined. This does not influence anything
+# in the process of exporting the variable value, though.
+REF= defined
+
+# Nothing surprising here. The variable UT_VAR gets exported, and this time,
+# REF is defined and gets expanded into the exported environment variable.
+.if ${:!echo "\$UT_VAR"!} != "<defined>"
+. error
+.endif
+
+all:
+.MAKEFLAGS: -d0
diff --git a/unit-tests/directive-export-literal.mk b/unit-tests/directive-export-literal.mk
index 51e5b522a3b9..5fafa4a7282d 100644
--- a/unit-tests/directive-export-literal.mk
+++ b/unit-tests/directive-export-literal.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-export-literal.mk,v 1.6 2020/11/03 17:17:31 rillig Exp $
+# $NetBSD: directive-export-literal.mk,v 1.7 2020/12/13 01:07:54 rillig Exp $
#
# Tests for the .export-literal directive, which exports a variable value
# without expanding it.
@@ -7,9 +7,7 @@ UT_VAR= value with ${UNEXPANDED} expression
.export-literal UT_VAR
-.export-litera # oops: misspelled
.export-literal # oops: missing argument
-.export-literally # oops: misspelled
all:
@echo "$$UT_VAR"
diff --git a/unit-tests/directive-export.exp b/unit-tests/directive-export.exp
index bd828b63c10c..39a9383953dd 100644
--- a/unit-tests/directive-export.exp
+++ b/unit-tests/directive-export.exp
@@ -1,4 +1 @@
-make: "directive-export.mk" line 25: Unknown directive "expor"
-make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
-exit status 1
+exit status 0
diff --git a/unit-tests/directive-export.mk b/unit-tests/directive-export.mk
index bae50aecbdaf..40fda0968cb0 100644
--- a/unit-tests/directive-export.mk
+++ b/unit-tests/directive-export.mk
@@ -1,12 +1,19 @@
-# $NetBSD: directive-export.mk,v 1.4 2020/11/03 17:17:31 rillig Exp $
+# $NetBSD: directive-export.mk,v 1.6 2020/12/13 01:07:54 rillig Exp $
#
# Tests for the .export directive.
+#
+# See also:
+# directive-misspellings.mk
# TODO: Implementation
INDIRECT= indirect
VAR= value $$ ${INDIRECT}
+# Before 2020-12-13, this unusual expression invoked undefined behavior since
+# it accessed out-of-bounds memory via Var_Export -> ExportVar -> MayExport.
+.export ${:U }
+
# A variable is exported using the .export directive.
# During that, its value is expanded, just like almost everywhere else.
.export VAR
@@ -21,11 +28,8 @@ VAR= value $$ ${INDIRECT}
. error
.endif
-# Tests for parsing the .export directive.
-.expor # misspelled
-.export # oops: missing argument
-.export VARNAME
-.exporting works # oops: misspelled
+# No argument means to export all variables.
+.export
all:
@:;
diff --git a/unit-tests/directive-for-errors.exp b/unit-tests/directive-for-errors.exp
new file mode 100644
index 000000000000..6088a93c9a4a
--- /dev/null
+++ b/unit-tests/directive-for-errors.exp
@@ -0,0 +1,22 @@
+make: "directive-for-errors.mk" line 7: Unknown directive "fori"
+make: "directive-for-errors.mk" line 8: warning:
+make: "directive-for-errors.mk" line 9: for-less endfor
+make: "directive-for-errors.mk" line 19: Unknown directive "for"
+make: "directive-for-errors.mk" line 20: warning:
+make: "directive-for-errors.mk" line 21: for-less endfor
+make: "directive-for-errors.mk" line 37: Dollar $ 1 1 and backslash 2 2 2.
+make: "directive-for-errors.mk" line 37: Dollar $ 3 3 and backslash 4 4 4.
+make: "directive-for-errors.mk" line 43: no iteration variables in for
+make: "directive-for-errors.mk" line 47: warning: Should not be reached.
+make: "directive-for-errors.mk" line 48: for-less endfor
+make: "directive-for-errors.mk" line 53: Wrong number of words (5) in .for substitution list with 3 variables
+make: "directive-for-errors.mk" line 64: missing `in' in for
+make: "directive-for-errors.mk" line 66: warning: Should not be reached.
+make: "directive-for-errors.mk" line 67: for-less endfor
+make: "directive-for-errors.mk" line 73: Unknown modifier 'Z'
+make: "directive-for-errors.mk" line 74: warning: Should not be reached.
+make: "directive-for-errors.mk" line 74: warning: Should not be reached.
+make: "directive-for-errors.mk" line 74: warning: Should not be reached.
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-for-errors.mk b/unit-tests/directive-for-errors.mk
new file mode 100644
index 000000000000..7890e2375af4
--- /dev/null
+++ b/unit-tests/directive-for-errors.mk
@@ -0,0 +1,75 @@
+# $NetBSD: directive-for-errors.mk,v 1.1 2020/12/31 03:05:12 rillig Exp $
+#
+# Tests for error handling in .for loops.
+
+# A .for directive must be followed by whitespace, everything else results
+# in a parse error.
+.fori in 1 2 3
+. warning ${i}
+.endfor
+
+# A slash is not whitespace, therefore this is not parsed as a .for loop.
+#
+# XXX: The error message is misleading though. As of 2020-12-31, it says
+# "Unknown directive "for"", but that directive is actually known. This is
+# because ForEval does not detect the .for loop as such, so parsing
+# continues in ParseLine > ParseDependency > ParseDoDependency >
+# ParseDoDependencyTargets > ParseErrorNoDependency, and there the directive
+# name is parsed a bit differently.
+.for/i in 1 2 3
+. warning ${i}
+.endfor
+
+# As of 2020-12-31, the variable name can be an arbitrary word, it just needs
+# to be separated by whitespace. Even '$' and '\' are valid variable names,
+# which is not useful in practice.
+#
+# The '$$' is not replaced with the values '1' or '3' from the .for loop,
+# instead it is kept as-is, and when the .info directive expands its argument,
+# each '$$' gets replaced with a single '$'. The "long variable expression"
+# ${$} gets replaced though, even though this would be a parse error everywhere
+# outside a .for loop.
+#
+# The '\' on the other hand is treated as a normal variable name.
+${:U\$}= dollar # see whether the "variable" '$' is local
+${:U\\}= backslash # see whether the "variable" '\' is local
+.for $ \ in 1 2 3 4
+. info Dollar $$ ${$} $($) and backslash $\ ${\} $(\).
+.endfor
+
+# If there are no variables, there is no point in expanding the .for loop
+# since this would end up in an endless loop, each time consuming 0 of the
+# 3 values.
+.for in 1 2 3
+# XXX: This should not be reached. It should be skipped, as already done
+# when the number of values is not a multiple of the number of variables,
+# see below.
+. warning Should not be reached.
+.endfor
+
+# There are 3 variables and 5 values. These 5 values cannot be split evenly
+# among the variables, therefore the loop is not expanded at all, it is
+# rather skipped.
+.for a b c in 1 2 3 4 5
+. warning Should not be reached.
+.endfor
+
+# The list of values after the 'in' may be empty, no matter if this emptiness
+# comes from an empty expansion or even from a syntactically empty line.
+.for i in
+. info Would be reached if there were items to loop over.
+.endfor
+
+# A missing 'in' should parse the .for loop but skip the body.
+.for i : k
+# XXX: As of 2020-12-31, this line is reached once.
+. warning Should not be reached.
+.endfor
+
+# A malformed modifier should be detected and skip the body of the loop.
+#
+# XXX: As of 2020-12-31, Var_Subst doesn't report any errors, therefore
+# the loop body is expanded as if no error had happened.
+.for i in 1 2 ${:U3:Z} 4
+. warning Should not be reached.
+.endfor
diff --git a/unit-tests/directive-for-escape.exp b/unit-tests/directive-for-escape.exp
new file mode 100644
index 000000000000..3d2d2ec744aa
--- /dev/null
+++ b/unit-tests/directive-for-escape.exp
@@ -0,0 +1,74 @@
+For: end for 1
+For: loop body:
+. info ${:U!"#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~}
+make: Unclosed variable specification (expecting '}') for "" (value "!"") modifier U
+make: "directive-for-escape.mk" line 19: !"
+For: end for 1
+For: loop body:
+. info ${:U!"\\\\#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~}
+make: Unclosed variable specification (expecting '}') for "" (value "!"\\") modifier U
+make: "directive-for-escape.mk" line 29: !"\\
+For: end for 1
+For: loop body:
+. info ${:U\$}
+make: "directive-for-escape.mk" line 41: $
+For: loop body:
+. info ${:U${V}}
+make: "directive-for-escape.mk" line 41: value
+For: loop body:
+. info ${:U${V:=-with-modifier}}
+make: "directive-for-escape.mk" line 41: value-with-modifier
+For: loop body:
+. info ${:U$(V)}
+make: "directive-for-escape.mk" line 41: value
+For: loop body:
+. info ${:U$(V:=-with-modifier)}
+make: "directive-for-escape.mk" line 41: value-with-modifier
+For: end for 1
+For: loop body:
+. info ${:U\${UNDEF\:U\\$\\$}
+make: "directive-for-escape.mk" line 52: ${UNDEF:U\$
+For: loop body:
+. info ${:U{{\}\}}
+make: "directive-for-escape.mk" line 52: {{}}
+For: loop body:
+. info ${:Uend\}}
+make: "directive-for-escape.mk" line 52: end}
+For: end for 1
+For: loop body:
+. info ${:U\$}
+make: "directive-for-escape.mk" line 60: $
+For: end for 1
+For: loop body:
+. info ${NUMBERS} ${:Ureplaced}
+make: "directive-for-escape.mk" line 68: one two three replaced
+For: end for 1
+For: loop body:
+. info ${:Ureplaced}
+make: "directive-for-escape.mk" line 78: replaced
+For: end for 1
+For: loop body:
+. info . $$i: ${:Uinner}
+. info . $${i}: ${:Uinner}
+. info . $${i:M*}: ${:Uinner:M*}
+. info . $$(i): $(:Uinner)
+. info . $$(i:M*): $(:Uinner:M*)
+. info . $${i$${:U}}: ${i${:U}}
+. info . $${i\}}: ${:Uinner\}} # XXX: unclear why SubstVarLong needs this
+. info . $${i2}: ${i2}
+. info . $${i,}: ${i,}
+. info . adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner}
+make: "directive-for-escape.mk" line 86: . $i: inner
+make: "directive-for-escape.mk" line 87: . ${i}: inner
+make: "directive-for-escape.mk" line 88: . ${i:M*}: inner
+make: "directive-for-escape.mk" line 89: . $(i): inner
+make: "directive-for-escape.mk" line 90: . $(i:M*): inner
+make: "directive-for-escape.mk" line 91: . ${i${:U}}: outer
+make: "directive-for-escape.mk" line 92: . ${i\}}: inner}
+make: "directive-for-escape.mk" line 93: . ${i2}: two
+make: "directive-for-escape.mk" line 94: . ${i,}: comma
+make: "directive-for-escape.mk" line 95: . adjacent: innerinnerinnerinner
+make: no target to make.
+
+make: stopped in unit-tests
+exit status 2
diff --git a/unit-tests/directive-for-escape.mk b/unit-tests/directive-for-escape.mk
new file mode 100644
index 000000000000..0bd2af68625a
--- /dev/null
+++ b/unit-tests/directive-for-escape.mk
@@ -0,0 +1,96 @@
+# $NetBSD: directive-for-escape.mk,v 1.3 2020/12/31 14:26:37 rillig Exp $
+#
+# Test escaping of special characters in the iteration values of a .for loop.
+# These values get expanded later using the :U variable modifier, and this
+# escaping and unescaping must pass all characters and strings effectively
+# unmodified.
+
+.MAKEFLAGS: -df
+
+# Even though the .for loops takes quotes into account when splitting the
+# string into words, the quotes don't need to be balances, as of 2020-12-31.
+# This could be considered a bug.
+ASCII= !"\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
+
+# XXX: As of 2020-12-31, the '#' is not preserved in the expanded body of
+# the loop since it would not need only the escaping for the :U variable
+# modifier but also the escaping for the line-end comment.
+.for chars in ${ASCII}
+. info ${chars}
+.endfor
+
+# As of 2020-12-31, using 2 backslashes before be '#' would treat the '#'
+# as comment character. Using 3 backslashes doesn't help either since
+# then the situation is essentially the same as with 1 backslash.
+# This means that a '#' sign cannot be passed in the value of a .for loop
+# at all.
+ASCII.2020-12-31= !"\\\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
+.for chars in ${ASCII.2020-12-31}
+. info ${chars}
+.endfor
+
+# Cover the code in for_var_len.
+#
+# XXX: It is unexpected that the variable V gets expanded in the loop body.
+# The double '$$' should prevent exactly this. Probably nobody was
+# adventurous enough to use literal dollar signs in the values for a .for
+# loop.
+V= value
+VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier)
+.for i in ${VALUES}
+. info $i
+.endfor
+
+# Cover the code for nested '{}' in for_var_len.
+#
+# The value of VALUES is not a variable expression. Instead, it is meant to
+# represent dollar, lbrace, "UNDEF:U", backslash, dollar, backslash, dollar,
+# space, nested braces, space, "end}".
+VALUES= $${UNDEF:U\$$\$$ {{}} end}
+# XXX: Where does the '\$$\$$' get converted into a single '\$'?
+.for i in ${VALUES}
+. info $i
+.endfor
+
+# A single trailing dollar doesn't happen in practice.
+# The dollar sign is correctly passed through to the body of the .for loop.
+# There, it is expanded by the .info directive, but even there a trailing
+# dollar sign is kept as-is.
+.for i in ${:U\$}
+. info ${i}
+.endfor
+
+# As of 2020-12-31, the name of the iteration variable can even contain
+# colons, which then affects variable expressions having this exact modifier.
+# This is clearly an unintended side effect of the implementation.
+NUMBERS= one two three
+.for NUMBERS:M*e in replaced
+. info ${NUMBERS} ${NUMBERS:M*e}
+.endfor
+
+# As of 2020-12-31, the name of the iteration variable can contain braces,
+# which gets even more surprising than colons, since it allows to replace
+# sequences of variable expressions. There is no practical use case for
+# this, though.
+BASENAME= one
+EXT= .c
+.for BASENAME}${EXT in replaced
+. info ${BASENAME}${EXT}
+.endfor
+
+# Demonstrate the various ways to refer to the iteration variable.
+i= outer
+i2= two
+i,= comma
+.for i in inner
+. info . $$i: $i
+. info . $${i}: ${i}
+. info . $${i:M*}: ${i:M*}
+. info . $$(i): $(i)
+. info . $$(i:M*): $(i:M*)
+. info . $${i$${:U}}: ${i${:U}}
+. info . $${i\}}: ${i\}} # XXX: unclear why SubstVarLong needs this
+. info . $${i2}: ${i2}
+. info . $${i,}: ${i,}
+. info . adjacent: $i${i}${i:M*}$i
+.endfor
diff --git a/unit-tests/directive-for-lines.exp b/unit-tests/directive-for-lines.exp
new file mode 100644
index 000000000000..7aeaaa4a7002
--- /dev/null
+++ b/unit-tests/directive-for-lines.exp
@@ -0,0 +1,10 @@
+make: "directive-for-lines.mk" line 23: expect 23
+make: "directive-for-lines.mk" line 23: expect 23
+make: "directive-for-lines.mk" line 30: expect 30
+make: "directive-for-lines.mk" line 23: expect 23
+make: "directive-for-lines.mk" line 23: expect 23
+make: "directive-for-lines.mk" line 30: expect 30
+make: no target to make.
+
+make: stopped in unit-tests
+exit status 2
diff --git a/unit-tests/directive-for-lines.mk b/unit-tests/directive-for-lines.mk
new file mode 100644
index 000000000000..96d659426882
--- /dev/null
+++ b/unit-tests/directive-for-lines.mk
@@ -0,0 +1,32 @@
+# $NetBSD: directive-for-lines.mk,v 1.3 2020/12/19 12:40:00 rillig Exp $
+#
+# Tests for the line numbers that are reported in .for loops.
+#
+# Between 2007-01-01 (git 4d3c468f96e1080e, parse.c 1.127) and 2020-12-19
+# (parse.c 1.494), the line numbers for the .info directives and error
+# messages inside .for loops had been wrong since ParseGetLine skipped empty
+# lines, even when collecting the lines for the .for loop body.
+
+.for outer in a b
+
+# comment \
+# continued comment
+
+.for inner in 1 2
+
+# comment \
+# continued comment
+
+VAR= \
+ multi-line
+
+.info expect 23
+
+.endfor
+
+# comment \
+# continued comment
+
+.info expect 30
+
+.endfor
diff --git a/unit-tests/directive-for-null.exp b/unit-tests/directive-for-null.exp
new file mode 100644
index 000000000000..37a7d68925ed
--- /dev/null
+++ b/unit-tests/directive-for-null.exp
@@ -0,0 +1,10 @@
+make: "(stdin)" line 2: Zero byte read from file
+make: "(stdin)" line 2: Unexpected end of file in for loop.
+make: "(stdin)" line 3: Zero byte read from file
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-for-null.mk b/unit-tests/directive-for-null.mk
new file mode 100644
index 000000000000..a374f508dd55
--- /dev/null
+++ b/unit-tests/directive-for-null.mk
@@ -0,0 +1,19 @@
+# $NetBSD: directive-for-null.mk,v 1.1 2020/12/19 16:00:17 rillig Exp $
+#
+# Test for parsing a .for loop that accidentally contains a null byte.
+#
+# As of 2020-12-19, there are 3 error messages:
+#
+# make: "(stdin)" line 2: Zero byte read from file
+# make: "(stdin)" line 2: Unexpected end of file in for loop.
+# make: "(stdin)" line 3: Zero byte read from file
+#
+# The one about "end of file" might be misleading but is due to the
+# implementation. On both errors and EOF, ParseGetLine returns NULL.
+#
+# The one about the "zero byte" in line 3 is surprising since the only
+# line that contains a null byte is line 2.
+
+all: .PHONY
+ @printf '%s\n' '.for i in 1 2 3' 'VAR=value' '.endfor' | tr 'l' '\0' \
+ | ${MAKE} -f -
diff --git a/unit-tests/directive-for.exp b/unit-tests/directive-for.exp
index af610cc34edd..bdaf4492baf0 100755
--- a/unit-tests/directive-for.exp
+++ b/unit-tests/directive-for.exp
@@ -16,4 +16,9 @@ make: "directive-for.mk" line 140: ][ ][ ][
make: "directive-for.mk" line 140: }{ }{ }{
make: "directive-for.mk" line 148: outer value value
make: "directive-for.mk" line 148: outer "quoted" \"quoted\"
-exit status 0
+make: "directive-for.mk" line 154: Unknown modifier 'Z'
+make: "directive-for.mk" line 155: XXX: Not reached word1
+make: "directive-for.mk" line 155: XXX: Not reached word3
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-for.mk b/unit-tests/directive-for.mk
index 93f0a14f5892..153762509b7a 100755
--- a/unit-tests/directive-for.mk
+++ b/unit-tests/directive-for.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-for.mk,v 1.9 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: directive-for.mk,v 1.10 2020/12/27 09:58:35 rillig Exp $
#
# Tests for the .for directive.
#
@@ -148,5 +148,12 @@ var= outer
. info ${var} ${var:Q} ${var:Q:Q}
.endfor
+
+# XXX: A parse error or evaluation error in the items of the .for loop
+# should skip the whole loop. As of 2020-12-27, the loop is expanded twice.
+.for var in word1 ${:Uword2:Z} word3
+. info XXX: Not reached ${var}
+.endfor
+
all:
@:;
diff --git a/unit-tests/directive-if.exp b/unit-tests/directive-if.exp
index 21a33fe4cfd6..89a394fc0f22 100644
--- a/unit-tests/directive-if.exp
+++ b/unit-tests/directive-if.exp
@@ -1,15 +1,17 @@
make: "directive-if.mk" line 13: 0 evaluates to false.
make: "directive-if.mk" line 17: 1 evaluates to true.
make: "directive-if.mk" line 40: Unknown directive "ifx"
-make: "directive-if.mk" line 41: Unknown directive "error"
+make: "directive-if.mk" line 41: This is not conditional.
make: "directive-if.mk" line 42: if-less else
-make: "directive-if.mk" line 43: Unknown directive "error"
+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: 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 3b1d13c7a0c0..b1ad2396b398 100644
--- a/unit-tests/directive-if.mk
+++ b/unit-tests/directive-if.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-if.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: directive-if.mk,v 1.9 2020/12/19 22:33:11 rillig Exp $
#
# Tests for the .if directive.
#
@@ -38,9 +38,9 @@
# are interpreted as ordinary directives, producing the error messages
# "if-less else" and "if-less endif".
.ifx 123
-. error
+.info This is not conditional.
.else
-. error
+.info This is not conditional.
.endif
# Missing condition.
@@ -78,4 +78,12 @@
. error
.endif
+.if(1)
+. info Don't do this, always put a space after a directive.
+.endif
+
+.if!0
+. info Don't do this, always put a space after a directive.
+.endif
+
all:
diff --git a/unit-tests/directive-include.mk b/unit-tests/directive-include.mk
index 120706cef8d7..d36914b25a63 100755
--- a/unit-tests/directive-include.mk
+++ b/unit-tests/directive-include.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-include.mk,v 1.4 2020/11/03 17:17:31 rillig Exp $
+# $NetBSD: directive-include.mk,v 1.5 2020/11/21 14:59:11 rillig Exp $
#
# Tests for the .include directive, which includes another file.
@@ -27,5 +27,8 @@
# including a directory technically succeeds, but shouldn't.
#.include "." # directory
+# As of 2020-11-21, anything after the delimiter '"' is ignored.
+.include "/dev/null" and ignore anything in the rest of the line.
+
all:
@:;
diff --git a/unit-tests/directive-info.exp b/unit-tests/directive-info.exp
index 971f417b706a..2652c191460c 100644
--- a/unit-tests/directive-info.exp
+++ b/unit-tests/directive-info.exp
@@ -1,14 +1,15 @@
-make: "directive-info.mk" line 7: begin .info tests
-make: "directive-info.mk" line 8: Unknown directive "inf"
-make: "directive-info.mk" line 9: Unknown directive "info"
-make: "directive-info.mk" line 10: message
-make: "directive-info.mk" line 11: indented message
-make: "directive-info.mk" line 12: Unknown directive "information"
-make: "directive-info.mk" line 13: message
-make: "directive-info.mk" line 18: Unknown directive "info"
-make: "directive-info.mk" line 19: Unknown directive "info"
-make: "directive-info.mk" line 22: Unknown directive "info-message"
-make: "directive-info.mk" line 23: no-target: no-source
+make: "directive-info.mk" line 11: begin .info tests
+make: "directive-info.mk" line 12: Unknown directive "inf"
+make: "directive-info.mk" line 13: Missing argument for ".info"
+make: "directive-info.mk" line 14: message
+make: "directive-info.mk" line 15: indented message
+make: "directive-info.mk" line 16: Unknown directive "information"
+make: "directive-info.mk" line 17: Unknown directive "information"
+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: 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 bbfc80ea0c9a..5feea0cde565 100644
--- a/unit-tests/directive-info.mk
+++ b/unit-tests/directive-info.mk
@@ -1,16 +1,20 @@
-# $NetBSD: directive-info.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $
+# $NetBSD: directive-info.mk,v 1.8 2020/12/19 22:33:11 rillig Exp $
#
# Tests for the .info directive.
+#
+# Until parse.c 1.502 from 2020-12-19, a missing argument to the directive
+# produced the wrong error message "Unknown directive". Since parse.c 1.503
+# from 2020-12-19, the correct "Missing argument" is produced.
# TODO: Implementation
.info begin .info tests
.inf # misspelled
-.info # oops: message should be "missing parameter"
+.info # "Missing argument"
.info message
.info indented message
.information
-.information message # oops: misspelled
+.information message # Accepted before 2020-12-13 01:07:54.
.info.man: # not a message, but possibly a suffix rule
# Even if lines would have trailing whitespace, this would be trimmed by
@@ -23,5 +27,13 @@
.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\
+ multi$\
+ -line message
+
all:
@:;
diff --git a/unit-tests/directive-misspellings.exp b/unit-tests/directive-misspellings.exp
new file mode 100644
index 000000000000..e51d8473b305
--- /dev/null
+++ b/unit-tests/directive-misspellings.exp
@@ -0,0 +1,45 @@
+make: "directive-misspellings.mk" line 12: Unknown directive "dinclud"
+make: "directive-misspellings.mk" line 14: Unknown directive "dincludx"
+make: "directive-misspellings.mk" line 15: .include filename must be delimited by '"' or '<'
+make: "directive-misspellings.mk" line 17: Unknown directive "erro"
+make: "directive-misspellings.mk" line 18: Unknown directive "errox"
+make: "directive-misspellings.mk" line 22: Unknown directive "expor"
+make: "directive-misspellings.mk" line 24: Unknown directive "exporx"
+make: "directive-misspellings.mk" line 25: Unknown directive "exports"
+make: "directive-misspellings.mk" line 27: Unknown directive "export-en"
+make: "directive-misspellings.mk" line 30: Unknown directive "export-environment"
+make: "directive-misspellings.mk" line 32: Unknown directive "export-litera"
+make: "directive-misspellings.mk" line 34: Unknown directive "export-literax"
+make: "directive-misspellings.mk" line 35: Unknown directive "export-literally"
+make: "directive-misspellings.mk" line 37: Unknown directive "-includ"
+make: "directive-misspellings.mk" line 39: Unknown directive "-includx"
+make: "directive-misspellings.mk" line 40: .include filename must be delimited by '"' or '<'
+make: "directive-misspellings.mk" line 42: Unknown directive "includ"
+make: "directive-misspellings.mk" line 43: Could not find file
+make: "directive-misspellings.mk" line 44: Unknown directive "includx"
+make: "directive-misspellings.mk" line 45: .include filename must be delimited by '"' or '<'
+make: "directive-misspellings.mk" line 47: Unknown directive "inf"
+make: "directive-misspellings.mk" line 48: msg
+make: "directive-misspellings.mk" line 49: Unknown directive "infx"
+make: "directive-misspellings.mk" line 50: Unknown directive "infos"
+make: "directive-misspellings.mk" line 52: Unknown directive "sinclud"
+make: "directive-misspellings.mk" line 54: Unknown directive "sincludx"
+make: "directive-misspellings.mk" line 55: .include filename must be delimited by '"' or '<'
+make: "directive-misspellings.mk" line 57: Unknown directive "unde"
+make: "directive-misspellings.mk" line 59: Unknown directive "undex"
+make: "directive-misspellings.mk" line 60: Unknown directive "undefs"
+make: "directive-misspellings.mk" line 62: Unknown directive "unexpor"
+make: "directive-misspellings.mk" line 64: Unknown directive "unexporx"
+make: "directive-misspellings.mk" line 65: Unknown directive "unexports"
+make: "directive-misspellings.mk" line 67: Unknown directive "unexport-en"
+make: "directive-misspellings.mk" line 69: The directive .unexport-env does not take arguments
+make: "directive-misspellings.mk" line 70: Unknown directive "unexport-enx"
+make: "directive-misspellings.mk" line 71: Unknown directive "unexport-envs"
+make: "directive-misspellings.mk" line 73: Unknown directive "warn"
+make: "directive-misspellings.mk" line 74: Unknown directive "warnin"
+make: "directive-misspellings.mk" line 75: warning: msg
+make: "directive-misspellings.mk" line 76: Unknown directive "warninx"
+make: "directive-misspellings.mk" line 77: Unknown directive "warnings"
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-misspellings.mk b/unit-tests/directive-misspellings.mk
new file mode 100644
index 000000000000..5f479f03b7f1
--- /dev/null
+++ b/unit-tests/directive-misspellings.mk
@@ -0,0 +1,79 @@
+# $NetBSD: directive-misspellings.mk,v 1.3 2020/12/13 01:10:22 rillig Exp $
+#
+# Tests for misspelled directives.
+#
+# Before 2020-12-12, make didn't catch most of these misspellings. For
+# example, the directive ".exporting" was interpreted as if it were spelled
+# ".export ing", which would export the variable named "ing" if that existed.
+# Another misspelling, as improbable as the others, was that both ".infos" and
+# ".information" were aliases to ".info" since the code for these diagnostic
+# directives just skipped any letters following the "error", "warn" or "info".
+
+.dinclud "file"
+.dinclude "file"
+.dincludx "file"
+.dincludes "file" # XXX: the 's' is not meant to be a filename
+
+.erro msg
+.errox msg
+# no .error since that would exit immediately
+# no .errors since that would exit immediately, even with the typo
+
+.expor varname
+.export varname
+.exporx varname
+.exports varname # Accepted before 2020-12-13 01:07:54.
+
+.export-en # Accepted before 2020-12-13 01:07:54.
+.export-env
+.export-env extra argument # XXX: undetected extra argument
+.export-environment # Accepted before 2020-12-13 01:07:54.
+
+.export-litera varname # Accepted before 2020-12-13 01:07:54.
+.export-literal varname
+.export-literax varname # Accepted before 2020-12-13 01:07:54.
+.export-literally varname # Accepted before 2020-12-13 01:07:54.
+
+.-includ "file"
+.-include "file"
+.-includx "file"
+.-includes "file" # XXX: the 's' is not meant to be a filename
+
+.includ "file"
+.include "file"
+.includx "file"
+.includex "file" # XXX: the 's' is not meant to be a filename
+
+.inf msg
+.info msg
+.infx msg
+.infos msg # Accepted before 2020-12-13 01:07:54.
+
+.sinclud "file"
+.sinclude "file"
+.sincludx "file"
+.sincludes "file" # XXX: the 's' is not meant to be a filename
+
+.unde varname
+.undef varname
+.undex varname
+.undefs varname # Accepted before 2020-12-13 01:07:54.
+
+.unexpor varname
+.unexport varname
+.unexporx varname
+.unexports varname # Accepted before 2020-12-12 18:00:18.
+
+.unexport-en # Accepted before 2020-12-12 18:11:42.
+.unexport-env
+.unexport-env extra argument # Accepted before 2020-12-12 18:00:18.
+.unexport-enx # Accepted before 2020-12-12 18:00:18.
+.unexport-envs # Accepted before 2020-12-12 18:00:18.
+
+.warn msg
+.warnin msg
+.warning msg
+.warninx msg
+.warnings msg # Accepted before 2020-12-13 01:07:54.
+
+all:
diff --git a/unit-tests/directive-undef.exp b/unit-tests/directive-undef.exp
index 303d5a3e2a27..d64cb8b5afe0 100644
--- a/unit-tests/directive-undef.exp
+++ b/unit-tests/directive-undef.exp
@@ -1,4 +1,5 @@
-make: "directive-undef.mk" line 16: Unknown directive "unde"
+make: "directive-undef.mk" line 29: The .undef directive requires an argument
+make: "directive-undef.mk" line 86: Unknown modifier 'Z'
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/directive-undef.mk b/unit-tests/directive-undef.mk
index c72513a1bf5a..b9a69f733517 100644
--- a/unit-tests/directive-undef.mk
+++ b/unit-tests/directive-undef.mk
@@ -1,21 +1,90 @@
-# $NetBSD: directive-undef.mk,v 1.5 2020/11/03 17:17:31 rillig Exp $
+# $NetBSD: directive-undef.mk,v 1.9 2020/12/22 20:10:21 rillig Exp $
#
# Tests for the .undef directive.
+#
+# See also:
+# directive-misspellings.mk
-# As of 2020-07-28, .undef only undefines the first variable.
-# All further variable names are silently ignored.
-# See parse.c, string literal "undef".
+# Before var.c 1.737 from 2020-12-19, .undef only undefined the first
+# variable, silently skipping all further variable names.
+#
+# Before var.c 1.761 from 2020-12-22, .undef complained about too many
+# arguments.
+#
+# Since var.c 1.761 from 2020-12-22, .undef handles multiple variable names
+# just like the .export directive.
1= 1
2= 2
3= 3
.undef 1 2 3
-.if ${1:U_}${2:U_}${3:U_} != _23
+.if ${1:U_}${2:U_}${3:U_} != ___
. warning $1$2$3
.endif
-.unde # misspelled
-.undef # oops: missing argument
-.undefined # oops: misspelled
+
+# Without any arguments, until var.c 1.736 from 2020-12-19, .undef tried
+# to delete the variable with the empty name, which never exists; see
+# varname-empty.mk. Since var.c 1.737 from 2020-12-19, .undef complains
+# about a missing argument.
+.undef
+
+
+# Trying to delete the variable with the empty name is ok, it just won't
+# ever do anything since that variable is never defined.
+.undef ${:U}
+
+
+# The argument of .undef is first expanded exactly once and then split into
+# words, just like everywhere else. This prevents variables whose names
+# contain spaces or unbalanced 'single' or "double" quotes from being
+# undefined, but these characters do not appear in variables names anyway.
+1= 1
+2= 2
+3= 3
+${:U1 2 3}= one two three
+VARNAMES= 1 2 3
+.undef ${VARNAMES} # undefines the variable "1 2 3"
+.if !defined(${:U1 2 3})
+. error
+.endif
+.if ${1:U_}${2:U_}${3:U_} != "___" # these are still defined
+. error
+.endif
+
+
+# A variable named " " cannot be undefined. There's no practical use case
+# for such variables anyway.
+SPACE= ${:U }
+${SPACE}= space
+.if !defined(${SPACE})
+. error
+.endif
+.undef ${SPACE}
+.if !defined(${SPACE})
+. error
+.endif
+
+
+# A variable named "$" can be undefined since the argument to .undef is
+# expanded exactly once, before being split into words.
+DOLLAR= $$
+${DOLLAR}= dollar
+.if !defined(${DOLLAR})
+. error
+.endif
+.undef ${DOLLAR}
+.if defined(${DOLLAR})
+. error
+.endif
+
+
+# Since var.c 1.762 from 2020-12-22, parse errors in the argument should be
+# properly detected and should stop the .undef directive from doing any work.
+#
+# As of var.c 1.762, this doesn't happen though because the error handling
+# in Var_Parse and Var_Subst is not done properly.
+.undef ${VARNAMES:L:Z}
+
all:
@:;
diff --git a/unit-tests/directive-unexport-env.exp b/unit-tests/directive-unexport-env.exp
index 39a9383953dd..677596ea4aa8 100644
--- a/unit-tests/directive-unexport-env.exp
+++ b/unit-tests/directive-unexport-env.exp
@@ -1 +1,18 @@
-exit status 0
+make: "directive-unexport-env.mk" line 13: Unknown directive "unexport-en"
+make: "directive-unexport-env.mk" line 15: Unknown directive "unexport-environment"
+Global:UT_EXPORTED = value
+Global:UT_UNEXPORTED = value
+Global:.MAKE.EXPORTED = UT_EXPORTED
+make: "directive-unexport-env.mk" line 21: The directive .unexport-env does not take arguments
+Var_Parse: ${.MAKE.EXPORTED:O:u} with VARE_WANTRES
+Applying ${.MAKE.EXPORTED:O} to "UT_EXPORTED" (VARE_WANTRES, none, none)
+Result of ${.MAKE.EXPORTED:O} is "UT_EXPORTED" (VARE_WANTRES, none, none)
+Applying ${.MAKE.EXPORTED:u} to "UT_EXPORTED" (VARE_WANTRES, none, none)
+Result of ${.MAKE.EXPORTED:u} is "UT_EXPORTED" (VARE_WANTRES, none, none)
+Unexporting "UT_EXPORTED"
+Global:delete .MAKE.EXPORTED
+Global:.MAKEFLAGS = -r -k -d v -d
+Global:.MAKEFLAGS = -r -k -d v -d 0
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-unexport-env.mk b/unit-tests/directive-unexport-env.mk
index 637286af0d6b..ef58ae732e6d 100644
--- a/unit-tests/directive-unexport-env.mk
+++ b/unit-tests/directive-unexport-env.mk
@@ -1,12 +1,25 @@
-# $NetBSD: directive-unexport-env.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $
+# $NetBSD: directive-unexport-env.mk,v 1.7 2020/12/12 18:11:42 rillig Exp $
#
# Tests for the .unexport-env directive.
+#
+# Before 2020-12-13, the directive unexport-env wrongly accepted arguments
+# and ignored them.
+#
+# Before 2020-12-13, misspelled directive names like "unexport-environment"
+# were not properly detected.
# TODO: Implementation
-.unexport-en # oops: misspelled
+.unexport-en # misspelled
.unexport-env # ok
-.unexport-environment # oops: misspelled
+.unexport-environment # misspelled
+
+.MAKEFLAGS: -dv
+UT_EXPORTED= value
+UT_UNEXPORTED= value
+.export UT_EXPORTED
+.unexport-env UT_EXPORTED UT_UNEXPORTED
+.MAKEFLAGS: -d0
all:
@:;
diff --git a/unit-tests/directive-unexport.exp b/unit-tests/directive-unexport.exp
index 72b24e7344fc..d59fb4713259 100644
--- a/unit-tests/directive-unexport.exp
+++ b/unit-tests/directive-unexport.exp
@@ -1,8 +1,5 @@
-make: "directive-unexport.mk" line 14: UT_A=a UT_B=b UT_C=c
-make: "directive-unexport.mk" line 15: UT_A UT_B UT_C
-make: "directive-unexport.mk" line 23: UT_A=a UT_B=b UT_C=c
-make: "directive-unexport.mk" line 24:
-make: "directive-unexport.mk" line 26: Unknown directive "unexpor"
-make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
-exit status 1
+make: "directive-unexport.mk" line 18: UT_A=a UT_B=b UT_C=c
+make: "directive-unexport.mk" line 19: UT_A UT_B UT_C
+make: "directive-unexport.mk" line 27: UT_A=a UT_B=b UT_C=c
+make: "directive-unexport.mk" line 28:
+exit status 0
diff --git a/unit-tests/directive-unexport.mk b/unit-tests/directive-unexport.mk
index 3ba4a1b1f307..efc103efedf6 100644
--- a/unit-tests/directive-unexport.mk
+++ b/unit-tests/directive-unexport.mk
@@ -1,8 +1,12 @@
-# $NetBSD: directive-unexport.mk,v 1.5 2020/11/03 17:17:31 rillig Exp $
+# $NetBSD: directive-unexport.mk,v 1.7 2020/12/13 01:07:54 rillig Exp $
#
# Tests for the .unexport directive.
-
-# TODO: Implementation
+#
+# Before 2020-12-13, misspelled directives like ".unexporting" or
+# ".unexport-en" had not been detected properly.
+#
+# See also:
+# directive-misspellings.mk
# First, export 3 variables.
UT_A= a
@@ -23,9 +27,7 @@ UT_C= c
.info ${:!env|sort|grep '^UT_'!}
.info ${.MAKE.EXPORTED}
-.unexpor # misspelled
.unexport # oops: missing argument
-.unexporting works # oops: misspelled
all:
@:;
diff --git a/unit-tests/directive-warning.exp b/unit-tests/directive-warning.exp
index 630285fd3612..b08b3207392c 100644
--- a/unit-tests/directive-warning.exp
+++ b/unit-tests/directive-warning.exp
@@ -1,11 +1,11 @@
-make: "directive-warning.mk" line 7: Unknown directive "warn"
-make: "directive-warning.mk" line 8: Unknown directive "warn"
-make: "directive-warning.mk" line 9: Unknown directive "warnin"
-make: "directive-warning.mk" line 10: Unknown directive "warnin"
-make: "directive-warning.mk" line 11: Unknown directive "warning"
-make: "directive-warning.mk" line 12: warning: message
-make: "directive-warning.mk" line 13: Unknown directive "warnings"
-make: "directive-warning.mk" line 14: warning: messages
+make: "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: 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 75560aa9e4df..d586c9fed170 100644
--- a/unit-tests/directive-warning.mk
+++ b/unit-tests/directive-warning.mk
@@ -1,6 +1,10 @@
-# $NetBSD: directive-warning.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $
+# $NetBSD: directive-warning.mk,v 1.6 2020/12/19 22:33:11 rillig Exp $
#
# Tests for the .warning directive.
+#
+# Until parse.c 1.502 from 2020-12-19, a missing argument to the directive
+# produced the wrong error message "Unknown directive". Since parse.c 1.503
+# from 2020-12-19, the correct "Missing argument" is produced.
# TODO: Implementation
@@ -8,10 +12,10 @@
.warn message # misspelled
.warnin # misspelled
.warnin message # misspelled
-.warning # oops: should be "missing argument"
+.warning # "Missing argument"
.warning message # ok
.warnings # misspelled
-.warnings messages # oops
+.warnings messages # Accepted before 2020-12-13 01:07:54.
all:
@:;
diff --git a/unit-tests/jobs-error-indirect.exp b/unit-tests/jobs-error-indirect.exp
new file mode 100644
index 000000000000..5c5a3801f4f6
--- /dev/null
+++ b/unit-tests/jobs-error-indirect.exp
@@ -0,0 +1,8 @@
+false
+*** [indirect] Error code 1
+
+make: stopped in unit-tests
+1 error
+
+make: stopped in unit-tests
+exit status 2
diff --git a/unit-tests/jobs-error-indirect.mk b/unit-tests/jobs-error-indirect.mk
new file mode 100644
index 000000000000..55e193d9b18c
--- /dev/null
+++ b/unit-tests/jobs-error-indirect.mk
@@ -0,0 +1,21 @@
+# $NetBSD: jobs-error-indirect.mk,v 1.1 2020/12/01 17:50:04 rillig Exp $
+#
+# Ensure that in jobs mode, when a command fails, the current directory is
+# printed, to aid in debugging.
+#
+# XXX: This test is run without the -k flag, which prints "stopped in" twice.
+# Why?
+#
+# This particular case is not the cause for the PRs, but it is very close.
+#
+# https://gnats.netbsd.org/55578
+# https://gnats.netbsd.org/55832
+#
+#
+
+.MAKEFLAGS: -j1
+
+all: .PHONY indirect
+
+indirect: .PHONY
+ false
diff --git a/unit-tests/jobs-error-nested-make.exp b/unit-tests/jobs-error-nested-make.exp
new file mode 100644
index 000000000000..88c32ab8d1f6
--- /dev/null
+++ b/unit-tests/jobs-error-nested-make.exp
@@ -0,0 +1,11 @@
+make -f jobs-error-nested-make.mk nested
+false
+*** [nested] Error code 1
+
+make: stopped in unit-tests
+1 error
+
+make: stopped in unit-tests
+
+make: stopped in unit-tests
+exit status 2
diff --git a/unit-tests/jobs-error-nested-make.mk b/unit-tests/jobs-error-nested-make.mk
new file mode 100644
index 000000000000..8cccf7df6a52
--- /dev/null
+++ b/unit-tests/jobs-error-nested-make.mk
@@ -0,0 +1,20 @@
+# $NetBSD: jobs-error-nested-make.mk,v 1.2 2021/01/07 18:11:23 sjg Exp $
+#
+# Ensure that in jobs mode, when a command fails, the current directory is
+# printed, to aid in debugging, even if the target is marked as .MAKE.
+# This marker is typically used for targets like 'all' that descend into
+# subdirectories.
+#
+# XXX: In case of .MAKE targets, the "stopped if" output has been suppressed
+# since job.c 1.198 from 2020-06-19.
+#
+# https://gnats.netbsd.org/55578
+# https://gnats.netbsd.org/55832
+
+.MAKEFLAGS: -j1
+
+all: .PHONY .MAKE
+ ${MAKE} -f ${MAKEFILE} nested
+
+nested: .PHONY
+ false
diff --git a/unit-tests/jobs-error-nested.exp b/unit-tests/jobs-error-nested.exp
new file mode 100644
index 000000000000..f96b5d016777
--- /dev/null
+++ b/unit-tests/jobs-error-nested.exp
@@ -0,0 +1,15 @@
+make -f jobs-error-nested.mk nested
+false
+*** [nested] Error code 1
+
+make: stopped in unit-tests
+1 error
+
+make: stopped in unit-tests
+*** [all] Error code 2
+
+make: stopped in unit-tests
+1 error
+
+make: stopped in unit-tests
+exit status 2
diff --git a/unit-tests/jobs-error-nested.mk b/unit-tests/jobs-error-nested.mk
new file mode 100644
index 000000000000..879bfff89984
--- /dev/null
+++ b/unit-tests/jobs-error-nested.mk
@@ -0,0 +1,20 @@
+# $NetBSD: jobs-error-nested.mk,v 1.1 2020/12/01 17:50:04 rillig Exp $
+#
+# Ensure that in jobs mode, when a command fails, the current directory is
+# printed, to aid in debugging.
+#
+# XXX: This test is run without the -k flag, which prints "stopped in" 4
+# times. Why?
+#
+# This particular case is not the cause for the PRs, but it is very close.
+#
+# https://gnats.netbsd.org/55578
+# https://gnats.netbsd.org/55832
+
+.MAKEFLAGS: -j1
+
+all: .PHONY
+ ${MAKE} -f ${MAKEFILE} nested
+
+nested: .PHONY
+ false
diff --git a/unit-tests/make-exported.mk b/unit-tests/make-exported.mk
index db7f09dc490f..58cb15183b8d 100755
--- a/unit-tests/make-exported.mk
+++ b/unit-tests/make-exported.mk
@@ -22,4 +22,4 @@ UT_VAR= ${UNEXPANDED}
.MAKE.EXPORTED= -literal UT_VAR
all:
- @env | sort | grep -E '^UT_|make-exported-value' || true
+ @env | sort | egrep '^UT_|make-exported-value' || true
diff --git a/unit-tests/meta-cmd-cmp.exp b/unit-tests/meta-cmd-cmp.exp
new file mode 100644
index 000000000000..bfc52123e3b2
--- /dev/null
+++ b/unit-tests/meta-cmd-cmp.exp
@@ -0,0 +1,37 @@
+one:
+Building .meta-cmd-cmp.cmp
+Building .meta-cmd-cmp.nocmp
+Building .meta-cmd-cmp.cmp2
+This line not compared FLAGS=
+Skipping meta for .END: .SPECIAL
+two:
+`.meta-cmd-cmp.cmp' is up to date.
+`.meta-cmd-cmp.nocmp' is up to date.
+.meta-cmd-cmp.cmp2.meta: 3: cannot compare command using .OODATE
+`.meta-cmd-cmp.cmp2' is up to date.
+Skipping meta for .END: .SPECIAL
+change1:
+.meta-cmd-cmp.cmp.meta: 2: a build command has changed
+@echo FLAGS= > .meta-cmd-cmp.cmp
+vs
+@echo FLAGS=changed > .meta-cmd-cmp.cmp
+Building .meta-cmd-cmp.cmp
+`.meta-cmd-cmp.nocmp' is up to date.
+.meta-cmd-cmp.cmp2.meta: 3: cannot compare command using .OODATE
+`.meta-cmd-cmp.cmp2' is up to date.
+Skipping meta for .END: .SPECIAL
+change2:
+.meta-cmd-cmp.cmp.meta: 2: a build command has changed
+@echo FLAGS=changed > .meta-cmd-cmp.cmp
+vs
+@echo FLAGS= > .meta-cmd-cmp.cmp
+Building .meta-cmd-cmp.cmp
+`.meta-cmd-cmp.nocmp' is up to date.
+.meta-cmd-cmp.cmp2.meta: 2: a build command has changed
+@echo FLAGS2= > .meta-cmd-cmp.cmp2
+vs
+@echo FLAGS2=changed > .meta-cmd-cmp.cmp2
+Building .meta-cmd-cmp.cmp2
+This line not compared FLAGS=
+Skipping meta for .END: .SPECIAL
+exit status 0
diff --git a/unit-tests/meta-cmd-cmp.mk b/unit-tests/meta-cmd-cmp.mk
new file mode 100644
index 000000000000..a1c0f7c10063
--- /dev/null
+++ b/unit-tests/meta-cmd-cmp.mk
@@ -0,0 +1,52 @@
+# $NetBSD: meta-cmd-cmp.mk,v 1.2 2020/12/05 22:51:34 sjg Exp $
+#
+# Tests META_MODE command line comparison
+#
+
+.MAIN: all
+
+.MAKE.MODE= meta verbose silent=yes curdirok=yes
+tf:= .${.PARSEFILE:R}
+
+.if ${.TARGETS:Nall} == ""
+all: prep one two change1 change2 post
+
+CLEANFILES= ${tf}*
+
+prep post: .PHONY
+ @rm -f ${CLEANFILES}
+
+.endif
+
+FLAGS?=
+FLAGS2?=
+
+tests= ${tf}.cmp ${tf}.nocmp ${tf}.cmp2
+
+${tf}.cmp:
+ @echo FLAGS=${FLAGS:Uempty} > $@
+
+${tf}.nocmp: .NOMETA_CMP
+ @echo FLAGS=${FLAGS:Uempty} > $@
+
+# a line containing ${.OODATE} will not be compared
+# this allows the trick below
+${tf}.cmp2:
+ @echo FLAGS2=${FLAGS2:Uempty} > $@
+ @echo This line not compared FLAGS=${FLAGS:Uempty} ${.OODATE:MNOMETA_CMP}
+
+# these do the same
+one two: .PHONY
+ @echo $@:
+ @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} ${tests}
+
+change1: .PHONY
+ @echo $@:
+ @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} FLAGS=changed ${tests}
+
+change2: .PHONY
+ @echo $@:
+ @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} FLAGS2=changed ${tests}
+
+# don't let gcov mess up the results
+.MAKE.META.IGNORE_PATTERNS+= *.gcda
diff --git a/unit-tests/modmisc.exp b/unit-tests/modmisc.exp
index 94f131052fdc..10475e65ee0f 100644
--- a/unit-tests/modmisc.exp
+++ b/unit-tests/modmisc.exp
@@ -1,4 +1,3 @@
-make: Unknown modifier '$'
path=':/bin:/tmp::/:.:/no/such/dir:.'
path='/bin:/tmp:/:/no/such/dir'
path='/bin:/tmp:/:/no/such/dir'
diff --git a/unit-tests/modmisc.mk b/unit-tests/modmisc.mk
index 64a84ce0dadd..9ace35c15162 100644
--- a/unit-tests/modmisc.mk
+++ b/unit-tests/modmisc.mk
@@ -1,4 +1,4 @@
-# $NetBSD: modmisc.mk,v 1.51 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: modmisc.mk,v 1.52 2020/12/20 19:29:06 rillig Exp $
#
# miscellaneous modifier tests
@@ -63,30 +63,3 @@ mod-quote:
# Cover the bmake_realloc in Str_Words.
mod-break-many-words:
@echo $@: ${UNDEF:U:range=500:[#]}
-
-# To apply a modifier indirectly via another variable, the whole
-# modifier must be put into a single variable expression.
-.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
-. warning unexpected
-.endif
-
-# Adding another level of indirection (the 2 nested :U expressions) helps.
-.if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement"
-. warning unexpected
-.endif
-
-# Multiple indirect modifiers can be applied one after another as long as
-# they are separated with colons.
-.if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE"
-. warning unexpected
-.endif
-
-# An indirect variable that evaluates to the empty string is allowed though.
-# This makes it possible to define conditional modifiers, like this:
-#
-# M.little-endian= S,1234,4321,
-# M.big-endian= # none
-.if ${value:L:${:Dempty}S,a,A,} != "vAlue"
-. warning unexpected
-.endif
-
diff --git a/unit-tests/opt-chdir.exp b/unit-tests/opt-chdir.exp
index d20f9eb2f07b..d9759cf9ed8b 100644
--- a/unit-tests/opt-chdir.exp
+++ b/unit-tests/opt-chdir.exp
@@ -1,6 +1,6 @@
make: chdir /./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././: File name too long
-*** Error code 1 (ignored)
+*** Error code 2 (ignored)
cwd: /
make: chdir /nonexistent: No such file or directory
-*** Error code 1 (ignored)
+*** Error code 2 (ignored)
exit status 0
diff --git a/unit-tests/opt-debug-errors.exp b/unit-tests/opt-debug-errors.exp
index dd13e66526b0..859a431f23bb 100644
--- a/unit-tests/opt-debug-errors.exp
+++ b/unit-tests/opt-debug-errors.exp
@@ -31,4 +31,7 @@ word1 word2
*** Failed command: echo 'word1' 'word2'; false
*** Error code 1 (continuing)
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/opt-debug-graph1.exp b/unit-tests/opt-debug-graph1.exp
index db8367c6f250..45f403f75f1b 100644
--- a/unit-tests/opt-debug-graph1.exp
+++ b/unit-tests/opt-debug-graph1.exp
@@ -13,5 +13,41 @@
# Files that are only sources:
# unmade-sources [unmade-sources]
# unmade-silent-source [unmade-silent-source] .SILENT
+#*** Global Variables:
+.ALLTARGETS = all made-target made-target-no-sources made-source unmade-target unmade-sources unmade-silent-source unmade-target-no-sources
+.CURDIR = <curdir>
+.INCLUDES =
+.LIBS =
+.MAKE = <details omitted>
+.MAKE.DEPENDFILE = <details omitted>
+.MAKE.GID = <details omitted>
+.MAKE.LEVEL = <details omitted>
+.MAKE.MAKEFILES = <details omitted>
+.MAKE.MAKEFILE_PREFERENCE = <details omitted>
+.MAKE.OS = <details omitted>
+.MAKE.PID = <details omitted>
+.MAKE.PPID = <details omitted>
+.MAKE.UID = <details omitted>
+.MAKEFLAGS = -r -k -d g1
+.MAKEOVERRIDES =
+.OBJDIR = <curdir>
+.PATH = . <curdir>
+.TARGETS =
+.newline =
+
+MACHINE = <details omitted>
+MACHINE_ARCH = <details omitted>
+MAKE = <details omitted>
+MFLAGS = -r -k -d g1
+#*** Command-line Variables:
+.MAKE.LEVEL.ENV = MAKELEVEL
+
+#*** Directory Cache:
+# Stats: 0 hits 2 misses 0 near misses 0 losers (0%)
+# refs hits directory
+# 1 0 <curdir>
+# 1 0 .
+
+#*** Suffixes:
#*** Transformations:
exit status 0
diff --git a/unit-tests/opt-debug-jobs.exp b/unit-tests/opt-debug-jobs.exp
index 0431867756a1..e79d8e94a952 100644
--- a/unit-tests/opt-debug-jobs.exp
+++ b/unit-tests/opt-debug-jobs.exp
@@ -13,10 +13,10 @@ echo ": 'single' and \"double\" quotes"
{ sleep 1
} || exit $?
Running all
- Command: sh
+ Command: <shell>
JobExec(all): pid <pid> added to jobs table
job table @ job started
-job 0, status 3, flags 0, pid <pid>
+job 0, status 3, flags ---, pid <pid>
: expanded expression
: variable
: 'single' and "double" quotes
diff --git a/unit-tests/opt-debug-lint.exp b/unit-tests/opt-debug-lint.exp
index b0be460848fd..f2123f20e37f 100644
--- a/unit-tests/opt-debug-lint.exp
+++ b/unit-tests/opt-debug-lint.exp
@@ -2,8 +2,7 @@ make: "opt-debug-lint.mk" line 19: Variable "X" is undefined
make: "opt-debug-lint.mk" line 41: Variable "UNDEF" is undefined
make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "L"
make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "P"
-make: "opt-debug-lint.mk" line 67: Missing delimiter ':' after indirect modifier "${:UL}"
-make: Unknown modifier '$'
+make: "opt-debug-lint.mk" line 69: Unknown modifier '$'
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/opt-debug-lint.mk b/unit-tests/opt-debug-lint.mk
index 9075243208b1..bb1b38feb717 100644
--- a/unit-tests/opt-debug-lint.mk
+++ b/unit-tests/opt-debug-lint.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-lint.mk,v 1.11 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: opt-debug-lint.mk,v 1.12 2020/12/20 19:10:53 rillig Exp $
#
# Tests for the -dL command line option, which runs additional checks
# to catch common mistakes, such as unclosed variable expressions.
@@ -62,11 +62,20 @@ ${UNDEF}: ${UNDEF}
. error
.endif
-# Since 2020-10-03, in lint mode the variable modifier must be separated
-# by colons. See varparse-mod.mk.
+# Between 2020-10-03 and var.c 1.752 from 2020-12-20, in lint mode the
+# variable modifier had to be separated by colons. This was wrong though
+# since make always fell back trying to parse the indirect modifier as a
+# SysV modifier.
.if ${value:${:UL}PL} != "LPL}" # FIXME: "LPL}" is unexpected here.
. error ${value:${:UL}PL}
.endif
+# Typically, an indirect modifier is followed by a colon or the closing
+# brace. This one isn't, therefore make falls back to parsing it as the SysV
+# modifier ":lue=lid".
+.if ${value:L:${:Ulue}=${:Ulid}} != "valid"
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/opt-file.exp b/unit-tests/opt-file.exp
index 39a9383953dd..76a832949aca 100644
--- a/unit-tests/opt-file.exp
+++ b/unit-tests/opt-file.exp
@@ -1 +1,12 @@
-exit status 0
+value
+value
+line-with-trailing-whitespace
+make: "(stdin)" line 1: Zero byte read from file
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/opt-file.mk b/unit-tests/opt-file.mk
index 86bc100bebc2..3ab8ef4e3c7d 100644
--- a/unit-tests/opt-file.mk
+++ b/unit-tests/opt-file.mk
@@ -1,8 +1,105 @@
-# $NetBSD: opt-file.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-file.mk,v 1.11 2020/12/22 08:57:23 rillig Exp $
#
# Tests for the -f command line option.
# TODO: Implementation
+all: .PHONY
+all: file-ending-in-backslash
+all: file-ending-in-backslash-mmap
+all: line-with-trailing-whitespace
+all: file-containing-null-byte
+
+# Passing '-' as the filename reads from stdin. This is unusual but possible.
+#
+# In the unlikely case where a file ends in a backslash instead of a newline,
+# that backslash is trimmed. See ParseGetLine.
+#
+# make-2014.01.01.00.00.00 invoked undefined behavior, reading text from
+# outside of the file buffer.
+#
+# printf '%s' 'VAR=value\' \
+# | MALLOC_OPTIONS=JA make-2014.01.01.00.00.00 -r -f - -V VAR -dA 2>&1 \
+# | less
+#
+# The debug output shows how make happily uses freshly allocated memory (the
+# <A5>) and already freed memory ('Z').
+#
+# ParseReadLine (1): 'VAR=value\<A5><A5><A5><A5><A5><A5>'
+# Global:VAR = value\<A5><A5><A5><A5><A5><A5>value\<A5><A5><A5><A5><A5><A5>
+# ParseReadLine (2): 'alue\<A5><A5><A5><A5><A5><A5>'
+# ParseDoDependency(alue\<A5><A5><A5><A5><A5><A5>)
+# make-2014.01.01.00.00.00: "(stdin)" line 2: Need an operator
+# ParseReadLine (3): '<A5><A5><A5>ZZZZZZZZZZZZZZZZ'
+# ParseDoDependency(<A5><A5><A5>ZZZZZZZZZZZZZZZZ)
+#
+file-ending-in-backslash: .PHONY
+ @printf '%s' 'VAR=value\' \
+ | ${MAKE} -r -f - -V VAR
+
+# Between parse.c 1.170 from 2010-12-25 and parse.c 1.511 from 2020-12-22,
+# there was an out-of-bounds write in ParseGetLine, where line_end pointed at
+# the end of the allocated buffer, in the special case where loadedfile_mmap
+# had not added the final newline character.
+file-ending-in-backslash-mmap: .PHONY
+ @printf '%s' 'VAR=value\' > opt-file-backslash
+ @${MAKE} -r -f opt-file-backslash -V VAR
+ @rm opt-file-backslash
+
+# Since parse.c 1.511 from 2020-12-22, an assertion in ParseGetLine failed
+# for lines that contained trailing whitespace. Worked around in parse.c
+# 1.513, properly fixed in parse.c 1.514.
+line-with-trailing-whitespace: .PHONY
+ @printf '%s' 'VAR=$@ ' > opt-file-trailing-whitespace
+ @${MAKE} -r -f opt-file-trailing-whitespace -V VAR
+ @rm opt-file-trailing-whitespace
+
+# If a makefile contains null bytes, it is an error. Throughout the history
+# of make, the behavior has changed several times, sometimes intentionally,
+# sometimes by accident.
+#
+# echo 'VAR=value' | tr 'l' '\0' > zero-byte.in
+# printf '%s\n' 'all:' ': VAR=${VAR:Q}' >> zero-byte.in
+#
+# for year in $(seq 2003 2020); do
+# echo $year:
+# make-$year.01.01.00.00.00 -r -f zero-byte.in
+# echo "exit status $?"
+# echo
+# done 2>&1 \
+# | sed "s,$PWD/,.,"
+#
+# This program generated the following output:
+#
+# 2003 to 2007:
+# exit status 0
+#
+# 2008 to 2010:
+# make: "zero-byte.in" line 1: Zero byte read from file
+# make: Fatal errors encountered -- cannot continue
+#
+# make: stopped in .
+# exit status 1
+#
+# 2011 to 2013:
+# make: no target to make.
+#
+# make: stopped in .
+# exit status 2
+#
+# 2014 to 2020-12-06:
+# make: "zero-byte.in" line 1: warning: Zero byte read from file, skipping rest of line.
+# exit status 0
+#
+# Since 2020-12-07:
+# make: "zero-byte.in" line 1: Zero byte read from file
+# make: Fatal errors encountered -- cannot continue
+# make: stopped in .
+# exit status 1
+file-containing-null-byte: .PHONY
+ @printf '%s\n' 'VAR=value' 'VAR2=VALUE2' \
+ | tr 'l' '\0' \
+ | ${MAKE} -r -f - -V VAR -V VAR2
+
all:
- @:;
+ : Making ${.TARGET}
diff --git a/unit-tests/opt-jobs-no-action.exp b/unit-tests/opt-jobs-no-action.exp
new file mode 100644
index 000000000000..8556fa3bf943
--- /dev/null
+++ b/unit-tests/opt-jobs-no-action.exp
@@ -0,0 +1,61 @@
+begin explain
+# .echoOff
+# .echoTmpl
+echo "false regular"
+# .runChkTmpl
+{ false regular
+} || exit $?
+# .echoOn
+# .runChkTmpl
+{ : silent
+} || exit $?
+# .echoOn
+false ignore-errors
+echo run despite the -n option
+run despite the -n option
+end explain
+
+begin combined
+
+silent=no always=no ignerr=no
+# .echoOff
+# .echoTmpl
+echo "echo running"
+# .runChkTmpl
+{ echo running
+} || exit $?
+# .echoOn
+
+silent=no always=no ignerr=yes
+echo running; false
+
+silent=no always=yes ignerr=no
+echo running
+running
+
+silent=no always=yes ignerr=yes
+echo running; false
+running
+*** Error code 1 (ignored)
+
+silent=yes always=no ignerr=no
+# .runChkTmpl
+{ echo running
+} || exit $?
+# .echoOn
+
+silent=yes always=no ignerr=yes
+echo running; false
+# .echoOn
+
+silent=yes always=yes ignerr=no
+echo running
+running
+
+silent=yes always=yes ignerr=yes
+echo running; false
+running
+*** Error code 1 (ignored)
+
+end combined
+exit status 0
diff --git a/unit-tests/opt-jobs-no-action.mk b/unit-tests/opt-jobs-no-action.mk
new file mode 100644
index 000000000000..a75fc38cf2fa
--- /dev/null
+++ b/unit-tests/opt-jobs-no-action.mk
@@ -0,0 +1,102 @@
+# $NetBSD: opt-jobs-no-action.mk,v 1.8 2020/12/10 23:54:41 rillig Exp $
+#
+# Tests for the combination of the options -j and -n, which prints the
+# commands instead of actually running them.
+#
+# The format of the output differs from the output of only the -n option,
+# without the -j. This is because all this code is implemented twice, once
+# in compat.c and once in job.c.
+#
+# See also:
+# opt-jobs.mk
+# The corresponding tests without the -n option
+# opt-no-action-combined.mk
+# The corresponding tests without the -j option
+
+.MAKEFLAGS: -j1 -n
+
+# Change the templates for running the commands in jobs mode, to make it
+# easier to see what actually happens.
+#
+# The shell attributes are handled by Job_ParseShell.
+# The shell attributes 'quiet' and 'echo' don't need a trailing newline,
+# this is handled by the [0] != '\0' checks in Job_ParseShell.
+# The '\#' is handled by ParseGetLine.
+# The '\n' is handled by Str_Words in Job_ParseShell.
+# The '$$' is handled by Var_Subst in ParseDependency.
+.SHELL: \
+ name=sh \
+ path=${.SHELL} \
+ quiet="\# .echoOff" \
+ echo="\# .echoOn" \
+ filter="\# .noPrint\n" \
+ check="\# .echoTmpl\n""echo \"%s\"\n" \
+ ignore="\# .runIgnTmpl\n""%s\n" \
+ errout="\# .runChkTmpl\n""{ %s \n} || exit $$?\n"
+
+all: explained combined
+.ORDER: explained combined
+
+# Explain the most basic cases in detail.
+explained: .PHONY
+ @+echo hide-from-output 'begin explain'
+
+ # The following command is regular, it is printed twice:
+ # - first using the template shell.echoTmpl,
+ # - then using the template shell.runChkTmpl.
+ false regular
+
+ # The following command is silent, it is printed once, using the
+ # template shell.runChkTmpl.
+ @: silent
+
+ # The following command ignores errors, it is printed once, using
+ # the default template for cmdTemplate, which is "%s\n".
+ # XXX: Why is it not printed using shell.echoTmpl as well?
+ # XXX: The '-' should not influence the echoing of the command.
+ -false ignore-errors
+
+ # The following command ignores the -n command line option, it is
+ # not handled by the Job module but by the Compat module, see the
+ # '!silent' in Compat_RunCommand.
+ +echo run despite the -n option
+
+ @+echo hide-from-output 'end explain'
+ @+echo hide-from-output
+
+
+# Test all combinations of the 3 RunFlags.
+#
+# TODO: Closely inspect the output whether it makes sense.
+# XXX: silent=no always=no ignerr={no,yes} should be almost the same.
+#
+SILENT.no= # none
+SILENT.yes= @
+ALWAYS.no= # none
+ALWAYS.yes= +
+IGNERR.no= echo running
+IGNERR.yes= -echo running; false
+#
+combined: combined-begin
+
+combined-begin: .PHONY
+ @+echo hide-from-output 'begin combined'
+ @+echo hide-from-output
+
+.for silent in no yes
+. for always in no yes
+. for ignerr in no yes
+. for target in combined-silent-${silent}-always-${always}-ignerr-${ignerr}
+combined: .WAIT ${target} .WAIT
+${target}: .PHONY
+ @+echo hide-from-output silent=${silent} always=${always} ignerr=${ignerr}
+ ${SILENT.${silent}}${ALWAYS.${always}}${IGNERR.${ignerr}}
+ @+echo hide-from-output
+. endfor
+. endfor
+. endfor
+.endfor
+
+combined: combined-end
+combined-end: .PHONY
+ @+echo hide-from-output 'end combined'
diff --git a/unit-tests/opt-keep-going-multiple.exp b/unit-tests/opt-keep-going-multiple.exp
new file mode 100644
index 000000000000..6d1bec18977b
--- /dev/null
+++ b/unit-tests/opt-keep-going-multiple.exp
@@ -0,0 +1,9 @@
+false fail1
+*** Error code 1 (continuing)
+false fail2
+*** Error code 1 (continuing)
+true succeed
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/opt-keep-going-multiple.mk b/unit-tests/opt-keep-going-multiple.mk
new file mode 100644
index 000000000000..cc08ccfb82c3
--- /dev/null
+++ b/unit-tests/opt-keep-going-multiple.mk
@@ -0,0 +1,21 @@
+# $NetBSD: opt-keep-going-multiple.mk,v 1.1 2020/12/07 01:32:04 rillig Exp $
+#
+# Tests for the -k command line option, which stops building a target as soon
+# as an error is detected, but continues building the other, independent
+# targets, as far as possible.
+#
+# Until 2020-12-07, the exit status of make depended only on the last of the
+# main targets. Even if the first few targets could not be made, make
+# nevertheless exited with status 0.
+
+.MAKEFLAGS: -k
+.MAKEFLAGS: fail1 fail2 succeed
+
+fail1 fail2: .PHONY
+ false ${.TARGET}
+
+succeed: .PHONY
+ true ${.TARGET}
+
+.END:
+ : The end.
diff --git a/unit-tests/opt-keep-going.exp b/unit-tests/opt-keep-going.exp
index cdad54ac24f8..2dbeb9927a30 100644
--- a/unit-tests/opt-keep-going.exp
+++ b/unit-tests/opt-keep-going.exp
@@ -3,4 +3,7 @@ dependency 1
other 1
*** Error code 1 (continuing)
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/opt-keep-going.mk b/unit-tests/opt-keep-going.mk
index 72f605246712..8a5e079ef406 100644
--- a/unit-tests/opt-keep-going.mk
+++ b/unit-tests/opt-keep-going.mk
@@ -1,8 +1,12 @@
-# $NetBSD: opt-keep-going.mk,v 1.5 2020/11/09 20:50:56 rillig Exp $
+# $NetBSD: opt-keep-going.mk,v 1.6 2020/12/07 00:53:30 rillig Exp $
#
# Tests for the -k command line option, which stops building a target as soon
# as an error is detected, but continues building the other, independent
# targets, as far as possible.
+#
+# Until 2020-12-07, if a dependency of the main target failed, the exit
+# status was nevertheless 0, which was wrong since the main targets could
+# not be made. This was only wrong in -k mode combined with compat mode.
.MAKEFLAGS: -d0 # switch stdout to being line-buffered
.MAKEFLAGS: -k
diff --git a/unit-tests/opt-no-action-runflags.exp b/unit-tests/opt-no-action-runflags.exp
new file mode 100644
index 000000000000..33b311228927
--- /dev/null
+++ b/unit-tests/opt-no-action-runflags.exp
@@ -0,0 +1,34 @@
+begin combined
+
+silent=no always=no ignerr=no
+echo running
+
+silent=no always=no ignerr=yes
+echo running; false
+
+silent=no always=yes ignerr=no
+echo running
+running
+
+silent=no always=yes ignerr=yes
+echo running; false
+running
+*** Error code 1 (ignored)
+
+silent=yes always=no ignerr=no
+echo running
+
+silent=yes always=no ignerr=yes
+echo running; false
+
+silent=yes always=yes ignerr=no
+echo running
+running
+
+silent=yes always=yes ignerr=yes
+echo running; false
+running
+*** Error code 1 (ignored)
+
+end combined
+exit status 0
diff --git a/unit-tests/opt-no-action-runflags.mk b/unit-tests/opt-no-action-runflags.mk
new file mode 100644
index 000000000000..61ae7b2bf319
--- /dev/null
+++ b/unit-tests/opt-no-action-runflags.mk
@@ -0,0 +1,32 @@
+# $NetBSD: opt-no-action-runflags.mk,v 1.1 2020/12/09 07:57:52 rillig Exp $
+#
+# Tests for the -n command line option, which runs almost no commands,
+# combined with the RunFlags '@', '-', '+' for individual commands.
+#
+# See also:
+# opt-jobs-no-action.mk
+# The corresponding test with the -j option
+
+.MAKEFLAGS: -n
+
+all: .PHONY combined
+
+SILENT.no= # none
+SILENT.yes= @
+ALWAYS.no= # none
+ALWAYS.yes= +
+IGNERR.no= echo running
+IGNERR.yes= -echo running; false
+#
+combined: .PHONY
+ @+echo hide-from-output 'begin $@'; echo
+.for silent in no yes
+. for always in no yes
+. for ignerr in no yes
+ @+echo hide-from-output silent=${silent} always=${always} ignerr=${ignerr}
+ ${SILENT.${silent}}${ALWAYS.${always}}${IGNERR.${ignerr}}
+ @+echo hide-from-output
+. endfor
+. endfor
+.endfor
+ @+echo hide-from-output 'end $@'
diff --git a/unit-tests/opt.exp b/unit-tests/opt.exp
index 11344ae0c359..3c96cf25025f 100644
--- a/unit-tests/opt.exp
+++ b/unit-tests/opt.exp
@@ -12,6 +12,10 @@ make -r -f /dev/null -- -VAR=value -f /dev/null
make: don't know how to make -f (continuing)
`/dev/null' is up to date.
+Stop.
+make: stopped in unit-tests
+*** Error code 1 (ignored)
+
make -?
usage: make [-BeikNnqrSstWwX]
[-C directory] [-D variable] [-d flags] [-f makefile]
diff --git a/unit-tests/posix.exp b/unit-tests/posix.exp
index 7e74cabadfb5..01961f363f59 100644
--- a/unit-tests/posix.exp
+++ b/unit-tests/posix.exp
@@ -20,4 +20,7 @@ a command prefixed by '+' executes even with -n
Now we expect an error...
*** Error code 1 (continuing)
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/qequals.exp b/unit-tests/qequals.exp
deleted file mode 100644
index 6b2f4dce6994..000000000000
--- a/unit-tests/qequals.exp
+++ /dev/null
@@ -1,2 +0,0 @@
-V.i386 ?= OK
-exit status 0
diff --git a/unit-tests/qequals.mk b/unit-tests/qequals.mk
deleted file mode 100644
index a964e99b2645..000000000000
--- a/unit-tests/qequals.mk
+++ /dev/null
@@ -1,8 +0,0 @@
-# $NetBSD: qequals.mk,v 1.3 2020/10/24 08:50:17 rillig Exp $
-
-M= i386
-V.i386= OK
-V.$M?= bug
-
-all:
- @echo 'V.$M ?= ${V.$M}'
diff --git a/unit-tests/sh-dots.exp b/unit-tests/sh-dots.exp
index 4d935096c48a..a7de932035f3 100755
--- a/unit-tests/sh-dots.exp
+++ b/unit-tests/sh-dots.exp
@@ -1,19 +1,19 @@
first first
hidden hidden
-make: exec(...) failed (No such file or directory)
-*** Error code 1 (ignored)
+<not found: ...>
+*** Error code <nonzero> (ignored)
hidden delayed hidden
repeated repeated
commented commented
... # Run the below commands later
-<normalized: ...: not found>
-*** Error code 127 (ignored)
+<not found: ...>
+*** Error code <nonzero> (ignored)
commented delayed commented
indirect regular
indirect-space regular
...
-make: exec(...) failed (No such file or directory)
-*** Error code 1 (ignored)
+<not found: ...>
+*** Error code <nonzero> (ignored)
indirect-space deferred
first delayed first
repeated delayed repeated
diff --git a/unit-tests/sh-errctl.exp b/unit-tests/sh-errctl.exp
new file mode 100644
index 000000000000..8e6bc3c82125
--- /dev/null
+++ b/unit-tests/sh-errctl.exp
@@ -0,0 +1,27 @@
+job_pipe -1 -1, maxjobs 1, tokens 1, compat 0
+Job_TokenWithdraw(<pid>): aborting 0, running 0
+(<pid>) withdrew token
+# echo off
+echo silent
+# echo on
+# echo off
+# error checking off
+set +e
+# echo on
+echo ignerr; false
+# echo off
+# error checking on
+set -e
+# echo on
+echo always
+Running all
+ Command: <shell>
+JobExec(all): pid <pid> added to jobs table
+job table @ job started
+job 0, status 3, flags ---, pid <pid>
+silent
+ignerr
+always
+Job_TokenWithdraw(<pid>): aborting 0, running 0
+(<pid>) withdrew token
+exit status 0
diff --git a/unit-tests/sh-errctl.mk b/unit-tests/sh-errctl.mk
new file mode 100644
index 000000000000..ecc2485e9b72
--- /dev/null
+++ b/unit-tests/sh-errctl.mk
@@ -0,0 +1,26 @@
+# $NetBSD: sh-errctl.mk,v 1.1 2020/12/12 15:06:11 rillig Exp $
+#
+# Test a shell with error control. This only works in jobs mode; in compat
+# mode, the default shell is always used, see InitShellNameAndPath.
+#
+# There is a subtle difference between error control and echo control.
+# With error control, each simple command is checked, whereas with echo
+# control, only the last command from each line is checked. A shell command
+# line that behaves differently in these two modes is "false; true". In
+# error control mode, this fails, while in echo control mode, it succeeds.
+
+.MAKEFLAGS: -j1 -dj
+
+.SHELL: \
+ name="sh" \
+ path="${.SHELL}" \
+ hasErrCtl="yes" \
+ check="\# error checking on\nset -e" \
+ ignore="\# error checking off\nset +e" \
+ echo="\# echo on" \
+ quiet="\# echo off"
+
+all:
+ @echo silent
+ -echo ignerr; false
+ +echo always
diff --git a/unit-tests/sh-flags.exp b/unit-tests/sh-flags.exp
new file mode 100644
index 000000000000..2fec7de2dd99
--- /dev/null
+++ b/unit-tests/sh-flags.exp
@@ -0,0 +1,4325 @@
+
+opt-______-tgt-___-cmd-___
+echo running
+running
+
+opt-______-tgt-___-cmd-__s
+running
+
+opt-______-tgt-___-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-___-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-___-cmd-a__
+echo running
+running
+
+opt-______-tgt-___-cmd-a_s
+running
+
+opt-______-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-___-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-__s-cmd-___
+running
+
+opt-______-tgt-__s-cmd-__s
+running
+
+opt-______-tgt-__s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-__s-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-__s-cmd-a__
+running
+
+opt-______-tgt-__s-cmd-a_s
+running
+
+opt-______-tgt-__s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-__s-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_i_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_i_-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_i_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_i_-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_i_-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_i_-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_is-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_is-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_is-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_is-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_is-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_is-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_is-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-_is-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-a__-cmd-___
+echo running
+running
+
+opt-______-tgt-a__-cmd-__s
+running
+
+opt-______-tgt-a__-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-a__-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-a__-cmd-a__
+echo running
+running
+
+opt-______-tgt-a__-cmd-a_s
+running
+
+opt-______-tgt-a__-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-a__-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-a_s-cmd-___
+running
+
+opt-______-tgt-a_s-cmd-__s
+running
+
+opt-______-tgt-a_s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-a_s-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-a_s-cmd-a__
+running
+
+opt-______-tgt-a_s-cmd-a_s
+running
+
+opt-______-tgt-a_s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-a_s-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ai_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ai_-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ai_-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ai_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ai_-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ai_-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ais-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ais-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ais-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ais-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ais-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ais-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ais-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-______-tgt-ais-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-___-cmd-___
+echo running
+
+opt-___n__-tgt-___-cmd-__s
+echo running
+
+opt-___n__-tgt-___-cmd-_i_
+echo running; false
+
+opt-___n__-tgt-___-cmd-_is
+echo running; false
+
+opt-___n__-tgt-___-cmd-a__
+echo running
+running
+
+opt-___n__-tgt-___-cmd-a_s
+echo running
+running
+
+opt-___n__-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-__s-cmd-___
+echo running
+
+opt-___n__-tgt-__s-cmd-__s
+echo running
+
+opt-___n__-tgt-__s-cmd-_i_
+echo running; false
+
+opt-___n__-tgt-__s-cmd-_is
+echo running; false
+
+opt-___n__-tgt-__s-cmd-a__
+echo running
+running
+
+opt-___n__-tgt-__s-cmd-a_s
+echo running
+running
+
+opt-___n__-tgt-__s-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-_i_-cmd-___
+echo running; false
+
+opt-___n__-tgt-_i_-cmd-__s
+echo running; false
+
+opt-___n__-tgt-_i_-cmd-_i_
+echo running; false
+
+opt-___n__-tgt-_i_-cmd-_is
+echo running; false
+
+opt-___n__-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-_is-cmd-___
+echo running; false
+
+opt-___n__-tgt-_is-cmd-__s
+echo running; false
+
+opt-___n__-tgt-_is-cmd-_i_
+echo running; false
+
+opt-___n__-tgt-_is-cmd-_is
+echo running; false
+
+opt-___n__-tgt-_is-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-_is-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-a__-cmd-___
+echo running
+running
+
+opt-___n__-tgt-a__-cmd-__s
+running
+
+opt-___n__-tgt-a__-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-a__-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-a__-cmd-a__
+echo running
+running
+
+opt-___n__-tgt-a__-cmd-a_s
+running
+
+opt-___n__-tgt-a__-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-a__-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-a_s-cmd-___
+running
+
+opt-___n__-tgt-a_s-cmd-__s
+running
+
+opt-___n__-tgt-a_s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-a_s-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-a_s-cmd-a__
+running
+
+opt-___n__-tgt-a_s-cmd-a_s
+running
+
+opt-___n__-tgt-a_s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-a_s-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ai_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ai_-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ai_-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ai_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ai_-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ai_-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ais-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ais-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ais-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ais-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ais-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ais-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ais-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-___n__-tgt-ais-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-___-cmd-___
+echo running
+running
+
+opt-__l___-tgt-___-cmd-__s
+echo running
+running
+
+opt-__l___-tgt-___-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-___-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-___-cmd-a__
+echo running
+running
+
+opt-__l___-tgt-___-cmd-a_s
+echo running
+running
+
+opt-__l___-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-__s-cmd-___
+running
+
+opt-__l___-tgt-__s-cmd-__s
+echo running
+running
+
+opt-__l___-tgt-__s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-__s-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-__s-cmd-a__
+running
+
+opt-__l___-tgt-__s-cmd-a_s
+echo running
+running
+
+opt-__l___-tgt-__s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_i_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_i_-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_i_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_i_-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_is-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_is-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_is-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_is-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_is-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_is-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-a__-cmd-___
+echo running
+running
+
+opt-__l___-tgt-a__-cmd-__s
+echo running
+running
+
+opt-__l___-tgt-a__-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-a__-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-a__-cmd-a__
+echo running
+running
+
+opt-__l___-tgt-a__-cmd-a_s
+echo running
+running
+
+opt-__l___-tgt-a__-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-a__-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-a_s-cmd-___
+running
+
+opt-__l___-tgt-a_s-cmd-__s
+echo running
+running
+
+opt-__l___-tgt-a_s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-a_s-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-a_s-cmd-a__
+running
+
+opt-__l___-tgt-a_s-cmd-a_s
+echo running
+running
+
+opt-__l___-tgt-a_s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-a_s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ai_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ai_-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ai_-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ai_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ai_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ai_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ais-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ais-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ais-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ais-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ais-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ais-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ais-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-__l___-tgt-ais-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-___-cmd-___
+echo running
+
+opt-__ln__-tgt-___-cmd-__s
+echo running
+
+opt-__ln__-tgt-___-cmd-_i_
+echo running; false
+
+opt-__ln__-tgt-___-cmd-_is
+echo running; false
+
+opt-__ln__-tgt-___-cmd-a__
+echo running
+running
+
+opt-__ln__-tgt-___-cmd-a_s
+echo running
+running
+
+opt-__ln__-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-__s-cmd-___
+echo running
+
+opt-__ln__-tgt-__s-cmd-__s
+echo running
+
+opt-__ln__-tgt-__s-cmd-_i_
+echo running; false
+
+opt-__ln__-tgt-__s-cmd-_is
+echo running; false
+
+opt-__ln__-tgt-__s-cmd-a__
+echo running
+running
+
+opt-__ln__-tgt-__s-cmd-a_s
+echo running
+running
+
+opt-__ln__-tgt-__s-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-_i_-cmd-___
+echo running; false
+
+opt-__ln__-tgt-_i_-cmd-__s
+echo running; false
+
+opt-__ln__-tgt-_i_-cmd-_i_
+echo running; false
+
+opt-__ln__-tgt-_i_-cmd-_is
+echo running; false
+
+opt-__ln__-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-_is-cmd-___
+echo running; false
+
+opt-__ln__-tgt-_is-cmd-__s
+echo running; false
+
+opt-__ln__-tgt-_is-cmd-_i_
+echo running; false
+
+opt-__ln__-tgt-_is-cmd-_is
+echo running; false
+
+opt-__ln__-tgt-_is-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-_is-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-a__-cmd-___
+echo running
+running
+
+opt-__ln__-tgt-a__-cmd-__s
+echo running
+running
+
+opt-__ln__-tgt-a__-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-a__-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-a__-cmd-a__
+echo running
+running
+
+opt-__ln__-tgt-a__-cmd-a_s
+echo running
+running
+
+opt-__ln__-tgt-a__-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-a__-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-a_s-cmd-___
+running
+
+opt-__ln__-tgt-a_s-cmd-__s
+echo running
+running
+
+opt-__ln__-tgt-a_s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-a_s-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-a_s-cmd-a__
+running
+
+opt-__ln__-tgt-a_s-cmd-a_s
+echo running
+running
+
+opt-__ln__-tgt-a_s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-a_s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ai_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ai_-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ai_-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ai_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ai_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ai_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ais-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ais-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ais-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ais-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ais-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ais-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ais-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-__ln__-tgt-ais-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j____-tgt-___-cmd-___
+echo running
+running
+
+opt-_j____-tgt-___-cmd-__s
+running
+
+opt-_j____-tgt-___-cmd-_i_
+echo running; false
+running
+*** [opt-_j____-tgt-___-cmd-_i_] Error code 1 (ignored)
+
+opt-_j____-tgt-___-cmd-_is
+running
+*** [opt-_j____-tgt-___-cmd-_is] Error code 1 (ignored)
+
+opt-_j____-tgt-___-cmd-a__
+echo running
+running
+
+opt-_j____-tgt-___-cmd-a_s
+running
+
+opt-_j____-tgt-___-cmd-ai_
+echo running; false
+running
+*** [opt-_j____-tgt-___-cmd-ai_] Error code 1 (ignored)
+
+opt-_j____-tgt-___-cmd-ais
+running
+*** [opt-_j____-tgt-___-cmd-ais] Error code 1 (ignored)
+
+opt-_j____-tgt-__s-cmd-___
+running
+
+opt-_j____-tgt-__s-cmd-__s
+running
+
+opt-_j____-tgt-__s-cmd-_i_
+echo running; false
+running
+*** [opt-_j____-tgt-__s-cmd-_i_] Error code 1 (ignored)
+
+opt-_j____-tgt-__s-cmd-_is
+running
+*** [opt-_j____-tgt-__s-cmd-_is] Error code 1 (ignored)
+
+opt-_j____-tgt-__s-cmd-a__
+running
+
+opt-_j____-tgt-__s-cmd-a_s
+running
+
+opt-_j____-tgt-__s-cmd-ai_
+echo running; false
+running
+*** [opt-_j____-tgt-__s-cmd-ai_] Error code 1 (ignored)
+
+opt-_j____-tgt-__s-cmd-ais
+running
+*** [opt-_j____-tgt-__s-cmd-ais] Error code 1 (ignored)
+
+opt-_j____-tgt-_i_-cmd-___
+echo running; false
+running
+*** [opt-_j____-tgt-_i_-cmd-___] Error code 1 (ignored)
+
+opt-_j____-tgt-_i_-cmd-__s
+running
+*** [opt-_j____-tgt-_i_-cmd-__s] Error code 1 (ignored)
+
+opt-_j____-tgt-_i_-cmd-_i_
+echo running; false
+running
+*** [opt-_j____-tgt-_i_-cmd-_i_] Error code 1 (ignored)
+
+opt-_j____-tgt-_i_-cmd-_is
+running
+*** [opt-_j____-tgt-_i_-cmd-_is] Error code 1 (ignored)
+
+opt-_j____-tgt-_i_-cmd-a__
+echo running; false
+running
+*** [opt-_j____-tgt-_i_-cmd-a__] Error code 1 (ignored)
+
+opt-_j____-tgt-_i_-cmd-a_s
+running
+*** [opt-_j____-tgt-_i_-cmd-a_s] Error code 1 (ignored)
+
+opt-_j____-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** [opt-_j____-tgt-_i_-cmd-ai_] Error code 1 (ignored)
+
+opt-_j____-tgt-_i_-cmd-ais
+running
+*** [opt-_j____-tgt-_i_-cmd-ais] Error code 1 (ignored)
+
+opt-_j____-tgt-_is-cmd-___
+running
+*** [opt-_j____-tgt-_is-cmd-___] Error code 1 (ignored)
+
+opt-_j____-tgt-_is-cmd-__s
+running
+*** [opt-_j____-tgt-_is-cmd-__s] Error code 1 (ignored)
+
+opt-_j____-tgt-_is-cmd-_i_
+echo running; false
+running
+*** [opt-_j____-tgt-_is-cmd-_i_] Error code 1 (ignored)
+
+opt-_j____-tgt-_is-cmd-_is
+running
+*** [opt-_j____-tgt-_is-cmd-_is] Error code 1 (ignored)
+
+opt-_j____-tgt-_is-cmd-a__
+running
+*** [opt-_j____-tgt-_is-cmd-a__] Error code 1 (ignored)
+
+opt-_j____-tgt-_is-cmd-a_s
+running
+*** [opt-_j____-tgt-_is-cmd-a_s] Error code 1 (ignored)
+
+opt-_j____-tgt-_is-cmd-ai_
+echo running; false
+running
+*** [opt-_j____-tgt-_is-cmd-ai_] Error code 1 (ignored)
+
+opt-_j____-tgt-_is-cmd-ais
+running
+*** [opt-_j____-tgt-_is-cmd-ais] Error code 1 (ignored)
+
+opt-_j____-tgt-a__-cmd-___
+echo running
+running
+
+opt-_j____-tgt-a__-cmd-__s
+running
+
+opt-_j____-tgt-a__-cmd-_i_
+echo running; false
+running
+*** [opt-_j____-tgt-a__-cmd-_i_] Error code 1 (ignored)
+
+opt-_j____-tgt-a__-cmd-_is
+running
+*** [opt-_j____-tgt-a__-cmd-_is] Error code 1 (ignored)
+
+opt-_j____-tgt-a__-cmd-a__
+echo running
+running
+
+opt-_j____-tgt-a__-cmd-a_s
+running
+
+opt-_j____-tgt-a__-cmd-ai_
+echo running; false
+running
+*** [opt-_j____-tgt-a__-cmd-ai_] Error code 1 (ignored)
+
+opt-_j____-tgt-a__-cmd-ais
+running
+*** [opt-_j____-tgt-a__-cmd-ais] Error code 1 (ignored)
+
+opt-_j____-tgt-a_s-cmd-___
+running
+
+opt-_j____-tgt-a_s-cmd-__s
+running
+
+opt-_j____-tgt-a_s-cmd-_i_
+echo running; false
+running
+*** [opt-_j____-tgt-a_s-cmd-_i_] Error code 1 (ignored)
+
+opt-_j____-tgt-a_s-cmd-_is
+running
+*** [opt-_j____-tgt-a_s-cmd-_is] Error code 1 (ignored)
+
+opt-_j____-tgt-a_s-cmd-a__
+running
+
+opt-_j____-tgt-a_s-cmd-a_s
+running
+
+opt-_j____-tgt-a_s-cmd-ai_
+echo running; false
+running
+*** [opt-_j____-tgt-a_s-cmd-ai_] Error code 1 (ignored)
+
+opt-_j____-tgt-a_s-cmd-ais
+running
+*** [opt-_j____-tgt-a_s-cmd-ais] Error code 1 (ignored)
+
+opt-_j____-tgt-ai_-cmd-___
+echo running; false
+running
+*** [opt-_j____-tgt-ai_-cmd-___] Error code 1 (ignored)
+
+opt-_j____-tgt-ai_-cmd-__s
+running
+*** [opt-_j____-tgt-ai_-cmd-__s] Error code 1 (ignored)
+
+opt-_j____-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** [opt-_j____-tgt-ai_-cmd-_i_] Error code 1 (ignored)
+
+opt-_j____-tgt-ai_-cmd-_is
+running
+*** [opt-_j____-tgt-ai_-cmd-_is] Error code 1 (ignored)
+
+opt-_j____-tgt-ai_-cmd-a__
+echo running; false
+running
+*** [opt-_j____-tgt-ai_-cmd-a__] Error code 1 (ignored)
+
+opt-_j____-tgt-ai_-cmd-a_s
+running
+*** [opt-_j____-tgt-ai_-cmd-a_s] Error code 1 (ignored)
+
+opt-_j____-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** [opt-_j____-tgt-ai_-cmd-ai_] Error code 1 (ignored)
+
+opt-_j____-tgt-ai_-cmd-ais
+running
+*** [opt-_j____-tgt-ai_-cmd-ais] Error code 1 (ignored)
+
+opt-_j____-tgt-ais-cmd-___
+running
+*** [opt-_j____-tgt-ais-cmd-___] Error code 1 (ignored)
+
+opt-_j____-tgt-ais-cmd-__s
+running
+*** [opt-_j____-tgt-ais-cmd-__s] Error code 1 (ignored)
+
+opt-_j____-tgt-ais-cmd-_i_
+echo running; false
+running
+*** [opt-_j____-tgt-ais-cmd-_i_] Error code 1 (ignored)
+
+opt-_j____-tgt-ais-cmd-_is
+running
+*** [opt-_j____-tgt-ais-cmd-_is] Error code 1 (ignored)
+
+opt-_j____-tgt-ais-cmd-a__
+running
+*** [opt-_j____-tgt-ais-cmd-a__] Error code 1 (ignored)
+
+opt-_j____-tgt-ais-cmd-a_s
+running
+*** [opt-_j____-tgt-ais-cmd-a_s] Error code 1 (ignored)
+
+opt-_j____-tgt-ais-cmd-ai_
+echo running; false
+running
+*** [opt-_j____-tgt-ais-cmd-ai_] Error code 1 (ignored)
+
+opt-_j____-tgt-ais-cmd-ais
+running
+*** [opt-_j____-tgt-ais-cmd-ais] Error code 1 (ignored)
+
+opt-_j_n__-tgt-___-cmd-___
+echo "echo running"
+{ echo running
+} || exit $?
+
+opt-_j_n__-tgt-___-cmd-__s
+{ echo running
+} || exit $?
+
+opt-_j_n__-tgt-___-cmd-_i_
+echo running; false
+
+opt-_j_n__-tgt-___-cmd-_is
+echo running; false
+
+opt-_j_n__-tgt-___-cmd-a__
+echo running
+running
+
+opt-_j_n__-tgt-___-cmd-a_s
+echo running
+running
+
+opt-_j_n__-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-__s-cmd-___
+{ echo running
+} || exit $?
+
+opt-_j_n__-tgt-__s-cmd-__s
+{ echo running
+} || exit $?
+
+opt-_j_n__-tgt-__s-cmd-_i_
+echo running; false
+
+opt-_j_n__-tgt-__s-cmd-_is
+echo running; false
+
+opt-_j_n__-tgt-__s-cmd-a__
+echo running
+running
+
+opt-_j_n__-tgt-__s-cmd-a_s
+echo running
+running
+
+opt-_j_n__-tgt-__s-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-_i_-cmd-___
+echo "echo running; false"
+{ echo running; false
+} || exit $?
+
+opt-_j_n__-tgt-_i_-cmd-__s
+{ echo running; false
+} || exit $?
+
+opt-_j_n__-tgt-_i_-cmd-_i_
+echo running; false
+
+opt-_j_n__-tgt-_i_-cmd-_is
+echo running; false
+
+opt-_j_n__-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-_is-cmd-___
+{ echo running; false
+} || exit $?
+
+opt-_j_n__-tgt-_is-cmd-__s
+{ echo running; false
+} || exit $?
+
+opt-_j_n__-tgt-_is-cmd-_i_
+echo running; false
+
+opt-_j_n__-tgt-_is-cmd-_is
+echo running; false
+
+opt-_j_n__-tgt-_is-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-_is-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_j_n__-tgt-a__-cmd-___
+echo running
+running
+
+opt-_j_n__-tgt-a__-cmd-__s
+running
+
+opt-_j_n__-tgt-a__-cmd-_i_
+echo running; false
+running
+
+opt-_j_n__-tgt-a__-cmd-_is
+running
+
+opt-_j_n__-tgt-a__-cmd-a__
+echo running
+running
+
+opt-_j_n__-tgt-a__-cmd-a_s
+running
+
+opt-_j_n__-tgt-a__-cmd-ai_
+echo running; false
+running
+
+opt-_j_n__-tgt-a__-cmd-ais
+running
+
+opt-_j_n__-tgt-a_s-cmd-___
+running
+
+opt-_j_n__-tgt-a_s-cmd-__s
+running
+
+opt-_j_n__-tgt-a_s-cmd-_i_
+echo running; false
+running
+
+opt-_j_n__-tgt-a_s-cmd-_is
+running
+
+opt-_j_n__-tgt-a_s-cmd-a__
+running
+
+opt-_j_n__-tgt-a_s-cmd-a_s
+running
+
+opt-_j_n__-tgt-a_s-cmd-ai_
+echo running; false
+running
+
+opt-_j_n__-tgt-a_s-cmd-ais
+running
+
+opt-_j_n__-tgt-ai_-cmd-___
+echo running; false
+running
+
+opt-_j_n__-tgt-ai_-cmd-__s
+running
+
+opt-_j_n__-tgt-ai_-cmd-_i_
+echo running; false
+running
+
+opt-_j_n__-tgt-ai_-cmd-_is
+running
+
+opt-_j_n__-tgt-ai_-cmd-a__
+echo running; false
+running
+
+opt-_j_n__-tgt-ai_-cmd-a_s
+running
+
+opt-_j_n__-tgt-ai_-cmd-ai_
+echo running; false
+running
+
+opt-_j_n__-tgt-ai_-cmd-ais
+running
+
+opt-_j_n__-tgt-ais-cmd-___
+running
+
+opt-_j_n__-tgt-ais-cmd-__s
+running
+
+opt-_j_n__-tgt-ais-cmd-_i_
+echo running; false
+running
+
+opt-_j_n__-tgt-ais-cmd-_is
+running
+
+opt-_j_n__-tgt-ais-cmd-a__
+running
+
+opt-_j_n__-tgt-ais-cmd-a_s
+running
+
+opt-_j_n__-tgt-ais-cmd-ai_
+echo running; false
+running
+
+opt-_j_n__-tgt-ais-cmd-ais
+running
+
+opt-_jl___-tgt-___-cmd-___
+echo running
+running
+
+opt-_jl___-tgt-___-cmd-__s
+echo running
+running
+
+opt-_jl___-tgt-___-cmd-_i_
+echo running; false
+running
+*** [opt-_jl___-tgt-___-cmd-_i_] Error code 1 (ignored)
+
+opt-_jl___-tgt-___-cmd-_is
+echo running; false
+running
+*** [opt-_jl___-tgt-___-cmd-_is] Error code 1 (ignored)
+
+opt-_jl___-tgt-___-cmd-a__
+echo running
+running
+
+opt-_jl___-tgt-___-cmd-a_s
+echo running
+running
+
+opt-_jl___-tgt-___-cmd-ai_
+echo running; false
+running
+*** [opt-_jl___-tgt-___-cmd-ai_] Error code 1 (ignored)
+
+opt-_jl___-tgt-___-cmd-ais
+echo running; false
+running
+*** [opt-_jl___-tgt-___-cmd-ais] Error code 1 (ignored)
+
+opt-_jl___-tgt-__s-cmd-___
+running
+
+opt-_jl___-tgt-__s-cmd-__s
+running
+
+opt-_jl___-tgt-__s-cmd-_i_
+echo running; false
+running
+*** [opt-_jl___-tgt-__s-cmd-_i_] Error code 1 (ignored)
+
+opt-_jl___-tgt-__s-cmd-_is
+echo running; false
+running
+*** [opt-_jl___-tgt-__s-cmd-_is] Error code 1 (ignored)
+
+opt-_jl___-tgt-__s-cmd-a__
+running
+
+opt-_jl___-tgt-__s-cmd-a_s
+running
+
+opt-_jl___-tgt-__s-cmd-ai_
+echo running; false
+running
+*** [opt-_jl___-tgt-__s-cmd-ai_] Error code 1 (ignored)
+
+opt-_jl___-tgt-__s-cmd-ais
+echo running; false
+running
+*** [opt-_jl___-tgt-__s-cmd-ais] Error code 1 (ignored)
+
+opt-_jl___-tgt-_i_-cmd-___
+echo running; false
+running
+*** [opt-_jl___-tgt-_i_-cmd-___] Error code 1 (ignored)
+
+opt-_jl___-tgt-_i_-cmd-__s
+echo running; false
+running
+*** [opt-_jl___-tgt-_i_-cmd-__s] Error code 1 (ignored)
+
+opt-_jl___-tgt-_i_-cmd-_i_
+echo running; false
+running
+*** [opt-_jl___-tgt-_i_-cmd-_i_] Error code 1 (ignored)
+
+opt-_jl___-tgt-_i_-cmd-_is
+echo running; false
+running
+*** [opt-_jl___-tgt-_i_-cmd-_is] Error code 1 (ignored)
+
+opt-_jl___-tgt-_i_-cmd-a__
+echo running; false
+running
+*** [opt-_jl___-tgt-_i_-cmd-a__] Error code 1 (ignored)
+
+opt-_jl___-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** [opt-_jl___-tgt-_i_-cmd-a_s] Error code 1 (ignored)
+
+opt-_jl___-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** [opt-_jl___-tgt-_i_-cmd-ai_] Error code 1 (ignored)
+
+opt-_jl___-tgt-_i_-cmd-ais
+echo running; false
+running
+*** [opt-_jl___-tgt-_i_-cmd-ais] Error code 1 (ignored)
+
+opt-_jl___-tgt-_is-cmd-___
+running
+*** [opt-_jl___-tgt-_is-cmd-___] Error code 1 (ignored)
+
+opt-_jl___-tgt-_is-cmd-__s
+running
+*** [opt-_jl___-tgt-_is-cmd-__s] Error code 1 (ignored)
+
+opt-_jl___-tgt-_is-cmd-_i_
+echo running; false
+running
+*** [opt-_jl___-tgt-_is-cmd-_i_] Error code 1 (ignored)
+
+opt-_jl___-tgt-_is-cmd-_is
+echo running; false
+running
+*** [opt-_jl___-tgt-_is-cmd-_is] Error code 1 (ignored)
+
+opt-_jl___-tgt-_is-cmd-a__
+running
+*** [opt-_jl___-tgt-_is-cmd-a__] Error code 1 (ignored)
+
+opt-_jl___-tgt-_is-cmd-a_s
+running
+*** [opt-_jl___-tgt-_is-cmd-a_s] Error code 1 (ignored)
+
+opt-_jl___-tgt-_is-cmd-ai_
+echo running; false
+running
+*** [opt-_jl___-tgt-_is-cmd-ai_] Error code 1 (ignored)
+
+opt-_jl___-tgt-_is-cmd-ais
+echo running; false
+running
+*** [opt-_jl___-tgt-_is-cmd-ais] Error code 1 (ignored)
+
+opt-_jl___-tgt-a__-cmd-___
+echo running
+running
+
+opt-_jl___-tgt-a__-cmd-__s
+echo running
+running
+
+opt-_jl___-tgt-a__-cmd-_i_
+echo running; false
+running
+*** [opt-_jl___-tgt-a__-cmd-_i_] Error code 1 (ignored)
+
+opt-_jl___-tgt-a__-cmd-_is
+echo running; false
+running
+*** [opt-_jl___-tgt-a__-cmd-_is] Error code 1 (ignored)
+
+opt-_jl___-tgt-a__-cmd-a__
+echo running
+running
+
+opt-_jl___-tgt-a__-cmd-a_s
+echo running
+running
+
+opt-_jl___-tgt-a__-cmd-ai_
+echo running; false
+running
+*** [opt-_jl___-tgt-a__-cmd-ai_] Error code 1 (ignored)
+
+opt-_jl___-tgt-a__-cmd-ais
+echo running; false
+running
+*** [opt-_jl___-tgt-a__-cmd-ais] Error code 1 (ignored)
+
+opt-_jl___-tgt-a_s-cmd-___
+running
+
+opt-_jl___-tgt-a_s-cmd-__s
+running
+
+opt-_jl___-tgt-a_s-cmd-_i_
+echo running; false
+running
+*** [opt-_jl___-tgt-a_s-cmd-_i_] Error code 1 (ignored)
+
+opt-_jl___-tgt-a_s-cmd-_is
+echo running; false
+running
+*** [opt-_jl___-tgt-a_s-cmd-_is] Error code 1 (ignored)
+
+opt-_jl___-tgt-a_s-cmd-a__
+running
+
+opt-_jl___-tgt-a_s-cmd-a_s
+running
+
+opt-_jl___-tgt-a_s-cmd-ai_
+echo running; false
+running
+*** [opt-_jl___-tgt-a_s-cmd-ai_] Error code 1 (ignored)
+
+opt-_jl___-tgt-a_s-cmd-ais
+echo running; false
+running
+*** [opt-_jl___-tgt-a_s-cmd-ais] Error code 1 (ignored)
+
+opt-_jl___-tgt-ai_-cmd-___
+echo running; false
+running
+*** [opt-_jl___-tgt-ai_-cmd-___] Error code 1 (ignored)
+
+opt-_jl___-tgt-ai_-cmd-__s
+echo running; false
+running
+*** [opt-_jl___-tgt-ai_-cmd-__s] Error code 1 (ignored)
+
+opt-_jl___-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** [opt-_jl___-tgt-ai_-cmd-_i_] Error code 1 (ignored)
+
+opt-_jl___-tgt-ai_-cmd-_is
+echo running; false
+running
+*** [opt-_jl___-tgt-ai_-cmd-_is] Error code 1 (ignored)
+
+opt-_jl___-tgt-ai_-cmd-a__
+echo running; false
+running
+*** [opt-_jl___-tgt-ai_-cmd-a__] Error code 1 (ignored)
+
+opt-_jl___-tgt-ai_-cmd-a_s
+echo running; false
+running
+*** [opt-_jl___-tgt-ai_-cmd-a_s] Error code 1 (ignored)
+
+opt-_jl___-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** [opt-_jl___-tgt-ai_-cmd-ai_] Error code 1 (ignored)
+
+opt-_jl___-tgt-ai_-cmd-ais
+echo running; false
+running
+*** [opt-_jl___-tgt-ai_-cmd-ais] Error code 1 (ignored)
+
+opt-_jl___-tgt-ais-cmd-___
+running
+*** [opt-_jl___-tgt-ais-cmd-___] Error code 1 (ignored)
+
+opt-_jl___-tgt-ais-cmd-__s
+running
+*** [opt-_jl___-tgt-ais-cmd-__s] Error code 1 (ignored)
+
+opt-_jl___-tgt-ais-cmd-_i_
+echo running; false
+running
+*** [opt-_jl___-tgt-ais-cmd-_i_] Error code 1 (ignored)
+
+opt-_jl___-tgt-ais-cmd-_is
+echo running; false
+running
+*** [opt-_jl___-tgt-ais-cmd-_is] Error code 1 (ignored)
+
+opt-_jl___-tgt-ais-cmd-a__
+running
+*** [opt-_jl___-tgt-ais-cmd-a__] Error code 1 (ignored)
+
+opt-_jl___-tgt-ais-cmd-a_s
+running
+*** [opt-_jl___-tgt-ais-cmd-a_s] Error code 1 (ignored)
+
+opt-_jl___-tgt-ais-cmd-ai_
+echo running; false
+running
+*** [opt-_jl___-tgt-ais-cmd-ai_] Error code 1 (ignored)
+
+opt-_jl___-tgt-ais-cmd-ais
+echo running; false
+running
+*** [opt-_jl___-tgt-ais-cmd-ais] Error code 1 (ignored)
+
+opt-_jln__-tgt-___-cmd-___
+echo "echo running"
+{ echo running
+} || exit $?
+
+opt-_jln__-tgt-___-cmd-__s
+echo "echo running"
+{ echo running
+} || exit $?
+
+opt-_jln__-tgt-___-cmd-_i_
+echo running; false
+
+opt-_jln__-tgt-___-cmd-_is
+echo running; false
+
+opt-_jln__-tgt-___-cmd-a__
+echo running
+running
+
+opt-_jln__-tgt-___-cmd-a_s
+echo running
+running
+
+opt-_jln__-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-__s-cmd-___
+{ echo running
+} || exit $?
+
+opt-_jln__-tgt-__s-cmd-__s
+{ echo running
+} || exit $?
+
+opt-_jln__-tgt-__s-cmd-_i_
+echo running; false
+
+opt-_jln__-tgt-__s-cmd-_is
+echo running; false
+
+opt-_jln__-tgt-__s-cmd-a__
+echo running
+running
+
+opt-_jln__-tgt-__s-cmd-a_s
+echo running
+running
+
+opt-_jln__-tgt-__s-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-_i_-cmd-___
+echo "echo running; false"
+{ echo running; false
+} || exit $?
+
+opt-_jln__-tgt-_i_-cmd-__s
+echo "echo running; false"
+{ echo running; false
+} || exit $?
+
+opt-_jln__-tgt-_i_-cmd-_i_
+echo running; false
+
+opt-_jln__-tgt-_i_-cmd-_is
+echo running; false
+
+opt-_jln__-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-_is-cmd-___
+{ echo running; false
+} || exit $?
+
+opt-_jln__-tgt-_is-cmd-__s
+{ echo running; false
+} || exit $?
+
+opt-_jln__-tgt-_is-cmd-_i_
+echo running; false
+
+opt-_jln__-tgt-_is-cmd-_is
+echo running; false
+
+opt-_jln__-tgt-_is-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-_is-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-_jln__-tgt-a__-cmd-___
+echo running
+running
+
+opt-_jln__-tgt-a__-cmd-__s
+echo running
+running
+
+opt-_jln__-tgt-a__-cmd-_i_
+echo running; false
+running
+
+opt-_jln__-tgt-a__-cmd-_is
+echo running; false
+running
+
+opt-_jln__-tgt-a__-cmd-a__
+echo running
+running
+
+opt-_jln__-tgt-a__-cmd-a_s
+echo running
+running
+
+opt-_jln__-tgt-a__-cmd-ai_
+echo running; false
+running
+
+opt-_jln__-tgt-a__-cmd-ais
+echo running; false
+running
+
+opt-_jln__-tgt-a_s-cmd-___
+running
+
+opt-_jln__-tgt-a_s-cmd-__s
+running
+
+opt-_jln__-tgt-a_s-cmd-_i_
+echo running; false
+running
+
+opt-_jln__-tgt-a_s-cmd-_is
+echo running; false
+running
+
+opt-_jln__-tgt-a_s-cmd-a__
+running
+
+opt-_jln__-tgt-a_s-cmd-a_s
+running
+
+opt-_jln__-tgt-a_s-cmd-ai_
+echo running; false
+running
+
+opt-_jln__-tgt-a_s-cmd-ais
+echo running; false
+running
+
+opt-_jln__-tgt-ai_-cmd-___
+echo running; false
+running
+
+opt-_jln__-tgt-ai_-cmd-__s
+echo running; false
+running
+
+opt-_jln__-tgt-ai_-cmd-_i_
+echo running; false
+running
+
+opt-_jln__-tgt-ai_-cmd-_is
+echo running; false
+running
+
+opt-_jln__-tgt-ai_-cmd-a__
+echo running; false
+running
+
+opt-_jln__-tgt-ai_-cmd-a_s
+echo running; false
+running
+
+opt-_jln__-tgt-ai_-cmd-ai_
+echo running; false
+running
+
+opt-_jln__-tgt-ai_-cmd-ais
+echo running; false
+running
+
+opt-_jln__-tgt-ais-cmd-___
+running
+
+opt-_jln__-tgt-ais-cmd-__s
+running
+
+opt-_jln__-tgt-ais-cmd-_i_
+echo running; false
+running
+
+opt-_jln__-tgt-ais-cmd-_is
+echo running; false
+running
+
+opt-_jln__-tgt-ais-cmd-a__
+running
+
+opt-_jln__-tgt-ais-cmd-a_s
+running
+
+opt-_jln__-tgt-ais-cmd-ai_
+echo running; false
+running
+
+opt-_jln__-tgt-ais-cmd-ais
+echo running; false
+running
+
+opt-i_____-tgt-___-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-___-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-___-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-___-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-___-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-___-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-___-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-__s-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-__s-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-__s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-__s-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-__s-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-__s-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-__s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-__s-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_i_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_i_-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_i_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_i_-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_i_-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_i_-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_is-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_is-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_is-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_is-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_is-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_is-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_is-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-_is-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a__-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a__-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a__-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a__-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a__-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a__-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a__-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a__-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a_s-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a_s-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a_s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a_s-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a_s-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a_s-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a_s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-a_s-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ai_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ai_-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ai_-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ai_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ai_-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ai_-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ais-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ais-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ais-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ais-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ais-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ais-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ais-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_____-tgt-ais-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-___-cmd-___
+echo running; false
+
+opt-i__n__-tgt-___-cmd-__s
+echo running; false
+
+opt-i__n__-tgt-___-cmd-_i_
+echo running; false
+
+opt-i__n__-tgt-___-cmd-_is
+echo running; false
+
+opt-i__n__-tgt-___-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-___-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-__s-cmd-___
+echo running; false
+
+opt-i__n__-tgt-__s-cmd-__s
+echo running; false
+
+opt-i__n__-tgt-__s-cmd-_i_
+echo running; false
+
+opt-i__n__-tgt-__s-cmd-_is
+echo running; false
+
+opt-i__n__-tgt-__s-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-__s-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-__s-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-_i_-cmd-___
+echo running; false
+
+opt-i__n__-tgt-_i_-cmd-__s
+echo running; false
+
+opt-i__n__-tgt-_i_-cmd-_i_
+echo running; false
+
+opt-i__n__-tgt-_i_-cmd-_is
+echo running; false
+
+opt-i__n__-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-_is-cmd-___
+echo running; false
+
+opt-i__n__-tgt-_is-cmd-__s
+echo running; false
+
+opt-i__n__-tgt-_is-cmd-_i_
+echo running; false
+
+opt-i__n__-tgt-_is-cmd-_is
+echo running; false
+
+opt-i__n__-tgt-_is-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-_is-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a__-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a__-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a__-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a__-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a__-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a__-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a__-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a__-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a_s-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a_s-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a_s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a_s-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a_s-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a_s-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a_s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-a_s-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ai_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ai_-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ai_-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ai_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ai_-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ai_-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ais-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ais-cmd-__s
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ais-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ais-cmd-_is
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ais-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ais-cmd-a_s
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ais-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i__n__-tgt-ais-cmd-ais
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-___-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-___-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-___-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-___-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-___-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-___-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-__s-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-__s-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-__s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-__s-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-__s-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-__s-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-__s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_i_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_i_-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_i_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_i_-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_is-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_is-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_is-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_is-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_is-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_is-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a__-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a__-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a__-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a__-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a__-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a__-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a__-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a__-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a_s-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a_s-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a_s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a_s-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a_s-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a_s-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a_s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-a_s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ai_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ai_-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ai_-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ai_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ai_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ai_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ais-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ais-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ais-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ais-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ais-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ais-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ais-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_l___-tgt-ais-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-___-cmd-___
+echo running; false
+
+opt-i_ln__-tgt-___-cmd-__s
+echo running; false
+
+opt-i_ln__-tgt-___-cmd-_i_
+echo running; false
+
+opt-i_ln__-tgt-___-cmd-_is
+echo running; false
+
+opt-i_ln__-tgt-___-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-___-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-__s-cmd-___
+echo running; false
+
+opt-i_ln__-tgt-__s-cmd-__s
+echo running; false
+
+opt-i_ln__-tgt-__s-cmd-_i_
+echo running; false
+
+opt-i_ln__-tgt-__s-cmd-_is
+echo running; false
+
+opt-i_ln__-tgt-__s-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-__s-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-__s-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-_i_-cmd-___
+echo running; false
+
+opt-i_ln__-tgt-_i_-cmd-__s
+echo running; false
+
+opt-i_ln__-tgt-_i_-cmd-_i_
+echo running; false
+
+opt-i_ln__-tgt-_i_-cmd-_is
+echo running; false
+
+opt-i_ln__-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-_is-cmd-___
+echo running; false
+
+opt-i_ln__-tgt-_is-cmd-__s
+echo running; false
+
+opt-i_ln__-tgt-_is-cmd-_i_
+echo running; false
+
+opt-i_ln__-tgt-_is-cmd-_is
+echo running; false
+
+opt-i_ln__-tgt-_is-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-_is-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a__-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a__-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a__-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a__-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a__-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a__-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a__-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a__-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a_s-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a_s-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a_s-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a_s-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a_s-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a_s-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a_s-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-a_s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ai_-cmd-___
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ai_-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ai_-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ai_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ai_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ai_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ais-cmd-___
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ais-cmd-__s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ais-cmd-_i_
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ais-cmd-_is
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ais-cmd-a__
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ais-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ais-cmd-ai_
+running
+*** Error code 1 (ignored)
+
+opt-i_ln__-tgt-ais-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij____-tgt-___-cmd-___
+echo running; false
+running
+*** [opt-ij____-tgt-___-cmd-___] Error code 1 (ignored)
+
+opt-ij____-tgt-___-cmd-__s
+running
+*** [opt-ij____-tgt-___-cmd-__s] Error code 1 (ignored)
+
+opt-ij____-tgt-___-cmd-_i_
+echo running; false
+running
+*** [opt-ij____-tgt-___-cmd-_i_] Error code 1 (ignored)
+
+opt-ij____-tgt-___-cmd-_is
+running
+*** [opt-ij____-tgt-___-cmd-_is] Error code 1 (ignored)
+
+opt-ij____-tgt-___-cmd-a__
+echo running; false
+running
+*** [opt-ij____-tgt-___-cmd-a__] Error code 1 (ignored)
+
+opt-ij____-tgt-___-cmd-a_s
+running
+*** [opt-ij____-tgt-___-cmd-a_s] Error code 1 (ignored)
+
+opt-ij____-tgt-___-cmd-ai_
+echo running; false
+running
+*** [opt-ij____-tgt-___-cmd-ai_] Error code 1 (ignored)
+
+opt-ij____-tgt-___-cmd-ais
+running
+*** [opt-ij____-tgt-___-cmd-ais] Error code 1 (ignored)
+
+opt-ij____-tgt-__s-cmd-___
+running
+*** [opt-ij____-tgt-__s-cmd-___] Error code 1 (ignored)
+
+opt-ij____-tgt-__s-cmd-__s
+running
+*** [opt-ij____-tgt-__s-cmd-__s] Error code 1 (ignored)
+
+opt-ij____-tgt-__s-cmd-_i_
+echo running; false
+running
+*** [opt-ij____-tgt-__s-cmd-_i_] Error code 1 (ignored)
+
+opt-ij____-tgt-__s-cmd-_is
+running
+*** [opt-ij____-tgt-__s-cmd-_is] Error code 1 (ignored)
+
+opt-ij____-tgt-__s-cmd-a__
+running
+*** [opt-ij____-tgt-__s-cmd-a__] Error code 1 (ignored)
+
+opt-ij____-tgt-__s-cmd-a_s
+running
+*** [opt-ij____-tgt-__s-cmd-a_s] Error code 1 (ignored)
+
+opt-ij____-tgt-__s-cmd-ai_
+echo running; false
+running
+*** [opt-ij____-tgt-__s-cmd-ai_] Error code 1 (ignored)
+
+opt-ij____-tgt-__s-cmd-ais
+running
+*** [opt-ij____-tgt-__s-cmd-ais] Error code 1 (ignored)
+
+opt-ij____-tgt-_i_-cmd-___
+echo running; false
+running
+*** [opt-ij____-tgt-_i_-cmd-___] Error code 1 (ignored)
+
+opt-ij____-tgt-_i_-cmd-__s
+running
+*** [opt-ij____-tgt-_i_-cmd-__s] Error code 1 (ignored)
+
+opt-ij____-tgt-_i_-cmd-_i_
+echo running; false
+running
+*** [opt-ij____-tgt-_i_-cmd-_i_] Error code 1 (ignored)
+
+opt-ij____-tgt-_i_-cmd-_is
+running
+*** [opt-ij____-tgt-_i_-cmd-_is] Error code 1 (ignored)
+
+opt-ij____-tgt-_i_-cmd-a__
+echo running; false
+running
+*** [opt-ij____-tgt-_i_-cmd-a__] Error code 1 (ignored)
+
+opt-ij____-tgt-_i_-cmd-a_s
+running
+*** [opt-ij____-tgt-_i_-cmd-a_s] Error code 1 (ignored)
+
+opt-ij____-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** [opt-ij____-tgt-_i_-cmd-ai_] Error code 1 (ignored)
+
+opt-ij____-tgt-_i_-cmd-ais
+running
+*** [opt-ij____-tgt-_i_-cmd-ais] Error code 1 (ignored)
+
+opt-ij____-tgt-_is-cmd-___
+running
+*** [opt-ij____-tgt-_is-cmd-___] Error code 1 (ignored)
+
+opt-ij____-tgt-_is-cmd-__s
+running
+*** [opt-ij____-tgt-_is-cmd-__s] Error code 1 (ignored)
+
+opt-ij____-tgt-_is-cmd-_i_
+echo running; false
+running
+*** [opt-ij____-tgt-_is-cmd-_i_] Error code 1 (ignored)
+
+opt-ij____-tgt-_is-cmd-_is
+running
+*** [opt-ij____-tgt-_is-cmd-_is] Error code 1 (ignored)
+
+opt-ij____-tgt-_is-cmd-a__
+running
+*** [opt-ij____-tgt-_is-cmd-a__] Error code 1 (ignored)
+
+opt-ij____-tgt-_is-cmd-a_s
+running
+*** [opt-ij____-tgt-_is-cmd-a_s] Error code 1 (ignored)
+
+opt-ij____-tgt-_is-cmd-ai_
+echo running; false
+running
+*** [opt-ij____-tgt-_is-cmd-ai_] Error code 1 (ignored)
+
+opt-ij____-tgt-_is-cmd-ais
+running
+*** [opt-ij____-tgt-_is-cmd-ais] Error code 1 (ignored)
+
+opt-ij____-tgt-a__-cmd-___
+echo running; false
+running
+*** [opt-ij____-tgt-a__-cmd-___] Error code 1 (ignored)
+
+opt-ij____-tgt-a__-cmd-__s
+running
+*** [opt-ij____-tgt-a__-cmd-__s] Error code 1 (ignored)
+
+opt-ij____-tgt-a__-cmd-_i_
+echo running; false
+running
+*** [opt-ij____-tgt-a__-cmd-_i_] Error code 1 (ignored)
+
+opt-ij____-tgt-a__-cmd-_is
+running
+*** [opt-ij____-tgt-a__-cmd-_is] Error code 1 (ignored)
+
+opt-ij____-tgt-a__-cmd-a__
+echo running; false
+running
+*** [opt-ij____-tgt-a__-cmd-a__] Error code 1 (ignored)
+
+opt-ij____-tgt-a__-cmd-a_s
+running
+*** [opt-ij____-tgt-a__-cmd-a_s] Error code 1 (ignored)
+
+opt-ij____-tgt-a__-cmd-ai_
+echo running; false
+running
+*** [opt-ij____-tgt-a__-cmd-ai_] Error code 1 (ignored)
+
+opt-ij____-tgt-a__-cmd-ais
+running
+*** [opt-ij____-tgt-a__-cmd-ais] Error code 1 (ignored)
+
+opt-ij____-tgt-a_s-cmd-___
+running
+*** [opt-ij____-tgt-a_s-cmd-___] Error code 1 (ignored)
+
+opt-ij____-tgt-a_s-cmd-__s
+running
+*** [opt-ij____-tgt-a_s-cmd-__s] Error code 1 (ignored)
+
+opt-ij____-tgt-a_s-cmd-_i_
+echo running; false
+running
+*** [opt-ij____-tgt-a_s-cmd-_i_] Error code 1 (ignored)
+
+opt-ij____-tgt-a_s-cmd-_is
+running
+*** [opt-ij____-tgt-a_s-cmd-_is] Error code 1 (ignored)
+
+opt-ij____-tgt-a_s-cmd-a__
+running
+*** [opt-ij____-tgt-a_s-cmd-a__] Error code 1 (ignored)
+
+opt-ij____-tgt-a_s-cmd-a_s
+running
+*** [opt-ij____-tgt-a_s-cmd-a_s] Error code 1 (ignored)
+
+opt-ij____-tgt-a_s-cmd-ai_
+echo running; false
+running
+*** [opt-ij____-tgt-a_s-cmd-ai_] Error code 1 (ignored)
+
+opt-ij____-tgt-a_s-cmd-ais
+running
+*** [opt-ij____-tgt-a_s-cmd-ais] Error code 1 (ignored)
+
+opt-ij____-tgt-ai_-cmd-___
+echo running; false
+running
+*** [opt-ij____-tgt-ai_-cmd-___] Error code 1 (ignored)
+
+opt-ij____-tgt-ai_-cmd-__s
+running
+*** [opt-ij____-tgt-ai_-cmd-__s] Error code 1 (ignored)
+
+opt-ij____-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** [opt-ij____-tgt-ai_-cmd-_i_] Error code 1 (ignored)
+
+opt-ij____-tgt-ai_-cmd-_is
+running
+*** [opt-ij____-tgt-ai_-cmd-_is] Error code 1 (ignored)
+
+opt-ij____-tgt-ai_-cmd-a__
+echo running; false
+running
+*** [opt-ij____-tgt-ai_-cmd-a__] Error code 1 (ignored)
+
+opt-ij____-tgt-ai_-cmd-a_s
+running
+*** [opt-ij____-tgt-ai_-cmd-a_s] Error code 1 (ignored)
+
+opt-ij____-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** [opt-ij____-tgt-ai_-cmd-ai_] Error code 1 (ignored)
+
+opt-ij____-tgt-ai_-cmd-ais
+running
+*** [opt-ij____-tgt-ai_-cmd-ais] Error code 1 (ignored)
+
+opt-ij____-tgt-ais-cmd-___
+running
+*** [opt-ij____-tgt-ais-cmd-___] Error code 1 (ignored)
+
+opt-ij____-tgt-ais-cmd-__s
+running
+*** [opt-ij____-tgt-ais-cmd-__s] Error code 1 (ignored)
+
+opt-ij____-tgt-ais-cmd-_i_
+echo running; false
+running
+*** [opt-ij____-tgt-ais-cmd-_i_] Error code 1 (ignored)
+
+opt-ij____-tgt-ais-cmd-_is
+running
+*** [opt-ij____-tgt-ais-cmd-_is] Error code 1 (ignored)
+
+opt-ij____-tgt-ais-cmd-a__
+running
+*** [opt-ij____-tgt-ais-cmd-a__] Error code 1 (ignored)
+
+opt-ij____-tgt-ais-cmd-a_s
+running
+*** [opt-ij____-tgt-ais-cmd-a_s] Error code 1 (ignored)
+
+opt-ij____-tgt-ais-cmd-ai_
+echo running; false
+running
+*** [opt-ij____-tgt-ais-cmd-ai_] Error code 1 (ignored)
+
+opt-ij____-tgt-ais-cmd-ais
+running
+*** [opt-ij____-tgt-ais-cmd-ais] Error code 1 (ignored)
+
+opt-ij_n__-tgt-___-cmd-___
+echo "echo running; false"
+{ echo running; false
+} || exit $?
+
+opt-ij_n__-tgt-___-cmd-__s
+{ echo running; false
+} || exit $?
+
+opt-ij_n__-tgt-___-cmd-_i_
+echo running; false
+
+opt-ij_n__-tgt-___-cmd-_is
+echo running; false
+
+opt-ij_n__-tgt-___-cmd-a__
+echo running; false
+running
+*** Error code 1 (continuing)
+
+opt-ij_n__-tgt-___-cmd-a_s
+echo running; false
+running
+*** Error code 1 (continuing)
+
+opt-ij_n__-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-__s-cmd-___
+{ echo running; false
+} || exit $?
+
+opt-ij_n__-tgt-__s-cmd-__s
+{ echo running; false
+} || exit $?
+
+opt-ij_n__-tgt-__s-cmd-_i_
+echo running; false
+
+opt-ij_n__-tgt-__s-cmd-_is
+echo running; false
+
+opt-ij_n__-tgt-__s-cmd-a__
+echo running; false
+running
+*** Error code 1 (continuing)
+
+opt-ij_n__-tgt-__s-cmd-a_s
+echo running; false
+running
+*** Error code 1 (continuing)
+
+opt-ij_n__-tgt-__s-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-_i_-cmd-___
+echo "echo running; false"
+{ echo running; false
+} || exit $?
+
+opt-ij_n__-tgt-_i_-cmd-__s
+{ echo running; false
+} || exit $?
+
+opt-ij_n__-tgt-_i_-cmd-_i_
+echo running; false
+
+opt-ij_n__-tgt-_i_-cmd-_is
+echo running; false
+
+opt-ij_n__-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-_is-cmd-___
+{ echo running; false
+} || exit $?
+
+opt-ij_n__-tgt-_is-cmd-__s
+{ echo running; false
+} || exit $?
+
+opt-ij_n__-tgt-_is-cmd-_i_
+echo running; false
+
+opt-ij_n__-tgt-_is-cmd-_is
+echo running; false
+
+opt-ij_n__-tgt-_is-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-_is-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ij_n__-tgt-a__-cmd-___
+echo running; false
+running
+
+opt-ij_n__-tgt-a__-cmd-__s
+running
+
+opt-ij_n__-tgt-a__-cmd-_i_
+echo running; false
+running
+
+opt-ij_n__-tgt-a__-cmd-_is
+running
+
+opt-ij_n__-tgt-a__-cmd-a__
+echo running; false
+running
+
+opt-ij_n__-tgt-a__-cmd-a_s
+running
+
+opt-ij_n__-tgt-a__-cmd-ai_
+echo running; false
+running
+
+opt-ij_n__-tgt-a__-cmd-ais
+running
+
+opt-ij_n__-tgt-a_s-cmd-___
+running
+
+opt-ij_n__-tgt-a_s-cmd-__s
+running
+
+opt-ij_n__-tgt-a_s-cmd-_i_
+echo running; false
+running
+
+opt-ij_n__-tgt-a_s-cmd-_is
+running
+
+opt-ij_n__-tgt-a_s-cmd-a__
+running
+
+opt-ij_n__-tgt-a_s-cmd-a_s
+running
+
+opt-ij_n__-tgt-a_s-cmd-ai_
+echo running; false
+running
+
+opt-ij_n__-tgt-a_s-cmd-ais
+running
+
+opt-ij_n__-tgt-ai_-cmd-___
+echo running; false
+running
+
+opt-ij_n__-tgt-ai_-cmd-__s
+running
+
+opt-ij_n__-tgt-ai_-cmd-_i_
+echo running; false
+running
+
+opt-ij_n__-tgt-ai_-cmd-_is
+running
+
+opt-ij_n__-tgt-ai_-cmd-a__
+echo running; false
+running
+
+opt-ij_n__-tgt-ai_-cmd-a_s
+running
+
+opt-ij_n__-tgt-ai_-cmd-ai_
+echo running; false
+running
+
+opt-ij_n__-tgt-ai_-cmd-ais
+running
+
+opt-ij_n__-tgt-ais-cmd-___
+running
+
+opt-ij_n__-tgt-ais-cmd-__s
+running
+
+opt-ij_n__-tgt-ais-cmd-_i_
+echo running; false
+running
+
+opt-ij_n__-tgt-ais-cmd-_is
+running
+
+opt-ij_n__-tgt-ais-cmd-a__
+running
+
+opt-ij_n__-tgt-ais-cmd-a_s
+running
+
+opt-ij_n__-tgt-ais-cmd-ai_
+echo running; false
+running
+
+opt-ij_n__-tgt-ais-cmd-ais
+running
+
+opt-ijl___-tgt-___-cmd-___
+echo running; false
+running
+*** [opt-ijl___-tgt-___-cmd-___] Error code 1 (ignored)
+
+opt-ijl___-tgt-___-cmd-__s
+echo running; false
+running
+*** [opt-ijl___-tgt-___-cmd-__s] Error code 1 (ignored)
+
+opt-ijl___-tgt-___-cmd-_i_
+echo running; false
+running
+*** [opt-ijl___-tgt-___-cmd-_i_] Error code 1 (ignored)
+
+opt-ijl___-tgt-___-cmd-_is
+echo running; false
+running
+*** [opt-ijl___-tgt-___-cmd-_is] Error code 1 (ignored)
+
+opt-ijl___-tgt-___-cmd-a__
+echo running; false
+running
+*** [opt-ijl___-tgt-___-cmd-a__] Error code 1 (ignored)
+
+opt-ijl___-tgt-___-cmd-a_s
+echo running; false
+running
+*** [opt-ijl___-tgt-___-cmd-a_s] Error code 1 (ignored)
+
+opt-ijl___-tgt-___-cmd-ai_
+echo running; false
+running
+*** [opt-ijl___-tgt-___-cmd-ai_] Error code 1 (ignored)
+
+opt-ijl___-tgt-___-cmd-ais
+echo running; false
+running
+*** [opt-ijl___-tgt-___-cmd-ais] Error code 1 (ignored)
+
+opt-ijl___-tgt-__s-cmd-___
+running
+*** [opt-ijl___-tgt-__s-cmd-___] Error code 1 (ignored)
+
+opt-ijl___-tgt-__s-cmd-__s
+running
+*** [opt-ijl___-tgt-__s-cmd-__s] Error code 1 (ignored)
+
+opt-ijl___-tgt-__s-cmd-_i_
+echo running; false
+running
+*** [opt-ijl___-tgt-__s-cmd-_i_] Error code 1 (ignored)
+
+opt-ijl___-tgt-__s-cmd-_is
+echo running; false
+running
+*** [opt-ijl___-tgt-__s-cmd-_is] Error code 1 (ignored)
+
+opt-ijl___-tgt-__s-cmd-a__
+running
+*** [opt-ijl___-tgt-__s-cmd-a__] Error code 1 (ignored)
+
+opt-ijl___-tgt-__s-cmd-a_s
+running
+*** [opt-ijl___-tgt-__s-cmd-a_s] Error code 1 (ignored)
+
+opt-ijl___-tgt-__s-cmd-ai_
+echo running; false
+running
+*** [opt-ijl___-tgt-__s-cmd-ai_] Error code 1 (ignored)
+
+opt-ijl___-tgt-__s-cmd-ais
+echo running; false
+running
+*** [opt-ijl___-tgt-__s-cmd-ais] Error code 1 (ignored)
+
+opt-ijl___-tgt-_i_-cmd-___
+echo running; false
+running
+*** [opt-ijl___-tgt-_i_-cmd-___] Error code 1 (ignored)
+
+opt-ijl___-tgt-_i_-cmd-__s
+echo running; false
+running
+*** [opt-ijl___-tgt-_i_-cmd-__s] Error code 1 (ignored)
+
+opt-ijl___-tgt-_i_-cmd-_i_
+echo running; false
+running
+*** [opt-ijl___-tgt-_i_-cmd-_i_] Error code 1 (ignored)
+
+opt-ijl___-tgt-_i_-cmd-_is
+echo running; false
+running
+*** [opt-ijl___-tgt-_i_-cmd-_is] Error code 1 (ignored)
+
+opt-ijl___-tgt-_i_-cmd-a__
+echo running; false
+running
+*** [opt-ijl___-tgt-_i_-cmd-a__] Error code 1 (ignored)
+
+opt-ijl___-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** [opt-ijl___-tgt-_i_-cmd-a_s] Error code 1 (ignored)
+
+opt-ijl___-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** [opt-ijl___-tgt-_i_-cmd-ai_] Error code 1 (ignored)
+
+opt-ijl___-tgt-_i_-cmd-ais
+echo running; false
+running
+*** [opt-ijl___-tgt-_i_-cmd-ais] Error code 1 (ignored)
+
+opt-ijl___-tgt-_is-cmd-___
+running
+*** [opt-ijl___-tgt-_is-cmd-___] Error code 1 (ignored)
+
+opt-ijl___-tgt-_is-cmd-__s
+running
+*** [opt-ijl___-tgt-_is-cmd-__s] Error code 1 (ignored)
+
+opt-ijl___-tgt-_is-cmd-_i_
+echo running; false
+running
+*** [opt-ijl___-tgt-_is-cmd-_i_] Error code 1 (ignored)
+
+opt-ijl___-tgt-_is-cmd-_is
+echo running; false
+running
+*** [opt-ijl___-tgt-_is-cmd-_is] Error code 1 (ignored)
+
+opt-ijl___-tgt-_is-cmd-a__
+running
+*** [opt-ijl___-tgt-_is-cmd-a__] Error code 1 (ignored)
+
+opt-ijl___-tgt-_is-cmd-a_s
+running
+*** [opt-ijl___-tgt-_is-cmd-a_s] Error code 1 (ignored)
+
+opt-ijl___-tgt-_is-cmd-ai_
+echo running; false
+running
+*** [opt-ijl___-tgt-_is-cmd-ai_] Error code 1 (ignored)
+
+opt-ijl___-tgt-_is-cmd-ais
+echo running; false
+running
+*** [opt-ijl___-tgt-_is-cmd-ais] Error code 1 (ignored)
+
+opt-ijl___-tgt-a__-cmd-___
+echo running; false
+running
+*** [opt-ijl___-tgt-a__-cmd-___] Error code 1 (ignored)
+
+opt-ijl___-tgt-a__-cmd-__s
+echo running; false
+running
+*** [opt-ijl___-tgt-a__-cmd-__s] Error code 1 (ignored)
+
+opt-ijl___-tgt-a__-cmd-_i_
+echo running; false
+running
+*** [opt-ijl___-tgt-a__-cmd-_i_] Error code 1 (ignored)
+
+opt-ijl___-tgt-a__-cmd-_is
+echo running; false
+running
+*** [opt-ijl___-tgt-a__-cmd-_is] Error code 1 (ignored)
+
+opt-ijl___-tgt-a__-cmd-a__
+echo running; false
+running
+*** [opt-ijl___-tgt-a__-cmd-a__] Error code 1 (ignored)
+
+opt-ijl___-tgt-a__-cmd-a_s
+echo running; false
+running
+*** [opt-ijl___-tgt-a__-cmd-a_s] Error code 1 (ignored)
+
+opt-ijl___-tgt-a__-cmd-ai_
+echo running; false
+running
+*** [opt-ijl___-tgt-a__-cmd-ai_] Error code 1 (ignored)
+
+opt-ijl___-tgt-a__-cmd-ais
+echo running; false
+running
+*** [opt-ijl___-tgt-a__-cmd-ais] Error code 1 (ignored)
+
+opt-ijl___-tgt-a_s-cmd-___
+running
+*** [opt-ijl___-tgt-a_s-cmd-___] Error code 1 (ignored)
+
+opt-ijl___-tgt-a_s-cmd-__s
+running
+*** [opt-ijl___-tgt-a_s-cmd-__s] Error code 1 (ignored)
+
+opt-ijl___-tgt-a_s-cmd-_i_
+echo running; false
+running
+*** [opt-ijl___-tgt-a_s-cmd-_i_] Error code 1 (ignored)
+
+opt-ijl___-tgt-a_s-cmd-_is
+echo running; false
+running
+*** [opt-ijl___-tgt-a_s-cmd-_is] Error code 1 (ignored)
+
+opt-ijl___-tgt-a_s-cmd-a__
+running
+*** [opt-ijl___-tgt-a_s-cmd-a__] Error code 1 (ignored)
+
+opt-ijl___-tgt-a_s-cmd-a_s
+running
+*** [opt-ijl___-tgt-a_s-cmd-a_s] Error code 1 (ignored)
+
+opt-ijl___-tgt-a_s-cmd-ai_
+echo running; false
+running
+*** [opt-ijl___-tgt-a_s-cmd-ai_] Error code 1 (ignored)
+
+opt-ijl___-tgt-a_s-cmd-ais
+echo running; false
+running
+*** [opt-ijl___-tgt-a_s-cmd-ais] Error code 1 (ignored)
+
+opt-ijl___-tgt-ai_-cmd-___
+echo running; false
+running
+*** [opt-ijl___-tgt-ai_-cmd-___] Error code 1 (ignored)
+
+opt-ijl___-tgt-ai_-cmd-__s
+echo running; false
+running
+*** [opt-ijl___-tgt-ai_-cmd-__s] Error code 1 (ignored)
+
+opt-ijl___-tgt-ai_-cmd-_i_
+echo running; false
+running
+*** [opt-ijl___-tgt-ai_-cmd-_i_] Error code 1 (ignored)
+
+opt-ijl___-tgt-ai_-cmd-_is
+echo running; false
+running
+*** [opt-ijl___-tgt-ai_-cmd-_is] Error code 1 (ignored)
+
+opt-ijl___-tgt-ai_-cmd-a__
+echo running; false
+running
+*** [opt-ijl___-tgt-ai_-cmd-a__] Error code 1 (ignored)
+
+opt-ijl___-tgt-ai_-cmd-a_s
+echo running; false
+running
+*** [opt-ijl___-tgt-ai_-cmd-a_s] Error code 1 (ignored)
+
+opt-ijl___-tgt-ai_-cmd-ai_
+echo running; false
+running
+*** [opt-ijl___-tgt-ai_-cmd-ai_] Error code 1 (ignored)
+
+opt-ijl___-tgt-ai_-cmd-ais
+echo running; false
+running
+*** [opt-ijl___-tgt-ai_-cmd-ais] Error code 1 (ignored)
+
+opt-ijl___-tgt-ais-cmd-___
+running
+*** [opt-ijl___-tgt-ais-cmd-___] Error code 1 (ignored)
+
+opt-ijl___-tgt-ais-cmd-__s
+running
+*** [opt-ijl___-tgt-ais-cmd-__s] Error code 1 (ignored)
+
+opt-ijl___-tgt-ais-cmd-_i_
+echo running; false
+running
+*** [opt-ijl___-tgt-ais-cmd-_i_] Error code 1 (ignored)
+
+opt-ijl___-tgt-ais-cmd-_is
+echo running; false
+running
+*** [opt-ijl___-tgt-ais-cmd-_is] Error code 1 (ignored)
+
+opt-ijl___-tgt-ais-cmd-a__
+running
+*** [opt-ijl___-tgt-ais-cmd-a__] Error code 1 (ignored)
+
+opt-ijl___-tgt-ais-cmd-a_s
+running
+*** [opt-ijl___-tgt-ais-cmd-a_s] Error code 1 (ignored)
+
+opt-ijl___-tgt-ais-cmd-ai_
+echo running; false
+running
+*** [opt-ijl___-tgt-ais-cmd-ai_] Error code 1 (ignored)
+
+opt-ijl___-tgt-ais-cmd-ais
+echo running; false
+running
+*** [opt-ijl___-tgt-ais-cmd-ais] Error code 1 (ignored)
+
+opt-ijln__-tgt-___-cmd-___
+echo "echo running; false"
+{ echo running; false
+} || exit $?
+
+opt-ijln__-tgt-___-cmd-__s
+echo "echo running; false"
+{ echo running; false
+} || exit $?
+
+opt-ijln__-tgt-___-cmd-_i_
+echo running; false
+
+opt-ijln__-tgt-___-cmd-_is
+echo running; false
+
+opt-ijln__-tgt-___-cmd-a__
+echo running; false
+running
+*** Error code 1 (continuing)
+
+opt-ijln__-tgt-___-cmd-a_s
+echo running; false
+running
+*** Error code 1 (continuing)
+
+opt-ijln__-tgt-___-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-___-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-__s-cmd-___
+{ echo running; false
+} || exit $?
+
+opt-ijln__-tgt-__s-cmd-__s
+{ echo running; false
+} || exit $?
+
+opt-ijln__-tgt-__s-cmd-_i_
+echo running; false
+
+opt-ijln__-tgt-__s-cmd-_is
+echo running; false
+
+opt-ijln__-tgt-__s-cmd-a__
+echo running; false
+running
+*** Error code 1 (continuing)
+
+opt-ijln__-tgt-__s-cmd-a_s
+echo running; false
+running
+*** Error code 1 (continuing)
+
+opt-ijln__-tgt-__s-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-__s-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-_i_-cmd-___
+echo "echo running; false"
+{ echo running; false
+} || exit $?
+
+opt-ijln__-tgt-_i_-cmd-__s
+echo "echo running; false"
+{ echo running; false
+} || exit $?
+
+opt-ijln__-tgt-_i_-cmd-_i_
+echo running; false
+
+opt-ijln__-tgt-_i_-cmd-_is
+echo running; false
+
+opt-ijln__-tgt-_i_-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-_i_-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-_i_-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-_i_-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-_is-cmd-___
+{ echo running; false
+} || exit $?
+
+opt-ijln__-tgt-_is-cmd-__s
+{ echo running; false
+} || exit $?
+
+opt-ijln__-tgt-_is-cmd-_i_
+echo running; false
+
+opt-ijln__-tgt-_is-cmd-_is
+echo running; false
+
+opt-ijln__-tgt-_is-cmd-a__
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-_is-cmd-a_s
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-_is-cmd-ai_
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-_is-cmd-ais
+echo running; false
+running
+*** Error code 1 (ignored)
+
+opt-ijln__-tgt-a__-cmd-___
+echo running; false
+running
+
+opt-ijln__-tgt-a__-cmd-__s
+echo running; false
+running
+
+opt-ijln__-tgt-a__-cmd-_i_
+echo running; false
+running
+
+opt-ijln__-tgt-a__-cmd-_is
+echo running; false
+running
+
+opt-ijln__-tgt-a__-cmd-a__
+echo running; false
+running
+
+opt-ijln__-tgt-a__-cmd-a_s
+echo running; false
+running
+
+opt-ijln__-tgt-a__-cmd-ai_
+echo running; false
+running
+
+opt-ijln__-tgt-a__-cmd-ais
+echo running; false
+running
+
+opt-ijln__-tgt-a_s-cmd-___
+running
+
+opt-ijln__-tgt-a_s-cmd-__s
+running
+
+opt-ijln__-tgt-a_s-cmd-_i_
+echo running; false
+running
+
+opt-ijln__-tgt-a_s-cmd-_is
+echo running; false
+running
+
+opt-ijln__-tgt-a_s-cmd-a__
+running
+
+opt-ijln__-tgt-a_s-cmd-a_s
+running
+
+opt-ijln__-tgt-a_s-cmd-ai_
+echo running; false
+running
+
+opt-ijln__-tgt-a_s-cmd-ais
+echo running; false
+running
+
+opt-ijln__-tgt-ai_-cmd-___
+echo running; false
+running
+
+opt-ijln__-tgt-ai_-cmd-__s
+echo running; false
+running
+
+opt-ijln__-tgt-ai_-cmd-_i_
+echo running; false
+running
+
+opt-ijln__-tgt-ai_-cmd-_is
+echo running; false
+running
+
+opt-ijln__-tgt-ai_-cmd-a__
+echo running; false
+running
+
+opt-ijln__-tgt-ai_-cmd-a_s
+echo running; false
+running
+
+opt-ijln__-tgt-ai_-cmd-ai_
+echo running; false
+running
+
+opt-ijln__-tgt-ai_-cmd-ais
+echo running; false
+running
+
+opt-ijln__-tgt-ais-cmd-___
+running
+
+opt-ijln__-tgt-ais-cmd-__s
+running
+
+opt-ijln__-tgt-ais-cmd-_i_
+echo running; false
+running
+
+opt-ijln__-tgt-ais-cmd-_is
+echo running; false
+running
+
+opt-ijln__-tgt-ais-cmd-a__
+running
+
+opt-ijln__-tgt-ais-cmd-a_s
+running
+
+opt-ijln__-tgt-ais-cmd-ai_
+echo running; false
+running
+
+opt-ijln__-tgt-ais-cmd-ais
+echo running; false
+running
+exit status 0
diff --git a/unit-tests/sh-flags.mk b/unit-tests/sh-flags.mk
new file mode 100644
index 000000000000..a4e17ca88384
--- /dev/null
+++ b/unit-tests/sh-flags.mk
@@ -0,0 +1,138 @@
+# $NetBSD: sh-flags.mk,v 1.4 2020/12/12 12:19:18 rillig Exp $
+#
+# Tests for the effective RunFlags of a shell command (run/skip, echo/silent,
+# error check, trace), which are controlled by 12 different switches. These
+# switches interact in various non-obvious ways. To analyze the interactions,
+# this test runs each possible combination of these switches, for now.
+#
+# As soon as an interaction of switches is identified as obvious and expected,
+# this particular interaction may be removed from the test, to focus on the
+# remaining ones.
+#
+# See also:
+# Compat_RunCommand
+# JobPrintSpecials
+
+all: .PHONY
+
+opt-ignerr.yes= -i
+opt-jobs.yes= -j1
+opt-loud.no= -d0 # side effect: make stdout unbuffered
+opt-loud.yes= -dl # side effect: make stdout unbuffered
+opt-no-action.yes= -n
+opt-silent.yes= -s
+opt-xtrace.yes= -dx
+tgt-always.yes= .MAKE
+tgt-ignerr.yes= .IGNORE
+tgt-silent.yes= .SILENT
+cmd-always.yes= +
+cmd-ignerr.yes= -
+cmd-silent.yes= @
+
+letter.always.yes= a
+letter.ignerr.yes= i
+letter.jobs.yes= j
+letter.loud.yes= l
+letter.no-action.yes= n
+letter.silent.yes= s
+letter.xtrace.yes= x
+
+.if !defined(OPT_TARGET)
+.for opt-ignerr in no yes
+.for opt-jobs in no yes
+.for opt-loud in no yes
+.for opt-no-action in no yes
+# Only 'no', not 'yes', since job->echo is based trivially on opts.silent.
+.for opt-silent in no
+# Only 'no', not 'yes', since that would add uncontrollable output from
+# reading /etc/profile or similar files.
+.for opt-xtrace in no
+
+target= opt-
+target+= ${letter.ignerr.${opt-ignerr}:U_}
+target+= ${letter.jobs.${opt-jobs}:U_}
+target+= ${letter.loud.${opt-loud}:U_}
+target+= ${letter.no-action.${opt-no-action}:U_}
+target+= ${letter.silent.${opt-silent}:U_}
+target+= ${letter.xtrace.${opt-xtrace}:U_}
+
+.for target in ${target:ts}
+
+MAKE_CMD.${target}= ${MAKE}
+MAKE_CMD.${target}+= ${opt-ignerr.${opt-ignerr}}
+MAKE_CMD.${target}+= ${opt-jobs.${opt-jobs}}
+MAKE_CMD.${target}+= ${opt-loud.${opt-loud}}
+MAKE_CMD.${target}+= ${opt-no-action.${opt-no-action}}
+MAKE_CMD.${target}+= ${opt-silent.${opt-silent}}
+MAKE_CMD.${target}+= ${opt-xtrace.${opt-xtrace}}
+MAKE_CMD.${target}+= -f ${MAKEFILE}
+MAKE_CMD.${target}+= OPT_TARGET=${target}
+MAKE_CMD.${target}+= ${target}
+
+all: ${target}
+${target}: .PHONY
+ @${MAKE_CMD.${target}:M*}
+
+.endfor
+.endfor
+.endfor
+.endfor
+.endfor
+.endfor
+.endfor
+.endif
+
+SILENT.yes= @
+ALWAYS.yes= +
+IGNERR.yes= -
+
+.if defined(OPT_TARGET)
+.for tgt-always in no yes
+.for tgt-ignerr in no yes
+.for tgt-silent in no yes
+.for cmd-always in no yes
+.for cmd-ignerr in no yes
+.for cmd-silent in no yes
+
+target= ${OPT_TARGET}-tgt-
+target+= ${letter.always.${tgt-always}:U_}
+target+= ${letter.ignerr.${tgt-ignerr}:U_}
+target+= ${letter.silent.${tgt-silent}:U_}
+target+= -cmd-
+target+= ${letter.always.${cmd-always}:U_}
+target+= ${letter.ignerr.${cmd-ignerr}:U_}
+target+= ${letter.silent.${cmd-silent}:U_}
+
+.for target in ${target:ts}
+
+${OPT_TARGET}: .WAIT ${target} .WAIT
+.if ${tgt-always} == yes
+${target}: .MAKE
+.endif
+.if ${tgt-ignerr} == yes
+${target}: .IGNORE
+.endif
+.if ${tgt-silent} == yes
+${target}: .SILENT
+.endif
+
+RUNFLAGS.${target}= ${SILENT.${cmd-silent}}${ALWAYS.${cmd-always}}${IGNERR.${cmd-ignerr}}
+.if ${OPT_TARGET:M*i*} || ${tgt-ignerr} == yes || ${cmd-ignerr} == yes
+CMD.${target}= echo running; false
+.else
+CMD.${target}= echo running
+.endif
+
+${target}: .PHONY
+ @+echo hide-from-output
+ @+echo hide-from-output ${target}
+ ${RUNFLAGS.${target}} ${CMD.${target}}
+.endfor
+
+.endfor
+.endfor
+.endfor
+.endfor
+.endfor
+.endfor
+.endif
diff --git a/unit-tests/sh-jobs.exp b/unit-tests/sh-jobs.exp
index 39a9383953dd..ef0c574fceed 100644
--- a/unit-tests/sh-jobs.exp
+++ b/unit-tests/sh-jobs.exp
@@ -1 +1,6 @@
-exit status 0
+comment-with-followup-line: This is printed.
+no-comment: This is printed.
+*** [no-comment] Error code 1
+
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/sh-jobs.mk b/unit-tests/sh-jobs.mk
index 62172c2a0c86..e8d4f976109a 100644
--- a/unit-tests/sh-jobs.mk
+++ b/unit-tests/sh-jobs.mk
@@ -1,9 +1,35 @@
-# $NetBSD: sh-jobs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: sh-jobs.mk,v 1.3 2020/12/11 01:06:10 rillig Exp $
#
# Tests for the "run in jobs mode" part of the "Shell Commands" section
# from the manual page.
-# TODO: Implementation
+# TODO: Tutorial
-all:
- @:;
+.MAKEFLAGS: -j1
+
+all: .PHONY comment .WAIT comment-with-followup-line .WAIT no-comment
+
+# If a shell command starts with a comment character after stripping the
+# leading '@', it is run in ignore-errors mode since the default runChkTmpl
+# would lead to a syntax error in the generated shell file, at least for
+# bash and dash, but not for NetBSD sh and ksh.
+#
+# See JobPrintCommand, cmdTemplate, runIgnTmpl
+comment: .PHONY
+ @# comment
+
+# If a shell command starts with a comment character after stripping the
+# leading '@', it is run in ignore-errors mode.
+#
+# See JobPrintCommand, cmdTemplate, runIgnTmpl
+comment-with-followup-line: .PHONY
+ @# comment${.newline}echo '$@: This is printed.'; false
+ @true
+
+# Without the comment, the commands are run in the default mode, which checks
+# the exit status of every makefile line.
+#
+# See JobPrintCommand, cmdTemplate, runChkTmpl
+no-comment: .PHONY
+ @echo '$@: This is printed.'; false
+ @true
diff --git a/unit-tests/sh-meta-chars.mk b/unit-tests/sh-meta-chars.mk
index a029c73a855c..10e29b4b117b 100644
--- a/unit-tests/sh-meta-chars.mk
+++ b/unit-tests/sh-meta-chars.mk
@@ -1,4 +1,4 @@
-# $NetBSD: sh-meta-chars.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: sh-meta-chars.mk,v 1.4 2020/12/07 22:27:56 rillig Exp $
#
# Tests for running shell commands that contain meta-characters.
#
@@ -9,7 +9,16 @@
# See also:
# Compat_RunCommand, useShell
-# TODO: Implementation
-
all:
- @:;
+
+# The command "exit 0" contains no special characters, therefore it is
+# run directly via execv, but only if MAKE_NATIVE is defined.
+USING_EXEC!= { echo 'all:; exit 0' | ${MAKE} -r -f - 1>/dev/null 2>&1; } \
+ && echo yes || echo no
+
+# It's hard to do any useful tests that result in the same output.
+# See SED_CMDS.sh-dots, which normalizes the test output for the specific
+# case of the special command '...'.
+.if ${USING_EXEC} != "yes" && ${USING_EXEC} != "no"
+. error
+.endif
diff --git a/unit-tests/shell-csh.mk b/unit-tests/shell-csh.mk
index 590775dbcb5a..99852e33ce16 100644
--- a/unit-tests/shell-csh.mk
+++ b/unit-tests/shell-csh.mk
@@ -1,8 +1,8 @@
-# $NetBSD: shell-csh.mk,v 1.5 2020/10/19 19:14:11 rillig Exp $
+# $NetBSD: shell-csh.mk,v 1.7 2020/12/13 02:09:55 sjg Exp $
#
# Tests for using a C shell for running the commands.
-CSH!= which csh || true
+CSH!= which csh 2> /dev/null || true
# The shell path must be an absolute path.
# This is only obvious in parallel mode since in compat mode,
@@ -11,7 +11,7 @@ CSH!= which csh || true
.SHELL: name="csh" path="${CSH}"
.endif
-# In parallel mode, the commandShell->noPrint command is filtered from
+# In parallel mode, the shell->noPrint command is filtered from
# the output, rather naively (in JobOutput).
#
# Until 2020-10-03, the output in parallel mode was garbled because
diff --git a/unit-tests/suff-add-later.exp b/unit-tests/suff-add-later.exp
index 0556529457f7..663016a672c1 100644
--- a/unit-tests/suff-add-later.exp
+++ b/unit-tests/suff-add-later.exp
@@ -1,6 +1,9 @@
+Adding suffix ".c"
+Adding suffix ".d"
defining transformation from `.c' to `.d'
inserting ".c" (1) at end of list
inserting ".d" (2) at end of list
+Adding suffix ".e"
defining transformation from `.d' to `.e'
inserting ".d" (2) at end of list
inserting ".e" (3) at end of list
@@ -12,4 +15,7 @@ make: don't know how to make issue5c (continuing)
make: don't know how to make issue5d.e (continuing)
make: don't know how to make issue5e.d (continuing)
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/suff-clear-regular.exp b/unit-tests/suff-clear-regular.exp
index f3d73f6e4a2f..75db9b47a55b 100644
--- a/unit-tests/suff-clear-regular.exp
+++ b/unit-tests/suff-clear-regular.exp
@@ -2,4 +2,7 @@ make: don't know how to make .a (continuing)
make: don't know how to make .a.b (continuing)
make: don't know how to make .b.a (continuing)
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/suff-clear-regular.mk b/unit-tests/suff-clear-regular.mk
index 4f98aa374818..227492bae082 100644
--- a/unit-tests/suff-clear-regular.mk
+++ b/unit-tests/suff-clear-regular.mk
@@ -1,4 +1,4 @@
-# $NetBSD: suff-clear-regular.mk,v 1.1 2020/10/20 20:36:53 rillig Exp $
+# $NetBSD: suff-clear-regular.mk,v 1.2 2020/12/07 00:53:30 rillig Exp $
#
# https://gnats.netbsd.org/49086, issue 4:
# Suffix rules do not become regular rules when .SUFFIXES is cleared.
@@ -27,5 +27,4 @@ all: .a .a.b .b.a
# XXX: don't know how to make .a
# XXX: don't know how to make .a.b
# XXX: don't know how to make .b.a
-# XXX: exit status 0
#.MAKEFLAGS: -dg1
diff --git a/unit-tests/suff-clear-single.exp b/unit-tests/suff-clear-single.exp
index f8abe6348b34..aa46ac75f6da 100644
--- a/unit-tests/suff-clear-single.exp
+++ b/unit-tests/suff-clear-single.exp
@@ -1,3 +1,6 @@
make: don't know how to make issue3 (continuing)
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/suff-incomplete.exp b/unit-tests/suff-incomplete.exp
new file mode 100644
index 000000000000..721f20eb33db
--- /dev/null
+++ b/unit-tests/suff-incomplete.exp
@@ -0,0 +1,42 @@
+ParseReadLine (9): '.SUFFIXES:'
+ParseDoDependency(.SUFFIXES:)
+Clearing all suffixes
+ParseReadLine (11): '.SUFFIXES: .a .b .c'
+ParseDoDependency(.SUFFIXES: .a .b .c)
+Adding suffix ".a"
+Adding suffix ".b"
+Adding suffix ".c"
+ParseReadLine (17): '.a.b:'
+ParseDoDependency(.a.b:)
+defining transformation from `.a' to `.b'
+inserting ".a" (1) at end of list
+inserting ".b" (2) at end of list
+ParseReadLine (21): '.a.c: ${.PREFIX}.dependency'
+deleting incomplete transformation from `.a' to `.b'
+ParseDoDependency(.a.c: ${.PREFIX}.dependency)
+defining transformation from `.a' to `.c'
+inserting ".a" (1) at end of list
+inserting ".c" (3) at end of list
+# LinkSource: added child .a.c - ${.PREFIX}.dependency
+# .a.c, made UNMADE, type OP_DEPENDS|OP_TRANSFORM, flags none
+# ${.PREFIX}.dependency, made UNMADE, type none, flags none
+ParseReadLine (23): '.DEFAULT:'
+transformation .a.c complete
+ParseDoDependency(.DEFAULT:)
+ParseReadLine (24): ' : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default.'
+transformation .DEFAULT complete
+Wildcard expanding "all"...
+SuffFindDeps "all"
+ No known suffix on all. Using .NULL suffix
+adding suffix rules
+Wildcard expanding "suff-incomplete.c"...suffix is ".c"...
+SuffFindDeps "suff-incomplete.c"
+ trying suff-incomplete.a...not there
+Wildcard expanding "suff-incomplete.c"...suffix is ".c"...
+: Making suff-incomplete.c from suff-incomplete.c all by default.
+Wildcard expanding "all"...
+SuffFindDeps ".END"
+ No known suffix on .END. Using .NULL suffix
+adding suffix rules
+Wildcard expanding ".END"...
+exit status 0
diff --git a/unit-tests/suff-incomplete.mk b/unit-tests/suff-incomplete.mk
new file mode 100644
index 000000000000..474c7aaab664
--- /dev/null
+++ b/unit-tests/suff-incomplete.mk
@@ -0,0 +1,31 @@
+# $NetBSD: suff-incomplete.mk,v 1.2 2020/11/22 11:05:49 rillig Exp $
+#
+# Tests incomplete transformation rules, which are ignored.
+
+all: suff-incomplete.c
+
+.MAKEFLAGS: -dps
+
+.SUFFIXES:
+
+.SUFFIXES: .a .b .c
+
+# This rule has no commands and no dependencies, therefore it is incomplete
+# and not added to the transformation rules.
+#
+# See Suff_EndTransform.
+.a.b:
+
+# This rule has a dependency, therefore it is a complete transformation.
+# Its commands are taken from a .DEFAULT target, if there is any.
+.a.c: ${.PREFIX}.dependency
+
+.DEFAULT:
+ : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default.
+
+# The debug log says "transformation .DEFAULT complete", which seems wrong
+# on the first sight. It is intentionally done though, in the call to
+# GNode_New(".DEFAULT").
+
+# XXX: The output of this test says "Making suff-incomplete.c from
+# suff-incomplete.c". It doesn't make sense to make something out of itself.
diff --git a/unit-tests/suff-lookup.exp b/unit-tests/suff-lookup.exp
index 6714e7975e18..a8f893fa9492 100644
--- a/unit-tests/suff-lookup.exp
+++ b/unit-tests/suff-lookup.exp
@@ -1,3 +1,9 @@
+Adding suffix ".c"
+Adding suffix ".cc"
+Adding suffix ".ccc"
+Adding suffix ".short"
+Adding suffix ".sho"
+Adding suffix ".dead-end"
defining transformation from `.ccc' to `.cc'
inserting ".ccc" (3) at end of list
inserting ".cc" (2) at end of list
@@ -18,22 +24,29 @@ defining transformation from `.dead-end' to `.short'
inserting ".dead-end" (6) at end of list
inserting ".short" (4) at end of list
transformation .dead-end.short complete
+Clearing all suffixes
+Adding suffix ".c"
+Adding suffix ".cc"
+Adding suffix ".ccc"
inserting ".ccc" (3) at end of list
inserting ".cc" (2) at end of list
inserting ".c" (1) at end of list
inserting ".ccc" (3) at end of list
+Adding suffix ".short"
inserting ".short" (4) at end of list
inserting ".c" (1) at end of list
+Adding suffix ".sho"
inserting ".sho" (5) at end of list
inserting ".c" (1) at end of list
+Adding suffix ".dead-end"
inserting ".dead-end" (6) at end of list
inserting ".short" (4) at end of list
Wildcard expanding "all"...
-SuffFindDeps (all)
+SuffFindDeps "all"
No known suffix on all. Using .NULL suffix
adding suffix rules
Wildcard expanding "suff-lookup.cc"...suffix is ".cc"...
-SuffFindDeps (suff-lookup.cc)
+SuffFindDeps "suff-lookup.cc"
trying suff-lookup.ccc...not there
trying suff-lookup.c...not there
trying suff-lookup.short...not there
@@ -44,14 +57,14 @@ SuffFindDeps (suff-lookup.cc)
suffix is ".ccc"...
suffix is ".c"...
suffix is ".sho"...
-SuffFindDeps (suff-lookup.sho)
+SuffFindDeps "suff-lookup.sho"
suffix is ".sho"...
: 'Making suff-lookup.sho out of nothing.'
: 'Making suff-lookup.c from suff-lookup.sho.'
: 'Making suff-lookup.ccc from suff-lookup.c.'
: 'Making suff-lookup.cc from suff-lookup.ccc.'
Wildcard expanding "all"...
-SuffFindDeps (.END)
+SuffFindDeps ".END"
No known suffix on .END. Using .NULL suffix
adding suffix rules
Wildcard expanding ".END"...
diff --git a/unit-tests/suff-main-several.exp b/unit-tests/suff-main-several.exp
new file mode 100644
index 000000000000..c938820c1eaf
--- /dev/null
+++ b/unit-tests/suff-main-several.exp
@@ -0,0 +1,141 @@
+ParseReadLine (8): '.1.2 .1.3 .1.4:'
+ParseDoDependency(.1.2 .1.3 .1.4:)
+Setting main node to ".1.2"
+ParseReadLine (9): ' : Making ${.TARGET} from ${.IMPSRC}.'
+ParseReadLine (14): 'next-main:'
+ParseDoDependency(next-main:)
+ParseReadLine (15): ' : Making ${.TARGET}'
+ParseReadLine (19): '.SUFFIXES: .1 .2 .3 .4'
+ParseDoDependency(.SUFFIXES: .1 .2 .3 .4)
+Adding suffix ".1"
+Adding suffix ".2"
+Setting main node from ".1.2" back to null
+defining transformation from `.1' to `.2'
+inserting ".1" (1) at end of list
+inserting ".2" (2) at end of list
+Setting main node to ".1.3"
+Adding suffix ".3"
+Setting main node from ".1.3" back to null
+defining transformation from `.1' to `.3'
+inserting ".1" (1) at end of list
+inserting ".3" (3) at end of list
+Setting main node to ".1.4"
+Adding suffix ".4"
+Setting main node from ".1.4" back to null
+defining transformation from `.1' to `.4'
+inserting ".1" (1) at end of list
+inserting ".4" (4) at end of list
+Setting main node to "next-main"
+ParseReadLine (24): '.SUFFIXES:'
+ParseDoDependency(.SUFFIXES:)
+Clearing all suffixes
+ParseReadLine (32): '.SUFFIXES: .4 .3 .2 .1'
+ParseDoDependency(.SUFFIXES: .4 .3 .2 .1)
+Adding suffix ".4"
+Adding suffix ".3"
+Adding suffix ".2"
+Adding suffix ".1"
+ParseReadLine (33): '.SUFFIXES:'
+ParseDoDependency(.SUFFIXES:)
+Clearing all suffixes
+ParseReadLine (34): '.SUFFIXES: .1 .2 .3 .4'
+ParseDoDependency(.SUFFIXES: .1 .2 .3 .4)
+Adding suffix ".1"
+Adding suffix ".2"
+Adding suffix ".3"
+Adding suffix ".4"
+ParseReadLine (35): '.SUFFIXES:'
+ParseDoDependency(.SUFFIXES:)
+Clearing all suffixes
+ParseReadLine (36): '.SUFFIXES: .4 .3 .2 .1'
+ParseDoDependency(.SUFFIXES: .4 .3 .2 .1)
+Adding suffix ".4"
+Adding suffix ".3"
+Adding suffix ".2"
+Adding suffix ".1"
+ParseReadLine (38): 'suff-main-several.1:'
+ParseDoDependency(suff-main-several.1:)
+ParseReadLine (39): ' : Making ${.TARGET} out of nothing.'
+ParseReadLine (40): 'next-main: suff-main-several.{2,3,4}'
+ParseDoDependency(next-main: suff-main-several.{2,3,4})
+# LinkSource: added child next-main - suff-main-several.{2,3,4}
+# next-main, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none
+# suff-main-several.{2,3,4}, made UNMADE, type none, flags none
+ParseReadLine (42): '.MAKEFLAGS: -d0 -dg1'
+ParseDoDependency(.MAKEFLAGS: -d0 -dg1)
+#*** Input graph:
+# .1.2, made UNMADE, type OP_TRANSFORM, flags none
+# .1.3, made UNMADE, type OP_TRANSFORM, flags none
+# .1.4, made UNMADE, type OP_TRANSFORM, flags none
+# next-main, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none
+# suff-main-several.1, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none
+# suff-main-several.{2,3,4}, made UNMADE, type none, flags none
+
+
+#
+# Files that are only sources:
+# .1.2 [.1.2]
+# .1.3 [.1.3]
+# .1.4 [.1.4]
+# suff-main-several.{2,3,4} [suff-main-several.{2,3,4}]
+#*** Global Variables:
+.ALLTARGETS = .1.2 .1.3 .1.4 next-main suff-main-several.1 suff-main-several.{2,3,4}
+.CURDIR = <curdir>
+.INCLUDES =
+.LIBS =
+.MAKE = <details omitted>
+.MAKE.DEPENDFILE = <details omitted>
+.MAKE.GID = <details omitted>
+.MAKE.LEVEL = <details omitted>
+.MAKE.MAKEFILES = <details omitted>
+.MAKE.MAKEFILE_PREFERENCE = <details omitted>
+.MAKE.OS = <details omitted>
+.MAKE.PID = <details omitted>
+.MAKE.PPID = <details omitted>
+.MAKE.UID = <details omitted>
+.MAKEFLAGS = -r -k -d mps -d 0 -d g1
+.MAKEOVERRIDES =
+.OBJDIR = <curdir>
+.PATH = . <curdir>
+.TARGETS =
+.newline =
+
+MACHINE = <details omitted>
+MACHINE_ARCH = <details omitted>
+MAKE = <details omitted>
+MFLAGS = -r -k -d mps -d 0 -d g1
+#*** Command-line Variables:
+.MAKE.LEVEL.ENV = MAKELEVEL
+
+#*** Directory Cache:
+# Stats: 0 hits 2 misses 0 near misses 0 losers (0%)
+# refs hits directory
+# 1 0 <curdir>
+# 1 0 .
+
+#*** Suffixes:
+# ".4" (num 1, ref 1)
+# To:
+# From:
+# Search Path:
+# ".3" (num 2, ref 1)
+# To:
+# From:
+# Search Path:
+# ".2" (num 3, ref 1)
+# To:
+# From:
+# Search Path:
+# ".1" (num 4, ref 1)
+# To:
+# From:
+# Search Path:
+#*** Transformations:
+make: don't know how to make suff-main-several.2 (continuing)
+make: don't know how to make suff-main-several.3 (continuing)
+make: don't know how to make suff-main-several.4 (continuing)
+`next-main' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/suff-main-several.mk b/unit-tests/suff-main-several.mk
new file mode 100644
index 000000000000..bcc21db5e128
--- /dev/null
+++ b/unit-tests/suff-main-several.mk
@@ -0,0 +1,42 @@
+# $NetBSD: suff-main-several.mk,v 1.1 2020/11/22 20:36:17 rillig Exp $
+#
+# Demonstrate that an inference rule is considered the main target if its
+# suffixes are not known at the point of declaration.
+
+.MAKEFLAGS: -dmps
+
+.1.2 .1.3 .1.4:
+ : Making ${.TARGET} from ${.IMPSRC}.
+
+# At this point, the above targets are normal targets.
+# The target '.1.2' is now the default main target.
+
+next-main:
+ : Making ${.TARGET}
+
+# At this point, 'next-main' is just a regular target.
+
+.SUFFIXES: .1 .2 .3 .4
+
+# Since the targets '.1.2', '.1.3' and '.1.4' have now been turned into
+# transformation rules, 'next-main' is the default main target now.
+
+.SUFFIXES: # clear all
+
+# At this point, 'next-main' is still the default main target, even though
+# it is not the first regular target anymore.
+
+# Define and undefine the suffixes, changing their order.
+# XXX: This should have no effect, but as of 2020-11-22, it does.
+# For some reason, mentioning the suffixes in reverse order disables them.
+.SUFFIXES: .4 .3 .2 .1
+.SUFFIXES: # none
+.SUFFIXES: .1 .2 .3 .4
+.SUFFIXES: # none
+.SUFFIXES: .4 .3 .2 .1
+
+suff-main-several.1:
+ : Making ${.TARGET} out of nothing.
+next-main: suff-main-several.{2,3,4}
+
+.MAKEFLAGS: -d0 -dg1
diff --git a/unit-tests/suff-phony.exp b/unit-tests/suff-phony.exp
new file mode 100644
index 000000000000..13f57f8ec2c6
--- /dev/null
+++ b/unit-tests/suff-phony.exp
@@ -0,0 +1,13 @@
+Adding suffix ".c"
+defining transformation from `.c' to `'
+inserting ".c" (1) at end of list
+inserting "" (0) at end of list
+transformation .c complete
+SuffFindDeps "all"
+ No valid suffix on all
+SuffFindDeps ".END"
+ No known suffix on .END. Using .NULL suffix
+adding suffix rules
+ trying .END.c...not there
+Wildcard expanding ".END"...
+exit status 0
diff --git a/unit-tests/suff-phony.mk b/unit-tests/suff-phony.mk
new file mode 100644
index 000000000000..f206187c7e85
--- /dev/null
+++ b/unit-tests/suff-phony.mk
@@ -0,0 +1,21 @@
+# $NetBSD: suff-phony.mk,v 1.1 2020/11/23 15:00:32 rillig Exp $
+#
+# Test that .PHONY targets are not resolved using suffix rules.
+#
+# The purpose of the .PHONY attribute is to mark them as independent from the
+# file system.
+#
+# See also:
+# FindDepsRegular, Ctrl+F OP_PHONY
+
+.MAKEFLAGS: -ds
+
+all: .PHONY
+
+.SUFFIXES: .c
+
+.c:
+ : Making ${.TARGET} from ${.IMPSRC}.
+
+all.c:
+ : Making ${.TARGET} out of nothing.
diff --git a/unit-tests/suff-rebuild.exp b/unit-tests/suff-rebuild.exp
index b5bb60c23477..ccb423a6086a 100644
--- a/unit-tests/suff-rebuild.exp
+++ b/unit-tests/suff-rebuild.exp
@@ -1,5 +1,73 @@
-: from nothing to a
-: from a to b
-: from b to c
-: from c to nothing
+ParseReadLine (10): '.SUFFIXES:'
+ParseDoDependency(.SUFFIXES:)
+Clearing all suffixes
+ParseReadLine (12): '.SUFFIXES: .a .b .c'
+ParseDoDependency(.SUFFIXES: .a .b .c)
+Adding suffix ".a"
+Adding suffix ".b"
+Adding suffix ".c"
+ParseReadLine (14): 'suff-rebuild-example.a:'
+ParseDoDependency(suff-rebuild-example.a:)
+Adding "suff-rebuild-example.a" to all targets.
+ParseReadLine (15): ' : Making ${.TARGET} out of nothing.'
+ParseReadLine (17): '.a.b:'
+ParseDoDependency(.a.b:)
+defining transformation from `.a' to `.b'
+inserting ".a" (1) at end of list
+inserting ".b" (2) at end of list
+ParseReadLine (18): ' : Making ${.TARGET} from ${.IMPSRC}.'
+ParseReadLine (19): '.b.c:'
+transformation .a.b complete
+ParseDoDependency(.b.c:)
+defining transformation from `.b' to `.c'
+inserting ".b" (2) at end of list
+inserting ".c" (3) at end of list
+ParseReadLine (20): ' : Making ${.TARGET} from ${.IMPSRC}.'
+ParseReadLine (21): '.c:'
+transformation .b.c complete
+ParseDoDependency(.c:)
+defining transformation from `.c' to `'
+inserting ".c" (3) at end of list
+inserting "" (0) at end of list
+ParseReadLine (22): ' : Making ${.TARGET} from ${.IMPSRC}.'
+ParseReadLine (44): '.SUFFIXES: .c .b .a'
+transformation .c complete
+ParseDoDependency(.SUFFIXES: .c .b .a)
+Adding ".END" to all targets.
+Wildcard expanding "all"...
+SuffFindDeps "all"
+ No known suffix on all. Using .NULL suffix
+adding suffix rules
+ trying all.c...not there
+ trying all.b...not there
+ trying all.a...not there
+Wildcard expanding "suff-rebuild-example"...
+SuffFindDeps "suff-rebuild-example"
+ No known suffix on suff-rebuild-example. Using .NULL suffix
+adding suffix rules
+ trying suff-rebuild-example.c...not there
+ trying suff-rebuild-example.b...not there
+ trying suff-rebuild-example.a...got it
+Adding "suff-rebuild-example.b" to all targets.
+ applying .a -> .b to "suff-rebuild-example.b"
+Adding "suff-rebuild-example.c" to all targets.
+ applying .b -> .c to "suff-rebuild-example.c"
+ applying .c -> to "suff-rebuild-example"
+suffix is ".c"...
+suffix is ".b"...
+suffix is ".a"...
+SuffFindDeps "suff-rebuild-example.a"
+suffix is ".a"...
+: Making suff-rebuild-example.a out of nothing.
+: Making suff-rebuild-example.b from suff-rebuild-example.a.
+: Making suff-rebuild-example.c from suff-rebuild-example.b.
+: Making suff-rebuild-example from suff-rebuild-example.c.
+Wildcard expanding "all"...
+SuffFindDeps ".END"
+ No known suffix on .END. Using .NULL suffix
+adding suffix rules
+ trying .END.c...not there
+ trying .END.b...not there
+ trying .END.a...not there
+Wildcard expanding ".END"...
exit status 0
diff --git a/unit-tests/suff-rebuild.mk b/unit-tests/suff-rebuild.mk
index d190eaf4c6e8..a1ce89402a01 100644
--- a/unit-tests/suff-rebuild.mk
+++ b/unit-tests/suff-rebuild.mk
@@ -1,31 +1,42 @@
-# $NetBSD: suff-rebuild.mk,v 1.2 2020/10/18 16:12:39 rillig Exp $
+# $NetBSD: suff-rebuild.mk,v 1.6 2020/11/21 12:01:16 rillig Exp $
#
# Demonstrates what happens to transformation rules (called inference rules
# by POSIX) when all suffixes are deleted.
all: suff-rebuild-example
+.MAKEFLAGS: -dpst
+
.SUFFIXES:
.SUFFIXES: .a .b .c
suff-rebuild-example.a:
- : from nothing to a
+ : Making ${.TARGET} out of nothing.
.a.b:
- : from a to b
+ : Making ${.TARGET} from ${.IMPSRC}.
.b.c:
- : from b to c
+ : Making ${.TARGET} from ${.IMPSRC}.
.c:
- : from c to nothing
+ : Making ${.TARGET} from ${.IMPSRC}.
-# XXX: At a quick glance, the code in SuffScanTargets looks as if it were
+# XXX: At a quick glance, the code in SuffUpdateTarget looks as if it were
# possible to delete the suffixes in the middle of the makefile, add back
# the suffixes from before, and have the transformation rules preserved.
#
# As of 2020-09-25, uncommenting the following line results in the error
# message "don't know how to make suff-rebuild-example" though.
#
+# If this is a bug, the actual cause is probably that when a suffix
+# transformation rule is defined, it is not added to the global list of
+# targets, see Suff_EndTransform. Later, UpdateTargets iterates over exactly
+# this global list of targets though.
+#
+# If UpdateTargets were to iterate over 'transforms' as well, it still
+# wouldn't work because the condition 'ptr == target->name' skips these
+# transformation rules.
+
#.SUFFIXES:
# Add the suffixes back. It should not matter that the order of the suffixes
diff --git a/unit-tests/suff-self.exp b/unit-tests/suff-self.exp
index 4e70762209a2..6192c508ab96 100644
--- a/unit-tests/suff-self.exp
+++ b/unit-tests/suff-self.exp
@@ -1,3 +1,6 @@
make: Graph cycles through suff-self.suff
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/suff-transform-debug.exp b/unit-tests/suff-transform-debug.exp
new file mode 100644
index 000000000000..70181be4b97f
--- /dev/null
+++ b/unit-tests/suff-transform-debug.exp
@@ -0,0 +1,62 @@
+#*** Input graph:
+# all, made UNMADE, type OP_DEPENDS, flags none
+
+
+#
+# Files that are only sources:
+#*** Global Variables:
+.ALLTARGETS = all
+.CURDIR = <curdir>
+.INCLUDES =
+.LIBS =
+.MAKE = <details omitted>
+.MAKE.DEPENDFILE = <details omitted>
+.MAKE.GID = <details omitted>
+.MAKE.LEVEL = <details omitted>
+.MAKE.MAKEFILES = <details omitted>
+.MAKE.MAKEFILE_PREFERENCE = <details omitted>
+.MAKE.OS = <details omitted>
+.MAKE.PID = <details omitted>
+.MAKE.PPID = <details omitted>
+.MAKE.UID = <details omitted>
+.MAKEFLAGS = -r -k -d g1
+.MAKEOVERRIDES =
+.OBJDIR = <curdir>
+.PATH = . <curdir>
+.TARGETS =
+.newline =
+
+MACHINE = <details omitted>
+MACHINE_ARCH = <details omitted>
+MAKE = <details omitted>
+MFLAGS = -r -k -d g1
+#*** Command-line Variables:
+.MAKE.LEVEL.ENV = MAKELEVEL
+
+#*** Directory Cache:
+# Stats: 0 hits 2 misses 0 near misses 0 losers (0%)
+# refs hits directory
+# 1 0 <curdir>
+# 1 0 .
+
+#*** Suffixes:
+# ".a" (num 1, ref 2)
+# To:
+# From: .cpp
+# Search Path:
+# ".c" (num 2, ref 2)
+# To: .cpp
+# From:
+# Search Path:
+# ".cpp" (num 3, ref 3)
+# To: .a
+# From: .c
+# Search Path:
+#*** Transformations:
+.c.cpp :
+ : Making ${.TARGET} from impsrc ${.IMPSRC} allsrc ${.ALLSRC}.
+
+.cpp.a :
+ : Making ${.TARGET} from impsrc ${.IMPSRC} allsrc ${.ALLSRC}.
+
+exit status 0
diff --git a/unit-tests/suff-transform-debug.mk b/unit-tests/suff-transform-debug.mk
new file mode 100644
index 000000000000..d206a0f691a2
--- /dev/null
+++ b/unit-tests/suff-transform-debug.mk
@@ -0,0 +1,12 @@
+# $NetBSD: suff-transform-debug.mk,v 1.1 2020/11/22 23:45:20 rillig Exp $
+#
+# Test how the debug output of transformation rules looks.
+
+.MAKEFLAGS: -dg1
+
+.SUFFIXES: .a .c .cpp
+
+.c.cpp .cpp.a:
+ : Making ${.TARGET} from impsrc ${.IMPSRC} allsrc ${.ALLSRC}.
+
+all:
diff --git a/unit-tests/suff-transform-endless.exp b/unit-tests/suff-transform-endless.exp
index 5cd267306b92..620c46626e22 100644
--- a/unit-tests/suff-transform-endless.exp
+++ b/unit-tests/suff-transform-endless.exp
@@ -1,4 +1,46 @@
-make: "suff-transform-endless.mk" line 36: prevent endless loop
+Adding suffix ".c"
+Adding suffix ".d"
+defining transformation from `.c' to `.d'
+inserting ".c" (1) at end of list
+inserting ".d" (2) at end of list
+Adding suffix ".e"
+defining transformation from `.d' to `.e'
+inserting ".d" (2) at end of list
+inserting ".e" (3) at end of list
+Adding suffix ".f"
+defining transformation from `.e' to `'
+inserting ".e" (3) at end of list
+inserting "" (0) at end of list
+defining transformation from `.e' to `.f'
+inserting ".e" (3) at end of list
+inserting ".f" (4) at end of list
+defining transformation from `.f' to `.e'
+inserting ".f" (4) at end of list
+inserting ".e" (3) at end of list
+transformation .e complete
+transformation .e.f complete
+transformation .f.e complete
+Wildcard expanding "all"...
+SuffFindDeps "all"
+ No known suffix on all. Using .NULL suffix
+adding suffix rules
+ trying all.e...not there
+ trying all.d...not there
+ trying all.f...not there
+ trying all.c...not there
+ trying all.e...not there
+FindThem: skipping duplicate "all.e"
+Wildcard expanding "issue6.f"...suffix is ".f"...
+SuffFindDeps "issue6.f"
+ trying issue6.e...not there
+ trying issue6.d...not there
+ trying issue6.f...got it
+ applying .f -> .e to "issue6.e"
+ applying .e -> .f to "issue6.f"
+suffix is ".e"...
+make: Graph cycles through issue6.f
+`all' not remade because of errors.
+Stop.
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/suff-transform-endless.mk b/unit-tests/suff-transform-endless.mk
index 546e24f398ad..d3b1cf24d8aa 100644
--- a/unit-tests/suff-transform-endless.mk
+++ b/unit-tests/suff-transform-endless.mk
@@ -1,4 +1,4 @@
-# $NetBSD: suff-transform-endless.mk,v 1.1 2020/10/20 20:36:53 rillig Exp $
+# $NetBSD: suff-transform-endless.mk,v 1.4 2020/11/23 14:47:12 rillig Exp $
# https://gnats.netbsd.org/49086, issue 6:
# Transformation search can end up in an infinite loop.
@@ -8,6 +8,8 @@
# exist, make would try to make .f from .e and then infinitely try
# to do .e from .d and vice versa.
+.MAKEFLAGS: -ds
+
all: issue6.f
.c.d .d.c .d .d.e .e.d:
@@ -16,7 +18,7 @@ all: issue6.f
.SUFFIXES: .c .d .e .f
.e .e.f .f.e:
- : 'Making ${.TARGET} out of nothing.'
+ : 'Making ${.TARGET} from ${.IMPSRC}.'
# XXX: As of 2020-10-20, the result is unexpected.
# XXX: .d.c is not a transformation rule.
@@ -33,4 +35,5 @@ all: issue6.f
# XXX: Found 'all' as '(not found)'
# XXX: trying all.e, all.e, all.f, all.e, all.e, repeatedly.
#.MAKEFLAGS: -dg1
-.error prevent endless loop
+
+# Before 24-11-2020, resolving all.e ran into an endless loop.
diff --git a/unit-tests/suff-transform-expand.exp b/unit-tests/suff-transform-expand.exp
index 178e264769af..c1821852707d 100644
--- a/unit-tests/suff-transform-expand.exp
+++ b/unit-tests/suff-transform-expand.exp
@@ -2,4 +2,7 @@
make: don't know how to make .first (continuing)
: 'Making issue11.second out of nothing.'
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/suff-transform-select.exp b/unit-tests/suff-transform-select.exp
index 8470ba3853dc..29065154c891 100644
--- a/unit-tests/suff-transform-select.exp
+++ b/unit-tests/suff-transform-select.exp
@@ -1,4 +1,47 @@
-make: "suff-transform-select.mk" line 28: prevent endless loop
+Adding suffix ".c"
+Adding suffix ".d"
+defining transformation from `.c' to `.d'
+inserting ".c" (1) at end of list
+inserting ".d" (2) at end of list
+Adding suffix ".e"
+defining transformation from `.d' to `.e'
+inserting ".d" (2) at end of list
+inserting ".e" (3) at end of list
+Adding suffix ".f"
+Adding suffix ".g"
+defining transformation from `.e' to `'
+inserting ".e" (3) at end of list
+inserting "" (0) at end of list
+defining transformation from `.e' to `.f'
+inserting ".e" (3) at end of list
+inserting ".f" (4) at end of list
+defining transformation from `.f' to `.e'
+inserting ".f" (4) at end of list
+inserting ".e" (3) at end of list
+transformation .e complete
+transformation .e.f complete
+transformation .f.e complete
+Wildcard expanding "all"...
+SuffFindDeps "all"
+ No known suffix on all. Using .NULL suffix
+adding suffix rules
+ trying all.e...not there
+ trying all.d...not there
+ trying all.f...not there
+ trying all.c...not there
+ trying all.e...not there
+FindThem: skipping duplicate "all.e"
+Wildcard expanding "issue10.e"...suffix is ".e"...
+SuffFindDeps "issue10.e"
+ trying issue10.d...got it
+suffix is ".d"...
+SuffFindDeps "issue10.d"
+ trying issue10.c...not there
+suffix is ".d"...
+: 'Making issue10.d out of nothing.'
+make: don't know how to make issue10.e (continuing)
+`all' not remade because of errors.
+Stop.
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/suff-transform-select.mk b/unit-tests/suff-transform-select.mk
index d4ae37086e84..a5af089d7714 100644
--- a/unit-tests/suff-transform-select.mk
+++ b/unit-tests/suff-transform-select.mk
@@ -1,4 +1,4 @@
-# $NetBSD: suff-transform-select.mk,v 1.1 2020/10/20 20:36:53 rillig Exp $
+# $NetBSD: suff-transform-select.mk,v 1.3 2020/11/23 14:47:12 rillig Exp $
#
# https://gnats.netbsd.org/49086, issue 10:
# Explicit dependencies affect transformation rule selection.
@@ -8,6 +8,8 @@
# The bug was that if issue10.d had an explicit dependency on issue10.f,
# it would choose .f.e instead.
+.MAKEFLAGS: -ds
+
_!= rm -f issue10.*
all: issue10.e
@@ -25,4 +27,5 @@ issue10.d issue10.f:
# XXX: see suff-bug-endless, which must be fixed first.
#.MAKEFLAGS: -dg1
-.error prevent endless loop
+
+# Before 24-11-2020, resolving all.e ran into an endless loop.
diff --git a/unit-tests/use-inference.exp b/unit-tests/use-inference.exp
index 14ecf0550574..135deabc918e 100644
--- a/unit-tests/use-inference.exp
+++ b/unit-tests/use-inference.exp
@@ -1,4 +1,7 @@
Building use-inference.from from nothing
make: don't know how to make use-inference.to (continuing)
`all' not remade because of errors.
-exit status 0
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/use-inference.mk b/unit-tests/use-inference.mk
index cde3c772edaa..550331a54d97 100644
--- a/unit-tests/use-inference.mk
+++ b/unit-tests/use-inference.mk
@@ -1,4 +1,4 @@
-# $NetBSD: use-inference.mk,v 1.2 2020/11/05 00:41:04 rillig Exp $
+# $NetBSD: use-inference.mk,v 1.3 2020/12/07 00:53:30 rillig Exp $
#
# Demonstrate that .USE rules do not have an effect on inference rules.
# At least not in the special case where the inference rule does not
@@ -34,5 +34,5 @@ use-inference.from: # assume it exists
# inference rule. But it seems to ignore it, maybe because it doesn't
# have any associated commands.
-# XXX: Despite the error message "don't know how to make", the exit status
-# is 0. This is inconsistent.
+# Until 2020-12-07, despite the error message "don't know how to make",
+# the exit status was 0. This was inconsistent.
diff --git a/unit-tests/var-op-default.mk b/unit-tests/var-op-default.mk
index afb0c55f827c..ca4fbcc27c88 100644
--- a/unit-tests/var-op-default.mk
+++ b/unit-tests/var-op-default.mk
@@ -1,9 +1,77 @@
-# $NetBSD: var-op-default.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: var-op-default.mk,v 1.3 2020/12/07 21:35:43 rillig Exp $
#
# Tests for the ?= variable assignment operator, which only assigns
# if the variable is still undefined.
-# TODO: Implementation
+# The variable VAR is not defined yet. Therefore it gets the default value
+# from the variable assignment.
+VAR?= default value
+.if ${VAR} != "default value"
+. error
+.endif
+
+# At this point, the variable 'VAR' is already defined. The '?=' therefore
+# ignores the new variable value, preserving the previous "default value".
+VAR?= ignored
+.if ${VAR} != "default value"
+. error
+.endif
+
+# The '?=' operator only checks whether the variable is defined or not.
+# An empty variable is defined, therefore the '?=' operator does nothing.
+EMPTY= # empty
+EMPTY?= ignored
+.if ${EMPTY} != ""
+. error
+.endif
+
+# The .for loop is described in the manual page as if it would operate on
+# variables. This is not entirely true. Instead, each occurrence of an
+# expression $i or ${i} or ${i:...} is substituted with ${:Uloop-value}.
+# This comes very close to the description, the only difference is that
+# there is never an actual variable named 'i' involved.
+#
+# Because there is not really a variable named 'i', the '?=' operator
+# performs the variable assignment, resulting in $i == "default".
+.for i in loop-value
+i?= default
+.endfor
+.if ${i} != "default"
+. error
+.endif
+
+# At the point where the '?=' operator checks whether the variable exists,
+# it expands the variable name exactly once. Therefore both 'VAR.param'
+# and 'VAR.${param}' expand to 'VAR.param', and the second '?=' assignment
+# has no effect.
+#
+# Since 2000.05.11.07.43.42 it has been possible to use nested variable
+# expressions in variable names, which made make much more versatile.
+# On 2008.03.31.00.12.21, this particular case of the '?=' operator has been
+# fixed. Before, the '?=' operator had not expanded the variable name
+# 'VAR.${:Uparam}' to see whether the variable already existed. Since that
+# variable didn't exist (and variables with '$' in their name are particularly
+# fragile), the variable assignment with "not used" was performed, and only
+# during that, the variable name was expanded.
+VAR.param= already defined
+VAR.${:Uparam}?= not used
+.if ${VAR.param} != "already defined"
+. error
+.endif
+
+# Now demonstrate that the variable name is indeed expanded exactly once.
+# This is tricky to measure correctly since there are many inconsistencies
+# in and around the code that expands variable expressions in the various
+# places where variable expressions can occur. If in doubt, enable the
+# following debug flags to see what happens:
+#.MAKEFLAGS: -dcpv
+EXPAND_NAME= EXPAND.$$$$ # The full variable name is EXPAND.$$
+PARAM= $$$$
+EXPAND.${PARAM}?= value with param
+.if ${${EXPAND_NAME}} != "value with param"
+. error
+.endif
+.MAKEFLAGS: -d0
all:
@:;
diff --git a/unit-tests/var-op-expand.exp b/unit-tests/var-op-expand.exp
index 8ccbbd5ae92d..39a9383953dd 100644
--- a/unit-tests/var-op-expand.exp
+++ b/unit-tests/var-op-expand.exp
@@ -1,10 +1 @@
-Var_Parse: ${UNDEF} with VARE_WANTRES
-Global:VAR_ASSIGN_ = undef value
-Var_Parse: ${UNDEF} with VARE_WANTRES
-Var_Parse: ${UNDEF} with VARE_WANTRES
-Global:VAR_SUBST_${UNDEF} =
-Var_Parse: ${UNDEF} with VARE_WANTRES
-Global:VAR_SUBST_ = undef value
-Global:.MAKEFLAGS = -r -k -d v -d
-Global:.MAKEFLAGS = -r -k -d v -d 0
exit status 0
diff --git a/unit-tests/var-op-expand.mk b/unit-tests/var-op-expand.mk
index 0b5ddbbc0386..ff62668a8ada 100644
--- a/unit-tests/var-op-expand.mk
+++ b/unit-tests/var-op-expand.mk
@@ -1,27 +1,178 @@
-# $NetBSD: var-op-expand.mk,v 1.4 2020/11/08 14:00:52 rillig Exp $
+# $NetBSD: var-op-expand.mk,v 1.11 2021/01/01 23:07:48 sjg Exp $
#
# Tests for the := variable assignment operator, which expands its
# right-hand side.
-# TODO: Implementation
-
-# XXX: edge case: When a variable name refers to an undefined variable, the
-# behavior differs between the '=' and the ':=' assignment operators.
-# This bug exists since var.c 1.42 from 2000-05-11.
-#
-# The '=' operator expands the undefined variable to an empty string, thus
-# assigning to VAR_ASSIGN_. In the name of variables to be set, it should
-# really be forbidden to refer to undefined variables.
-#
-# The ':=' operator expands the variable name twice. In one of these
-# expansions, the undefined variable expression is preserved (controlled by
-# preserveUndefined in VarAssign_EvalSubst), in the other expansion it expands
-# to an empty string. This way, 2 variables are created using a single
-# variable assignment. It's magic. :-/
-.MAKEFLAGS: -dv
-VAR_ASSIGN_${UNDEF}= undef value
-VAR_SUBST_${UNDEF}:= undef value
-.MAKEFLAGS: -d0
+.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.
+VAR:= value
+.if ${VAR} != "value"
+. error
+.endif
+
+# When a ':=' assignment is performed, its right-hand side is evaluated and
+# expanded as far as possible. Contrary to other situations, '$$' and
+# variable expressions based on undefined variables are preserved though.
+#
+# Whether a variable expression is undefined or not is determined at the end
+# of evaluating the expression. The consequence is that ${:Ufallback} expands
+# to "fallback"; initially this expression is undefined since it is based on
+# the variable named "", which is guaranteed to be never defined, but at the
+# end of evaluating the expression ${:Ufallback}, the modifier ':U' has turned
+# the expression into a defined expression.
+
+
+# literal dollar signs
+VAR:= $$ $$$$ $$$$$$$$
+.if ${VAR} != "\$ \$\$ \$\$\$\$"
+. error
+.endif
+
+
+# reference to a variable containing a literal dollar sign
+REF= $$ $$$$ $$$$$$$$
+VAR:= ${REF}
+REF= too late
+.if ${VAR} != "\$ \$\$ \$\$\$\$"
+. error
+.endif
+
+
+# reference to an undefined variable
+.undef UNDEF
+VAR:= <${UNDEF}>
+UNDEF= after
+.if ${VAR} != "<after>"
+. error
+.endif
+
+
+# reference to a variable whose name is computed from another variable
+REF2= referred to
+REF= REF2
+VAR:= ${${REF}}
+REF= too late
+.if ${VAR} != "referred to"
+. error
+.endif
+
+
+# expression with an indirect modifier referring to an undefined variable
+.undef UNDEF
+VAR:= ${:${UNDEF}}
+UNDEF= Uwas undefined
+.if ${VAR} != "was undefined"
+. error
+.endif
+
+
+# expression with an indirect modifier referring to another variable that
+# in turn refers to an undefined variable
+#
+# XXX: Even though this is a ':=' assignment, the '${UNDEF}' in the part of
+# the variable modifier is not preserved. To preserve it, ParseModifierPart
+# would have to call VarSubstExpr somehow since this is the only piece of
+# code that takes care of this global variable.
+.undef UNDEF
+REF= U${UNDEF}
+#.MAKEFLAGS: -dv
+VAR:= ${:${REF}}
+#.MAKEFLAGS: -d0
+REF= too late
+UNDEF= Uwas undefined
+.if ${VAR} != ""
+. error
+.endif
+
+
+# In variable assignments using the ':=' operator, undefined variables are
+# preserved, no matter how indirectly they are referenced.
+.undef REF3
+REF2= <${REF3}>
+REF= ${REF2}
+VAR:= ${REF}
+REF3= too late
+.if ${VAR} != "<too late>"
+. error
+.endif
+
+
+# In variable assignments using the ':=' operator, '$$' are preserved, no
+# matter how indirectly they are referenced.
+REF2= REF2:$$ $$$$
+REF= REF:$$ $$$$ ${REF2}
+VAR:= VAR:$$ $$$$ ${REF}
+.if ${VAR} != "VAR:\$ \$\$ REF:\$ \$\$ REF2:\$ \$\$"
+. error
+.endif
+
+
+# In variable assignments using the ':=' operator, '$$' are preserved in the
+# expressions of the top level, but not in expressions that are nested.
+VAR:= top:$$ ${:Unest1\:\$\$} ${:Unest2${:U\:\$\$}}
+.if ${VAR} != "top:\$ nest1:\$ nest2:\$"
+. error
+.endif
+
+
+# In variable assignments using the ':=' operator, there may be expressions
+# containing variable modifiers, and these modifiers may refer to other
+# variables. These referred-to variables are expanded at the time of
+# assignment. The undefined variables are kept as-is and are later expanded
+# when evaluating the condition.
+#
+# Contrary to the assignment operator '=', the assignment operator ':='
+# consumes the '$' from modifier parts.
+REF.word= 1:$$ 2:$$$$ 4:$$$$$$$$
+.undef REF.undef
+VAR:= ${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef}
+REF.word= word.after
+REF.undef= undef.after
+.if ${VAR} != "1:2:\$ 4:\$\$ undef.after, direct: 1:\$ 2:\$\$ 4:\$\$\$\$ undef.after"
+. error
+.endif
+
+# Just for comparison, the previous example using the assignment operator '='
+# instead of ':='. The right-hand side of the assignment is not evaluated at
+# the time of assignment but only later, when ${VAR} appears in the condition.
+#
+# At that point, both REF.word and REF.undef are defined.
+REF.word= 1:$$ 2:$$$$ 4:$$$$$$$$
+.undef REF.undef
+VAR= ${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef}
+REF.word= word.after
+REF.undef= undef.after
+.if ${VAR} != "word.after undef.after, direct: word.after undef.after"
+. error
+.endif
+
+
+# Between var.c 1.42 from 2000-05-11 and before parse.c 1.520 from 2020-12-27,
+# if the variable name in a ':=' assignment referred to an undefined variable,
+# there were actually 2 assignments to different variables:
+#
+# Global["VAR_SUBST_${UNDEF}"] = ""
+# Global["VAR_SUBST_"] = ""
+#
+# The variable name with the empty value actually included a dollar sign.
+# Variable names with dollars are not used in practice.
+#
+# It might be a good idea to forbid undefined variables on the left-hand side
+# of a variable assignment.
+.undef UNDEF
+VAR_ASSIGN_${UNDEF}= assigned by '='
+VAR_SUBST_${UNDEF}:= assigned by ':='
+.if ${VAR_ASSIGN_} != "assigned by '='"
+. error
+.endif
+.if defined(${:UVAR_SUBST_\${UNDEF\}})
+. error
+.endif
+.if ${VAR_SUBST_} != "assigned by ':='"
+. error
+.endif
all:
@:;
diff --git a/unit-tests/vardebug.exp b/unit-tests/vardebug.exp
index 06c8b590e1b1..cd89e81b3923 100644
--- a/unit-tests/vardebug.exp
+++ b/unit-tests/vardebug.exp
@@ -71,7 +71,7 @@ Var_Parse: ${:Uvariable:unknown} with VARE_UNDEFERR|VARE_WANTRES
Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF)
Result of ${:Uvariable} is "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF)
Applying ${:u...} to "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF)
-make: Unknown modifier 'u'
+make: "vardebug.mk" line 44: Unknown modifier 'u'
Result of ${:unknown} is error (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF)
make: "vardebug.mk" line 44: Malformed conditional (${:Uvariable:unknown})
Var_Parse: ${UNDEFINED} with VARE_UNDEFERR|VARE_WANTRES
diff --git a/unit-tests/varmisc.mk b/unit-tests/varmisc.mk
index aced1a0554d1..607c8d7e0ed3 100644
--- a/unit-tests/varmisc.mk
+++ b/unit-tests/varmisc.mk
@@ -1,5 +1,5 @@
-# $Id: varmisc.mk,v 1.21 2020/11/11 23:08:50 sjg Exp $
-# $NetBSD: varmisc.mk,v 1.28 2020/11/07 00:07:02 rillig Exp $
+# $Id: varmisc.mk,v 1.22 2020/11/30 19:27:41 sjg Exp $
+# $NetBSD: varmisc.mk,v 1.29 2020/11/28 14:08:37 rillig Exp $
#
# Miscellaneous variable tests.
@@ -78,16 +78,6 @@ MAN+= ${MAN$s}
manok:
@echo MAN=${MAN}
-# This is an expanded variant of the above .for loop.
-# Between 2020-06-28 and 2020-07-02 this paragraph generated a wrong
-# error message "Variable VARNAME is recursive".
-# When evaluating the !empty expression, the ${:U1} was not expanded and
-# thus resulted in the seeming definition VARNAME=${VARNAME}, which is
-# obviously recursive.
-VARNAME= ${VARNAME${:U1}}
-.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
-.endif
-
# begin .MAKE.SAVE_DOLLARS; see Var_SetWithFlags and ParseBoolean.
SD_VALUES= 0 1 2 False True false true Yes No yes no On Off ON OFF on off
SD_4_DOLLARS= $$$$
diff --git a/unit-tests/varmod-defined.exp b/unit-tests/varmod-defined.exp
index 7f61cc426305..15f40226f1db 100644
--- a/unit-tests/varmod-defined.exp
+++ b/unit-tests/varmod-defined.exp
@@ -1,22 +1,22 @@
Global:8_DOLLARS = $$$$$$$$
Global:VAR =
-Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_DOLLAR
+Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
Global:VAR = $$$$$$$$
-Var_Parse: ${VAR:D${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR
-Applying ${VAR:D...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none)
-Var_Parse: ${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR
-Result of ${VAR:D${8_DOLLARS}} is "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none)
+Var_Parse: ${VAR:D${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Applying ${VAR:D...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none)
+Var_Parse: ${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Result of ${VAR:D${8_DOLLARS}} is "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none)
Global:VAR = $$$$$$$$
-Var_Parse: ${VAR:@var@${8_DOLLARS}@} with VARE_WANTRES|VARE_KEEP_DOLLAR
-Applying ${VAR:@...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none)
+Var_Parse: ${VAR:@var@${8_DOLLARS}@} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Applying ${VAR:@...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none)
Modifier part: "var"
Modifier part: "${8_DOLLARS}"
ModifyWords: split "$$$$$$$$" into 1 words
Global:var = $$$$$$$$
-Var_Parse: ${8_DOLLARS} with VARE_WANTRES
+Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_UNDEF
ModifyWord_Loop: in "$$$$$$$$", replace "var" with "${8_DOLLARS}" to "$$$$"
Global:delete var
-Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none)
+Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none)
Global:VAR = $$$$
Global:.MAKEFLAGS = -r -k -d v -d
Global:.MAKEFLAGS = -r -k -d v -d 0
diff --git a/unit-tests/varmod-edge.exp b/unit-tests/varmod-edge.exp
index 94ba81e2e4f0..c90eef2756c6 100644
--- a/unit-tests/varmod-edge.exp
+++ b/unit-tests/varmod-edge.exp
@@ -1,22 +1,23 @@
-make: "varmod-edge.mk" line omitted: ok M-paren
-make: "varmod-edge.mk" line omitted: ok M-mixed
-make: "varmod-edge.mk" line omitted: ok M-unescape
+make: "varmod-edge.mk" line 166: ok M-paren
+make: "varmod-edge.mk" line 166: ok M-mixed
+make: "varmod-edge.mk" line 166: ok M-unescape
make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U
-make: "varmod-edge.mk" line omitted: ok M-nest-mix
-make: "varmod-edge.mk" line omitted: ok M-nest-brk
-make: "varmod-edge.mk" line omitted: ok M-pat-err
-make: "varmod-edge.mk" line omitted: ok M-bsbs
-make: "varmod-edge.mk" line omitted: ok M-bs1-par
-make: "varmod-edge.mk" line omitted: ok M-bs2-par
-make: "varmod-edge.mk" line omitted: ok M-128
-make: "varmod-edge.mk" line omitted: ok eq-ext
-make: "varmod-edge.mk" line omitted: ok eq-q
-make: "varmod-edge.mk" line omitted: ok eq-bs
+make: "varmod-edge.mk" line 166: ok M-nest-mix
+make: "varmod-edge.mk" line 166: ok M-nest-brk
+make: "varmod-edge.mk" line 166: ok M-pat-err
+make: "varmod-edge.mk" line 166: ok M-bsbs
+make: "varmod-edge.mk" line 166: ok M-bs1-par
+make: "varmod-edge.mk" line 166: ok M-bs2-par
+make: "varmod-edge.mk" line 166: ok M-128
+make: "varmod-edge.mk" line 166: ok eq-ext
+make: "varmod-edge.mk" line 166: ok eq-q
+make: "varmod-edge.mk" line 166: ok eq-bs
make: Unfinished modifier for INP.eq-esc ('=' missing)
-make: "varmod-edge.mk" line omitted: ok eq-esc
-make: "varmod-edge.mk" line omitted: ok colon
-make: Unknown modifier ':'
-make: Unknown modifier ':'
-make: "varmod-edge.mk" line omitted: ok colons
-ok
-exit status 0
+make: "varmod-edge.mk" line 166: ok eq-esc
+make: "varmod-edge.mk" line 166: ok colon
+make: "varmod-edge.mk" line 165: Unknown modifier ':'
+make: "varmod-edge.mk" line 165: Unknown modifier ':'
+make: "varmod-edge.mk" line 166: ok colons
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/varmod-extension.exp b/unit-tests/varmod-extension.exp
index 24f7403c7f3f..1ea2b2f0e995 100644
--- a/unit-tests/varmod-extension.exp
+++ b/unit-tests/varmod-extension.exp
@@ -7,4 +7,5 @@ extension of 'a.a' is 'a'
extension of '.gitignore' is 'gitignore'
extension of 'a' is ''
extension of 'a.a' is 'a'
+extension of 'trailing/' is ''
exit status 0
diff --git a/unit-tests/varmod-extension.mk b/unit-tests/varmod-extension.mk
index db501f7234c7..14ebed0debc1 100644
--- a/unit-tests/varmod-extension.mk
+++ b/unit-tests/varmod-extension.mk
@@ -1,9 +1,9 @@
-# $NetBSD: varmod-extension.mk,v 1.3 2020/08/23 15:09:15 rillig Exp $
+# $NetBSD: varmod-extension.mk,v 1.4 2020/12/20 22:57:40 rillig Exp $
#
# Tests for the :E variable modifier, which returns the filename extension
# of each word in the variable.
all:
-.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a
+.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/
@echo "extension of '"${path:Q}"' is '"${path:E:Q}"'"
.endfor
diff --git a/unit-tests/varmod-gmtime.exp b/unit-tests/varmod-gmtime.exp
index 06e6314c7bfb..1baa0215b6e6 100644
--- a/unit-tests/varmod-gmtime.exp
+++ b/unit-tests/varmod-gmtime.exp
@@ -1,27 +1,13 @@
-mod-gmtime:
-%Y
-2020
-%Y
-localtime == localtime
-mod-gmtime-indirect:
-make: Invalid time value: ${:U1593536400}}
-
-mtime=1593536400}
-parse-errors:
-make: Invalid time value: -1}.
-
-: -1 becomes mtime=-1}.
-make: Invalid time value: 1}.
-
-: space 1 becomes mtime= 1}.
-: 0 becomes ok.
-: 1 becomes Thu Jan 1 00:00:01 1970.
-: INT32_MAX becomes Tue Jan 19 03:14:07 2038.
-: INT32_MAX + 1 becomes Tue Jan 19 03:14:08 2038.
-make: Invalid time value: 10000000000000000000000000000000}.
-
-: overflow becomes mtime=10000000000000000000000000000000}.
-make: Invalid time value: error}.
-
-: letter becomes mtime=error}.
-exit status 0
+make: "varmod-gmtime.mk" line 60: Invalid time value: ${:U1593536400}} != "mtime=11593536400}"
+make: "varmod-gmtime.mk" line 60: Malformed conditional (${%Y:L:gmtime=${:U1593536400}} != "mtime=11593536400}")
+make: "varmod-gmtime.mk" line 70: Invalid time value: -1} != ""
+make: "varmod-gmtime.mk" line 70: Malformed conditional (${:L:gmtime=-1} != "")
+make: "varmod-gmtime.mk" line 79: Invalid time value: 1} != ""
+make: "varmod-gmtime.mk" line 79: Malformed conditional (${:L:gmtime= 1} != "")
+make: "varmod-gmtime.mk" line 118: Invalid time value: 10000000000000000000000000000000} != ""
+make: "varmod-gmtime.mk" line 118: Malformed conditional (${:L:gmtime=10000000000000000000000000000000} != "")
+make: "varmod-gmtime.mk" line 129: Invalid time value: error} != ""
+make: "varmod-gmtime.mk" line 129: Malformed conditional (${:L:gmtime=error} != "")
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/varmod-gmtime.mk b/unit-tests/varmod-gmtime.mk
index b404d1c2768e..3c5f04991c48 100644
--- a/unit-tests/varmod-gmtime.mk
+++ b/unit-tests/varmod-gmtime.mk
@@ -1,11 +1,11 @@
-# $NetBSD: varmod-gmtime.mk,v 1.6 2020/10/31 20:30:06 rillig Exp $
+# $NetBSD: varmod-gmtime.mk,v 1.9 2020/12/22 07:22:39 rillig Exp $
#
# Tests for the :gmtime variable modifier, which formats a timestamp
# using strftime(3) in UTC.
-all: mod-gmtime
-all: mod-gmtime-indirect
-all: parse-errors
+.if ${TZ:Uundefined} != "undefined" # see unit-tests/Makefile
+. error
+.endif
# Test for the default time format, %c. Since the time always varies, it's
# only possible to check for the general format here. The names of the
@@ -15,73 +15,122 @@ all: parse-errors
. error
.endif
-mod-gmtime:
- @echo $@:
-
- # modifier name too short
- @echo ${%Y:L:gmtim=1593536400}
-
- # 2020-07-01T00:00:00Z
- @echo ${%Y:L:gmtime=1593536400}
-
- # modifier name too long
- @echo ${%Y:L:gmtimer=1593536400}
-
- # If the modifier name is not matched exactly, fall back to the
- # :from=to modifier.
- @echo ${gmtime:L:gm%=local%} == localtime
-
-mod-gmtime-indirect:
- @echo $@:
-
- # As of 2020-08-16, it is not possible to pass the seconds via a
- # variable expression. This is because parsing of the :gmtime
- # modifier stops at the '$' and returns to ApplyModifiers.
- #
- # There, a colon would be skipped but not a dollar.
- # Parsing therefore continues at the '$' of the ${:U159...}, looking
- # for an ordinary variable modifier.
- #
- # At this point, the ${:U} is expanded and interpreted as a variable
- # modifier, which results in the error message "Unknown modifier '1'".
- #
- # If ApplyModifier_Gmtime were to pass its argument through
- # ParseModifierPart, this would work.
- @echo ${%Y:L:gmtime=${:U1593536400}}
-
-parse-errors:
- @echo $@:
-
- # As of 2020-10-31, it is possible to pass negative time stamps
- # to the :gmtime modifier, resulting in dates before 1970.
- # Going back 50 years in the past is not a practical use case for
- # make.
- : -1 becomes ${:L:gmtime=-1}.
-
- # Spaces are allowed, not because it would make sense but just as
- # a side-effect from using strtoul.
- : space 1 becomes ${:L:gmtime= 1}.
-
- # 0 means now; to get consistent test results, the actual value has
- # to be normalized.
- : 0 becomes ${:L:gmtime=0:C,^... ... .. ..:..:.. 20..$,ok,W}.
-
- : 1 becomes ${:L:gmtime=1}.
-
- : INT32_MAX becomes ${:L:gmtime=2147483647}.
-
- # This may be different if time_t is still a 32-bit signed integer.
- : INT32_MAX + 1 becomes ${:L:gmtime=2147483648}.
-
- # Integer overflow.
- # Because this modifier is implemented using strtoul, the parsed
- # time is ULONG_MAX, which gets converted to -1. This results
- # in a time stamp of the second before 1970.
- : overflow becomes ${:L:gmtime=10000000000000000000000000000000}.
-
- # As of 2020-10-31, there is no error handling while parsing the
- # :gmtime modifier, thus no error message is printed. Parsing
- # stops after the '=', and the remaining string is parsed for
- # more variable modifiers. Because of the unknown modifier 'e',
- # the whole variable value is discarded and thus not printed.
- : letter becomes ${:L:gmtime=error}.
+
+# modifier name too short, falling back to the SysV modifier.
+.if ${%Y:L:gmtim=1593536400} != "%Y"
+. error
+.endif
+
+
+# 2020-07-01T00:00:00Z
+.if ${%Y:L:gmtime=1593536400} != "2020"
+. error
+.endif
+
+
+# modifier name too long, falling back to the SysV modifier.
+.if ${%Y:L:gmtimer=1593536400} != "%Y"
+. error
+.endif
+
+
+# If the modifier name is not matched exactly, fall back to the
+# :from=to modifier.
+.if ${gmtime:L:gm%=local%} != "localtime"
+. error
+.endif
+
+
+# As of 2020-08-16, it is not possible to pass the seconds via a
+# variable expression. This is because parsing of the :gmtime
+# modifier stops at the '$' and returns to ApplyModifiers.
+#
+# There, a colon would be skipped but not a dollar.
+# Parsing therefore continues at the '$' of the ${:U159...}, looking
+# for an ordinary variable modifier.
+#
+# At this point, the ${:U} is expanded and interpreted as a variable
+# modifier, which results in the error message "Unknown modifier '1'".
+#
+# If ApplyModifier_Gmtime were to pass its argument through
+# ParseModifierPart, this would work.
+#
+# XXX: Where does the empty line 4 in varmod-gmtime.exp come from?
+# TODO: Remove the \n from "Invalid time value: %s\n" in var.c.
+.if ${%Y:L:gmtime=${:U1593536400}} != "mtime=11593536400}"
+. error
+.endif
+
+
+# Before var.c 1.631 from 2020-10-31 21:40:20, it was possible to pass
+# negative time stamps to the :gmtime modifier, resulting in dates before
+# 1970. Going back 50 years in the past is not a practical use case for
+# make. Therefore, since var.c 1.631, negative time stamps produce a
+# parse error.
+.if ${:L:gmtime=-1} != ""
+. error
+.else
+. error
+.endif
+
+
+# Spaces were allowed before var.c 1.631, not because it would make sense
+# but just as a side-effect from using strtoul.
+.if ${:L:gmtime= 1} != ""
+. error
+.endif
+
+
+# 0 means now; this differs from GNode.mtime, where a 0 means nonexistent.
+# Since "now" constantly changes, the strongest possible test is to match the
+# resulting pattern.
+.if !${:L:gmtime=0:tW:M??? ??? ?? ??\:??\:?? 20??}
+. error
+.endif
+
+
+.if ${:L:gmtime=1} != "Thu Jan 1 00:00:01 1970"
+. error
+.endif
+
+
+# INT32_MAX
+.if ${:L:gmtime=2147483647} != "Tue Jan 19 03:14:07 2038"
+. error
+.endif
+
+
+.if ${:L:gmtime=2147483648} == "Tue Jan 19 03:14:08 2038"
+# All systems that have unsigned time_t or 64-bit time_t.
+.elif ${:L:gmtime=2147483648} != "Fri Dec 13 20:45:52 1901"
+# FreeBSD-12.0-i386 still has 32-bit signed time_t.
+.else
+. error
+.endif
+
+
+# Integer overflow, at least before var.c 1.631 from 2020-10-31.
+# Because this modifier is implemented using strtoul, the parsed time was
+# ULONG_MAX, which got converted to -1. This resulted in a time stamp of
+# the second before 1970.
+#
+# Since var.c 1.631, the overflow is detected and produces a parse error.
+.if ${:L:gmtime=10000000000000000000000000000000} != ""
+. error
+.else
+. error
+.endif
+
+# Before var.c 1.631 from 2020-10-31, there was no error handling while
+# parsing the :gmtime modifier, thus no error message is printed. Parsing
+# stopped after the '=', and the remaining string was parsed for more variable
+# modifiers. Because of the unknown modifier 'e' from the 'error', the whole
+# variable value was discarded and thus not printed.
+.if ${:L:gmtime=error} != ""
+. error
+.else
+. error
+.endif
+
+
+all:
diff --git a/unit-tests/varmod-head.exp b/unit-tests/varmod-head.exp
index f0bf87f03012..651844439f5f 100644
--- a/unit-tests/varmod-head.exp
+++ b/unit-tests/varmod-head.exp
@@ -7,4 +7,5 @@ head (dirname) of 'a.a' is '.'
head (dirname) of '.gitignore' is '.'
head (dirname) of 'a' is '.'
head (dirname) of 'a.a' is '.'
+head (dirname) of 'trailing/' is 'trailing'
exit status 0
diff --git a/unit-tests/varmod-head.mk b/unit-tests/varmod-head.mk
index eda4820c5f14..66347b4bce61 100644
--- a/unit-tests/varmod-head.mk
+++ b/unit-tests/varmod-head.mk
@@ -1,9 +1,9 @@
-# $NetBSD: varmod-head.mk,v 1.3 2020/08/23 15:09:15 rillig Exp $
+# $NetBSD: varmod-head.mk,v 1.4 2020/12/20 22:57:40 rillig Exp $
#
# Tests for the :H variable modifier, which returns the dirname of
# each of the words in the variable value.
all:
-.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a
+.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/
@echo "head (dirname) of '"${path:Q}"' is '"${path:H:Q}"'"
.endfor
diff --git a/unit-tests/varmod-ifelse.exp b/unit-tests/varmod-ifelse.exp
index 75518c08117f..17d4d8afcbeb 100644
--- a/unit-tests/varmod-ifelse.exp
+++ b/unit-tests/varmod-ifelse.exp
@@ -11,6 +11,10 @@ lhs = 1.000000, rhs = 0.000000, op = ==
make: Bad conditional expression `1 == == 2' in 1 == == 2?yes:no
lhs = "", rhs = "", op = !=
make: "varmod-ifelse.mk" line 92: warning: Oops, the parse error should have been propagated.
+CondParser_Eval: ${ ${:U\$}{VAR} == value :?ok:bad} != "ok"
+CondParser_Eval: ${VAR} == value
+lhs = "value", rhs = "value", op = ==
+lhs = "ok", rhs = "ok", op = !=
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/varmod-ifelse.mk b/unit-tests/varmod-ifelse.mk
index ea94dc875e4d..5e0ad04584be 100644
--- a/unit-tests/varmod-ifelse.mk
+++ b/unit-tests/varmod-ifelse.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-ifelse.mk,v 1.6 2020/11/12 00:29:55 rillig Exp $
+# $NetBSD: varmod-ifelse.mk,v 1.8 2020/12/10 16:47:42 rillig Exp $
#
# Tests for the ${cond:?then:else} variable modifier, which evaluates either
# the then-expression or the else-expression, depending on the condition.
@@ -93,5 +93,23 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign}
.endif
.MAKEFLAGS: -d0
+# As of 2020-12-10, the variable "name" is first expanded, and the result of
+# this expansion is then taken as the condition. To force the variable
+# expression in the condition to be evaluated at exactly the right point,
+# the '$' of the intended '${VAR}' escapes from the parser in form of the
+# expression ${:U\$}. Because of this escaping, the variable "name" and thus
+# the condition ends up as "${VAR} == value", just as intended.
+#
+# This hack does not work for variables from .for loops since these are
+# expanded at parse time to their corresponding ${:Uvalue} expressions.
+# Making the '$' of the '${VAR}' expression indirect hides this expression
+# from the parser of the .for loop body. See SubstVarLong.
+.MAKEFLAGS: -dc
+VAR= value
+.if ${ ${:U\$}{VAR} == value :?ok:bad} != "ok"
+. error
+.endif
+.MAKEFLAGS: -d0
+
all:
@:;
diff --git a/unit-tests/varmod-indirect.exp b/unit-tests/varmod-indirect.exp
new file mode 100644
index 000000000000..8cdd49ad4f03
--- /dev/null
+++ b/unit-tests/varmod-indirect.exp
@@ -0,0 +1,59 @@
+make: "varmod-indirect.mk" line 13: Unknown modifier '$'
+make: "varmod-indirect.mk" line 108: before
+make: "varmod-indirect.mk" line 108: after
+make: "varmod-indirect.mk" line 114: before
+make: "varmod-indirect.mk" line 114: after
+make: "varmod-indirect.mk" line 120: before
+make: "varmod-indirect.mk" line 120: after
+make: "varmod-indirect.mk" line 124: Unknown modifier 'Z'
+make: "varmod-indirect.mk" line 125: before
+make: "varmod-indirect.mk" line 125: after
+ParseReadLine (134): '_:= before ${UNDEF} after'
+Global:_ =
+Var_Parse: ${UNDEF} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Global:_ = before ${UNDEF} after
+ParseReadLine (137): '_:= before ${UNDEF:${:US,a,a,}} after'
+Var_Parse: ${UNDEF:${:US,a,a,}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Var_Parse: ${:US,a,a,}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+Result of ${:US,a,a,} is "S,a,a," (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF|VEF_DEF)
+Indirect modifier "S,a,a," from "${:US,a,a,}"
+Applying ${UNDEF:S...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+Modifier part: "a"
+Modifier part: "a"
+ModifyWords: split "" into 1 words
+Result of ${UNDEF:S,a,a,} is "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+Var_Parse: ${:US,a,a,}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+Result of ${:US,a,a,} is "S,a,a," (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF|VEF_DEF)
+Global:_ = before ${UNDEF:S,a,a,} after
+ParseReadLine (147): '_:= before ${UNDEF:${:U}} after'
+Var_Parse: ${UNDEF:${:U}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Var_Parse: ${:U}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Applying ${:U} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+Result of ${:U} is "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF|VEF_DEF)
+Indirect modifier "" from "${:U}"
+Var_Parse: ${:U}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Applying ${:U} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+Result of ${:U} is "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF|VEF_DEF)
+Global:_ = before ${UNDEF:} after
+ParseReadLine (152): '_:= before ${UNDEF:${:UZ}} after'
+Var_Parse: ${UNDEF:${:UZ}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Var_Parse: ${:UZ}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+Result of ${:UZ} is "Z" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF|VEF_DEF)
+Indirect modifier "Z" from "${:UZ}"
+Applying ${UNDEF:Z} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+make: "varmod-indirect.mk" line 152: Unknown modifier 'Z'
+Result of ${UNDEF:Z} is error (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+Var_Parse: ${:UZ}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
+Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF)
+Result of ${:UZ} is "Z" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VEF_UNDEF|VEF_DEF)
+Global:_ = before ${UNDEF:Z} after
+ParseReadLine (154): '.MAKEFLAGS: -d0'
+ParseDoDependency(.MAKEFLAGS: -d0)
+Global:.MAKEFLAGS = -r -k -d 0 -d pv -d
+Global:.MAKEFLAGS = -r -k -d 0 -d pv -d 0
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/varmod-indirect.mk b/unit-tests/varmod-indirect.mk
new file mode 100644
index 000000000000..d130c7cae76d
--- /dev/null
+++ b/unit-tests/varmod-indirect.mk
@@ -0,0 +1,157 @@
+# $NetBSD: varmod-indirect.mk,v 1.5 2020/12/27 17:32:25 rillig Exp $
+#
+# Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}.
+# These can be used for very basic purposes like converting a string to either
+# uppercase or lowercase, as well as for fairly advanced modifiers that first
+# look like line noise and are hard to decipher.
+#
+# TODO: Since when are indirect modifiers supported?
+
+
+# To apply a modifier indirectly via another variable, the whole
+# modifier must be put into a single variable expression.
+.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
+. warning unexpected
+.endif
+
+
+# Adding another level of indirection (the 2 nested :U expressions) helps.
+.if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement"
+. warning unexpected
+.endif
+
+
+# Multiple indirect modifiers can be applied one after another as long as
+# they are separated with colons.
+.if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE"
+. warning unexpected
+.endif
+
+
+# An indirect variable that evaluates to the empty string is allowed though.
+# This makes it possible to define conditional modifiers, like this:
+#
+# M.little-endian= S,1234,4321,
+# M.big-endian= # none
+.if ${value:L:${:Dempty}S,a,A,} != "vAlue"
+. warning unexpected
+.endif
+
+
+# The nested variable expression expands to "tu", and this is interpreted as
+# a variable modifier for the value "Upper", resulting in "UPPER".
+.if ${Upper:L:${:Utu}} != "UPPER"
+. error
+.endif
+
+# The nested variable expression expands to "tl", and this is interpreted as
+# a variable modifier for the value "Lower", resulting in "lower".
+.if ${Lower:L:${:Utl}} != "lower"
+. error
+.endif
+
+
+# The nested variable expression is ${1 != 1:?Z:tl}, consisting of the
+# condition "1 != 1", the then-branch "Z" and the else-branch "tl". Since
+# the condition evaluates to false, the then-branch is ignored (it would
+# have been an unknown modifier anyway) and the ":tl" modifier is applied.
+.if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed"
+. error
+.endif
+
+
+# The indirect modifier can also replace an ':L' modifier, which allows for
+# brain twisters since by reading the expression alone, it is not possible
+# to say whether the variable name will be evaluated as a variable name or
+# as the immediate value of the expression.
+VAR= value
+M_ExpandVar= # an empty modifier
+M_VarAsValue= L
+#
+.if ${VAR:${M_ExpandVar}} != "value"
+. error
+.endif
+.if ${VAR:${M_VarAsValue}} != "VAR"
+. error
+.endif
+
+# The indirect modifier M_ListToSkip, when applied to a list of patterns,
+# expands to a sequence of ':N' modifiers, each of which filters one of the
+# patterns. This list of patterns can then be applied to another variable
+# to actually filter that variable.
+#
+M_ListToSkip= @pat@N$${pat}@:ts:
+#
+# The dollar signs need to be doubled in the above modifier expression,
+# otherwise they would be expanded too early, that is, when parsing the
+# modifier itself.
+#
+# In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'.
+# The 'N' comes from the expression 'N${pat}', the separating colons come
+# from the modifier ':ts:'.
+#
+#.MAKEFLAGS: -dcv # Uncomment this line to see the details
+#
+PRIMES= 2 3 5 7 1[1379]
+M_NoPrimes= ${PRIMES:${M_ListToSkip}}
+.if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20"
+. error
+.endif
+.MAKEFLAGS: -d0
+
+
+# In contrast to the .if conditions, the .for loop allows undefined variable
+# expressions. These expressions expand to empty strings.
+
+# An undefined expression without any modifiers expands to an empty string.
+.for var in before ${UNDEF} after
+. info ${var}
+.endfor
+
+# An undefined expression with only modifiers that keep the expression
+# undefined expands to an empty string.
+.for var in before ${UNDEF:${:US,a,a,}} after
+. info ${var}
+.endfor
+
+# Even in an indirect modifier based on an undefined variable, the value of
+# the expression in Var_Parse is a simple empty string.
+.for var in before ${UNDEF:${:U}} after
+. info ${var}
+.endfor
+
+# An error in an indirect modifier.
+.for var in before ${UNDEF:${:UZ}} after
+. info ${var}
+.endfor
+
+
+# Another slightly different evaluation context is the right-hand side of
+# a variable assignment using ':='.
+.MAKEFLAGS: -dpv
+
+# The undefined variable expression is kept as-is.
+_:= before ${UNDEF} after
+
+# The undefined variable expression is kept as-is.
+_:= before ${UNDEF:${:US,a,a,}} after
+
+# XXX: The subexpression ${:U} is fully defined, therefore it is expanded.
+# This results in ${UNDEF:}, which can lead to tricky parse errors later,
+# when the variable '_' is expanded further.
+#
+# XXX: What should be the correct strategy here? One possibility is to
+# expand the defined subexpression and replace it with ${:U...}, just like
+# in .for loops. This would preserve the structure of the expression while
+# at the same time expanding the expression as far as possible.
+_:= before ${UNDEF:${:U}} after
+
+# XXX: This expands to ${UNDEF:Z}, which will behave differently if the
+# variable '_' is used in a context where the variable expression ${_} is
+# parsed but not evaluated.
+_:= before ${UNDEF:${:UZ}} after
+
+.MAKEFLAGS: -d0
+.undef _
+
+all:
diff --git a/unit-tests/varmod-localtime.exp b/unit-tests/varmod-localtime.exp
index e89a03b40765..b58de7700466 100644
--- a/unit-tests/varmod-localtime.exp
+++ b/unit-tests/varmod-localtime.exp
@@ -1,27 +1,13 @@
-mod-localtime
-%Y
-2020
-%Y
-gmtime == gmtime
-mod-localtime-indirect:
-make: Invalid time value: ${:U1593536400}}
-
-ocaltime=1593536400}
-parse-errors:
-make: Invalid time value: -1}.
-
-: -1 becomes ocaltime=-1}.
-make: Invalid time value: 1}.
-
-: space 1 becomes ocaltime= 1}.
-: 0 becomes ok.
-: 1 becomes Thu Jan 1 01:00:01 1970.
-: INT32_MAX becomes Tue Jan 19 04:14:07 2038.
-: INT32_MAX + 1 becomes Tue Jan 19 04:14:08 2038.
-make: Invalid time value: 10000000000000000000000000000000}.
-
-: overflow becomes ocaltime=10000000000000000000000000000000}.
-make: Invalid time value: error}.
-
-: letter becomes ocaltime=error}.
-exit status 0
+make: "varmod-localtime.mk" line 60: Invalid time value: ${:U1593536400}} != "mtime=11593536400}"
+make: "varmod-localtime.mk" line 60: Malformed conditional (${%Y:L:localtime=${:U1593536400}} != "mtime=11593536400}")
+make: "varmod-localtime.mk" line 70: Invalid time value: -1} != ""
+make: "varmod-localtime.mk" line 70: Malformed conditional (${:L:localtime=-1} != "")
+make: "varmod-localtime.mk" line 79: Invalid time value: 1} != ""
+make: "varmod-localtime.mk" line 79: Malformed conditional (${:L:localtime= 1} != "")
+make: "varmod-localtime.mk" line 118: Invalid time value: 10000000000000000000000000000000} != ""
+make: "varmod-localtime.mk" line 118: Malformed conditional (${:L:localtime=10000000000000000000000000000000} != "")
+make: "varmod-localtime.mk" line 129: Invalid time value: error} != ""
+make: "varmod-localtime.mk" line 129: Malformed conditional (${:L:localtime=error} != "")
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/varmod-localtime.mk b/unit-tests/varmod-localtime.mk
index f7358e309046..3ee2f0ac93fb 100644
--- a/unit-tests/varmod-localtime.mk
+++ b/unit-tests/varmod-localtime.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-localtime.mk,v 1.5 2020/10/31 20:30:06 rillig Exp $
+# $NetBSD: varmod-localtime.mk,v 1.7 2020/12/22 07:22:39 rillig Exp $
#
# Tests for the :localtime variable modifier, which formats a timestamp
# using strftime(3) in local time.
@@ -7,10 +7,6 @@
. error
.endif
-all: mod-localtime
-all: mod-localtime-indirect
-all: parse-errors
-
# Test for the default time format, %c. Since the time always varies, it's
# only possible to check for the general format here. The names of the
# month and weekday are always in English, independent from the locale.
@@ -19,74 +15,122 @@ all: parse-errors
. error
.endif
-mod-localtime:
- @echo $@
-
- # modifier name too short
- @echo ${%Y:L:localtim=1593536400}
-
- # 2020-07-01T00:00:00Z
- @echo ${%Y:L:localtime=1593536400}
-
- # modifier name too long
- @echo ${%Y:L:localtimer=1593536400}
-
- # If the modifier name is not matched exactly, fall back to the
- # :from=to modifier.
- @echo ${localtime:L:local%=gm%} == gmtime
-
-mod-localtime-indirect:
- @echo $@:
-
- # As of 2020-08-16, it is not possible to pass the seconds via a
- # variable expression. This is because parsing of the :localtime
- # modifier stops at the '$' and returns to ApplyModifiers.
- #
- # There, a colon would be skipped but not a dollar.
- # Parsing therefore continues at the '$' of the ${:U159...}, looking
- # for an ordinary variable modifier.
- #
- # At this point, the ${:U} is expanded and interpreted as a variable
- # modifier, which results in the error message "Unknown modifier '1'".
- #
- # If ApplyModifier_Localtime were to pass its argument through
- # ParseModifierPart, this would work.
- @echo ${%Y:L:localtime=${:U1593536400}}
-
-parse-errors:
- @echo $@:
-
- # As of 2020-10-31, it is possible to pass negative time stamps
- # to the :localtime modifier, resulting in dates before 1970.
- # Going back 50 years in the past is not a practical use case for
- # make.
- : -1 becomes ${:L:localtime=-1}.
-
- # Spaces are allowed, not because it would make sense but just as
- # a side-effect from using strtoul.
- : space 1 becomes ${:L:localtime= 1}.
-
- # 0 means now; to get consistent test results, the actual value has
- # to be normalized.
- : 0 becomes ${:L:localtime=0:C,^... ... .. ..:..:.. 20..$,ok,W}.
-
- : 1 becomes ${:L:localtime=1}.
-
- : INT32_MAX becomes ${:L:localtime=2147483647}.
-
- # This may be different if time_t is still a 32-bit signed integer.
- : INT32_MAX + 1 becomes ${:L:localtime=2147483648}.
-
- # Integer overflow.
- # Because this modifier is implemented using strtoul, the parsed
- # time is ULONG_MAX, which gets converted to -1. This results
- # in a time stamp of the second before 1970 (in UTC) or 3599 seconds
- # after New Year's Day in Europe/Berlin.
- : overflow becomes ${:L:localtime=10000000000000000000000000000000}.
-
- # As of 2020-10-31, there is no error handling while parsing the
- # :localtime modifier, thus no error message is printed. Parsing
- # stops after the '=', and the remaining string is parsed for
- # more variable modifiers. Because of the unknown modifier 'e',
- # the whole variable value is discarded and thus not printed.
- : letter becomes ${:L:localtime=error}.
+
+# modifier name too short, falling back to the SysV modifier.
+.if ${%Y:L:localtim=1593536400} != "%Y"
+. error
+.endif
+
+
+# 2020-07-01T00:00:00Z
+.if ${%Y:L:localtime=1593536400} != "2020"
+. error
+.endif
+
+
+# modifier name too long, falling back to the SysV modifier.
+.if ${%Y:L:localtimer=1593536400} != "%Y"
+. error
+.endif
+
+
+# If the modifier name is not matched exactly, fall back to the
+# :from=to modifier.
+.if ${gmtime:L:gm%=local%} != "localtime"
+. error
+.endif
+
+
+# As of 2020-08-16, it is not possible to pass the seconds via a
+# variable expression. This is because parsing of the :localtime
+# modifier stops at the '$' and returns to ApplyModifiers.
+#
+# There, a colon would be skipped but not a dollar.
+# Parsing therefore continues at the '$' of the ${:U159...}, looking
+# for an ordinary variable modifier.
+#
+# At this point, the ${:U} is expanded and interpreted as a variable
+# modifier, which results in the error message "Unknown modifier '1'".
+#
+# If ApplyModifier_Localtime were to pass its argument through
+# ParseModifierPart, this would work.
+#
+# XXX: Where does the empty line 4 in varmod-localtime.exp come from?
+# TODO: Remove the \n from "Invalid time value: %s\n" in var.c.
+.if ${%Y:L:localtime=${:U1593536400}} != "mtime=11593536400}"
+. error
+.endif
+
+
+# Before var.c 1.631 from 2020-10-31 21:40:20, it was possible to pass
+# negative time stamps to the :localtime modifier, resulting in dates before
+# 1970. Going back 50 years in the past is not a practical use case for
+# make. Therefore, since var.c 1.631, negative time stamps produce a
+# parse error.
+.if ${:L:localtime=-1} != ""
+. error
+.else
+. error
+.endif
+
+
+# Spaces were allowed before var.c 1.631, not because it would make sense
+# but just as a side-effect from using strtoul.
+.if ${:L:localtime= 1} != ""
+. error
+.endif
+
+
+# 0 means now; this differs from GNode.mtime, where a 0 means nonexistent.
+# Since "now" constantly changes, the strongest possible test is to match the
+# resulting pattern.
+.if !${:L:localtime=0:tW:M??? ??? ?? ??\:??\:?? 20??}
+. error
+.endif
+
+
+.if ${:L:localtime=1} != "Thu Jan 1 01:00:01 1970"
+. error
+.endif
+
+
+# INT32_MAX
+.if ${:L:localtime=2147483647} != "Tue Jan 19 04:14:07 2038"
+. error
+.endif
+
+
+.if ${:L:localtime=2147483648} == "Tue Jan 19 04:14:08 2038"
+# All systems that have unsigned time_t or 64-bit time_t.
+.elif ${:L:localtime=2147483648} != "Fri Dec 13 21:45:52 1901"
+# FreeBSD-12.0-i386 still has 32-bit signed time_t.
+.else
+. error
+.endif
+
+
+# Integer overflow, at least before var.c 1.631 from 2020-10-31.
+# Because this modifier is implemented using strtoul, the parsed time was
+# ULONG_MAX, which got converted to -1. This resulted in a time stamp of
+# the second before 1970.
+#
+# Since var.c 1.631, the overflow is detected and produces a parse error.
+.if ${:L:localtime=10000000000000000000000000000000} != ""
+. error
+.else
+. error
+.endif
+
+# Before var.c 1.631 from 2020-10-31, there was no error handling while
+# parsing the :localtime modifier, thus no error message is printed. Parsing
+# stopped after the '=', and the remaining string was parsed for more variable
+# modifiers. Because of the unknown modifier 'e' from the 'error', the whole
+# variable value was discarded and thus not printed.
+.if ${:L:localtime=error} != ""
+. error
+.else
+. error
+.endif
+
+
+all:
diff --git a/unit-tests/varmod-range.exp b/unit-tests/varmod-range.exp
index eeeceb72b83f..3a9d4d032c3a 100644
--- a/unit-tests/varmod-range.exp
+++ b/unit-tests/varmod-range.exp
@@ -1,13 +1,12 @@
make: "varmod-range.mk" line 53: Invalid number: x}Rest" != "Rest"
-
make: "varmod-range.mk" line 53: Malformed conditional ("${:U:range=x}Rest" != "Rest")
-make: Unknown modifier 'x'
+make: "varmod-range.mk" line 62: Unknown modifier 'x'
make: "varmod-range.mk" line 62: Malformed conditional ("${:U:range=0x0}Rest" != "Rest")
-make: Unknown modifier 'r'
+make: "varmod-range.mk" line 78: Unknown modifier 'r'
make: "varmod-range.mk" line 78: Malformed conditional ("${a b c:L:rang}Rest" != "Rest")
-make: Unknown modifier 'r'
+make: "varmod-range.mk" line 85: Unknown modifier 'r'
make: "varmod-range.mk" line 85: Malformed conditional ("${a b c:L:rango}Rest" != "Rest")
-make: Unknown modifier 'r'
+make: "varmod-range.mk" line 92: Unknown modifier 'r'
make: "varmod-range.mk" line 92: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest")
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
diff --git a/unit-tests/varmod-root.exp b/unit-tests/varmod-root.exp
index 24ecbb875f77..2c99cd3ef4c7 100644
--- a/unit-tests/varmod-root.exp
+++ b/unit-tests/varmod-root.exp
@@ -7,4 +7,5 @@ root of 'a.a' is 'a'
root of '.gitignore' is ''
root of 'a' is 'a'
root of 'a.a' is 'a'
+root of 'trailing/' is 'trailing/'
exit status 0
diff --git a/unit-tests/varmod-root.mk b/unit-tests/varmod-root.mk
index 88af42d82510..1e3159733df0 100644
--- a/unit-tests/varmod-root.mk
+++ b/unit-tests/varmod-root.mk
@@ -1,9 +1,9 @@
-# $NetBSD: varmod-root.mk,v 1.3 2020/08/23 15:09:15 rillig Exp $
+# $NetBSD: varmod-root.mk,v 1.4 2020/12/20 22:57:40 rillig Exp $
#
# Tests for the :R variable modifier, which returns the filename root
# without the extension.
all:
-.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a
+.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/
@echo "root of '"${path:Q}"' is '"${path:R:Q}"'"
.endfor
diff --git a/unit-tests/varmod-subst-regex.exp b/unit-tests/varmod-subst-regex.exp
index eb9ae7f41fb9..207a97fc25e8 100644
--- a/unit-tests/varmod-subst-regex.exp
+++ b/unit-tests/varmod-subst-regex.exp
@@ -20,4 +20,6 @@ mod-regex-limits:22-ok:1 33 556
mod-regex-limits:capture:ihgfedcbaabcdefghijABCDEFGHIJa0a1a2rest
make: Regex compilation error: (details omitted)
mod-regex-errors:
+make: Unknown modifier 'Z'
+mod-regex-errors: xy
exit status 0
diff --git a/unit-tests/varmod-subst-regex.mk b/unit-tests/varmod-subst-regex.mk
index f558ae1134e8..91b2f0e6a2f9 100644
--- a/unit-tests/varmod-subst-regex.mk
+++ b/unit-tests/varmod-subst-regex.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-subst-regex.mk,v 1.5 2020/10/31 12:20:36 rillig Exp $
+# $NetBSD: varmod-subst-regex.mk,v 1.6 2020/12/05 18:13:44 rillig Exp $
#
# Tests for the :C,from,to, variable modifier.
@@ -102,3 +102,8 @@ mod-regex-limits:
mod-regex-errors:
@echo $@: ${UNDEF:Uvalue:C,[,,}
+
+ # If the replacement pattern produces a parse error because of an
+ # unknown modifier, the parse error is ignored in ParseModifierPart
+ # and the faulty variable expression expands to "".
+ @echo $@: ${word:L:C,.*,x${:U:Z}y,W}
diff --git a/unit-tests/varmod-sysv.exp b/unit-tests/varmod-sysv.exp
index 301519ecc747..57e69a667281 100644
--- a/unit-tests/varmod-sysv.exp
+++ b/unit-tests/varmod-sysv.exp
@@ -1,5 +1,5 @@
-make: Unfinished modifier for word203 ('=' missing)
-make: "varmod-sysv.mk" line 210: Malformed conditional (${word203:L:from${:D=}to})
+make: Unfinished modifier for word214 ('=' missing)
+make: "varmod-sysv.mk" line 214: Malformed conditional (${word214:L:from${:D=}to})
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/varmod-sysv.mk b/unit-tests/varmod-sysv.mk
index 10643495fef5..751736ceaf74 100644
--- a/unit-tests/varmod-sysv.mk
+++ b/unit-tests/varmod-sysv.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-sysv.mk,v 1.11 2020/11/01 22:28:52 rillig Exp $
+# $NetBSD: varmod-sysv.mk,v 1.12 2020/12/05 13:01:33 rillig Exp $
#
# Tests for the ${VAR:from=to} variable modifier, which replaces the suffix
# "from" with "to". It can also use '%' as a wildcard.
@@ -77,13 +77,16 @@
# If the variable value is empty, it is debatable whether it consists of a
# single empty word, or no word at all. The :from=to modifier treats it as
# no word at all.
+#
+# See SysVMatch, which doesn't handle w_len == p_len specially.
.if ${:L:=suffix} != ""
. error
.endif
# If the variable value is empty, it is debatable whether it consists of a
-# single empty word, or no word at all. The :from=to modifier treats it as
-# no word at all.
+# single empty word (before 2020-05-06), or no word at all (since 2020-05-06).
+#
+# See SysVMatch, percent != NULL && w[0] == '\0'.
.if ${:L:%=suffix} != ""
. error
.endif
@@ -205,9 +208,10 @@
# This is not a SysV modifier since the nested variable expression expands
# to an empty string. The '=' in it should be irrelevant during parsing.
-# As of 2020-11-01, this seemingly correct modifier leads to a parse error.
-# XXX
-.if ${word203:L:from${:D=}to}
+# XXX: As of 2020-12-05, this expression generates an "Unfinished modifier"
+# error, while the correct error message would be "Unknown modifier" since
+# there is no modifier named "fromto".
+.if ${word214:L:from${:D=}to}
. error
.endif
diff --git a/unit-tests/varmod-tail.exp b/unit-tests/varmod-tail.exp
index e25c1cc4b914..26c87f3b5c1b 100644
--- a/unit-tests/varmod-tail.exp
+++ b/unit-tests/varmod-tail.exp
@@ -7,4 +7,5 @@ tail (basename) of 'a.a' is 'a.a'
tail (basename) of '.gitignore' is '.gitignore'
tail (basename) of 'a' is 'a'
tail (basename) of 'a.a' is 'a.a'
+tail (basename) of 'trailing/' is ''
exit status 0
diff --git a/unit-tests/varmod-tail.mk b/unit-tests/varmod-tail.mk
index a8078cc67335..05eae481fe3e 100644
--- a/unit-tests/varmod-tail.mk
+++ b/unit-tests/varmod-tail.mk
@@ -1,9 +1,9 @@
-# $NetBSD: varmod-tail.mk,v 1.3 2020/08/23 15:09:15 rillig Exp $
+# $NetBSD: varmod-tail.mk,v 1.4 2020/12/20 22:57:40 rillig Exp $
#
# Tests for the :T variable modifier, which returns the basename of each of
# the words in the variable value.
all:
-.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a
+.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/
@echo "tail (basename) of '"${path:Q}"' is '"${path:T:Q}"'"
.endfor
diff --git a/unit-tests/varmod-to-many-words.mk b/unit-tests/varmod-to-many-words.mk
index 10cddb00c5e4..e96962ce4136 100644
--- a/unit-tests/varmod-to-many-words.mk
+++ b/unit-tests/varmod-to-many-words.mk
@@ -1,9 +1,21 @@
-# $NetBSD: varmod-to-many-words.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varmod-to-many-words.mk,v 1.3 2020/12/20 23:29:50 rillig Exp $
#
# Tests for the :tw modifier, which treats the variable as many words,
# to undo a previous :tW modifier.
-# TODO: Implementation
+SENTENCE= The quick brown fox jumps over the lazy brown dog.
+
+.if ${SENTENCE:tw:[#]} != 10
+. error
+.endif
+.if ${SENTENCE:tW:[#]} != 1
+. error
+.endif
+
+# Protect against accidental freeing of the variable value.
+.if ${SENTENCE} != "The quick brown fox jumps over the lazy brown dog."
+. error
+.endif
all:
@:;
diff --git a/unit-tests/varmod-to-one-word.mk b/unit-tests/varmod-to-one-word.mk
index 0865ce8fb41f..e4e2e99781f2 100644
--- a/unit-tests/varmod-to-one-word.mk
+++ b/unit-tests/varmod-to-one-word.mk
@@ -1,9 +1,21 @@
-# $NetBSD: varmod-to-one-word.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varmod-to-one-word.mk,v 1.3 2020/12/20 23:29:50 rillig Exp $
#
# Tests for the :tW variable modifier, which treats the variable value
# as a single word, for all following modifiers.
-# TODO: Implementation
+SENTENCE= The quick brown fox jumps over the lazy brown dog.
+
+.if ${SENTENCE:tW:[#]} != 1
+. error
+.endif
+.if ${SENTENCE:tw:[#]} != 10
+. error
+.endif
+
+# Protect against accidental freeing of the variable value.
+.if ${SENTENCE} != "The quick brown fox jumps over the lazy brown dog."
+. error
+.endif
all:
@:;
diff --git a/unit-tests/varmod-to-separator.exp b/unit-tests/varmod-to-separator.exp
index a3c323ac123a..44c9f0973ed9 100644
--- a/unit-tests/varmod-to-separator.exp
+++ b/unit-tests/varmod-to-separator.exp
@@ -1,8 +1,6 @@
make: "varmod-to-separator.mk" line 107: Invalid character number: 400:tu}
-
make: "varmod-to-separator.mk" line 107: Malformed conditional (${WORDS:[1..3]:ts\400:tu})
make: "varmod-to-separator.mk" line 121: Invalid character number: 100:tu}
-
make: "varmod-to-separator.mk" line 121: Malformed conditional (${WORDS:[1..3]:ts\x100:tu})
make: Bad modifier `:ts\-300' for WORDS
make: "varmod-to-separator.mk" line 128: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
diff --git a/unit-tests/varmod.exp b/unit-tests/varmod.exp
index a80979e1410d..e36c4ded9b47 100644
--- a/unit-tests/varmod.exp
+++ b/unit-tests/varmod.exp
@@ -2,7 +2,7 @@ make: "varmod.mk" line 42: To escape a dollar, use \$, not $$, at "$$:L} != """
make: "varmod.mk" line 42: Invalid variable name ':', at "$:L} != """
make: "varmod.mk" line 47: Dollar followed by nothing
make: "varmod.mk" line 56: Missing delimiter ':' after modifier "P"
-make: "varmod.mk" line 57: Unknown directive "error"
+make: "varmod.mk" line 57: Missing argument for ".error"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/varmod.mk b/unit-tests/varmod.mk
index b496bdd206a2..21ddf9103251 100644
--- a/unit-tests/varmod.mk
+++ b/unit-tests/varmod.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod.mk,v 1.4 2020/11/02 17:30:22 rillig Exp $
+# $NetBSD: varmod.mk,v 1.5 2020/12/19 22:33:11 rillig Exp $
#
# Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback.
@@ -51,7 +51,7 @@ DOLLAR2= ${:U\$}
# The variable modifier :P does not fall back to the SysV modifier.
# Therefore the modifier :P=RE generates a parse error.
# XXX: The .error should not be reached since the variable expression is
-# malformed.
+# malformed, and this error should be propagated up to Cond_EvalLine.
VAR= STOP
.if ${VAR:P=RE} != "STORE"
. error
diff --git a/unit-tests/varname-dot-makeflags.exp b/unit-tests/varname-dot-makeflags.exp
new file mode 100644
index 000000000000..dbf96469f86b
--- /dev/null
+++ b/unit-tests/varname-dot-makeflags.exp
@@ -0,0 +1,3 @@
+echo "$MAKEFLAGS"
+ -r -k -d 00000 -D VARNAME WITH SPACES
+exit status 0
diff --git a/unit-tests/varname-dot-makeflags.mk b/unit-tests/varname-dot-makeflags.mk
new file mode 100644
index 000000000000..10d1903022cb
--- /dev/null
+++ b/unit-tests/varname-dot-makeflags.mk
@@ -0,0 +1,15 @@
+# $NetBSD: varname-dot-makeflags.mk,v 1.1 2020/12/01 20:37:30 rillig Exp $
+#
+# Tests for the special .MAKEFLAGS variable, which collects almost all
+# command line arguments and passes them on to any child processes via
+# the environment variable MAKEFLAGS (without leading '.').
+
+# When options are parsed, the option and its argument are appended as
+# separate words to .MAKEFLAGS. Special characters in the option argument
+# are not quoted though. It seems to have not been necessary at least from
+# 1993 until 2020.
+.MAKEFLAGS: -d00000 -D"VARNAME WITH SPACES"
+
+all:
+ echo "$$MAKEFLAGS"
+ @:;
diff --git a/unit-tests/varname-dot-shell.exp b/unit-tests/varname-dot-shell.exp
index f219515444cf..46a1b2127c98 100755
--- a/unit-tests/varname-dot-shell.exp
+++ b/unit-tests/varname-dot-shell.exp
@@ -1,6 +1,6 @@
ParseReadLine (10): 'ORIG_SHELL:= ${.SHELL}'
Global:ORIG_SHELL =
-Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_KEEP_DOLLAR
+Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF
Global:delete .SHELL (not found)
Command:.SHELL = (details omitted)
Global:ORIG_SHELL = (details omitted)
diff --git a/unit-tests/varname-make_print_var_on_error-jobs.exp b/unit-tests/varname-make_print_var_on_error-jobs.exp
index 057c09dcbcf6..81bea0e99ae9 100644
--- a/unit-tests/varname-make_print_var_on_error-jobs.exp
+++ b/unit-tests/varname-make_print_var_on_error-jobs.exp
@@ -1,7 +1,8 @@
-fail
+echo fail all; false 'all' '${.TARGET}' '$${.TARGET}'
+fail all
*** [all] Error code 1
make: stopped in unit-tests
.ERROR_TARGET='all'
-.ERROR_CMD='@: command before @echo fail; false @: command after, with variable expressions expanded'
+.ERROR_CMD='@: before '${.TARGET}' '${.TARGET}' '$${.TARGET}' echo fail ${.TARGET}; false '${.TARGET}' '${.TARGET}' '$${.TARGET}' @: after '${.TARGET}' '${.TARGET}' '$${.TARGET}''
exit status 1
diff --git a/unit-tests/varname-make_print_var_on_error-jobs.mk b/unit-tests/varname-make_print_var_on_error-jobs.mk
index 7e611d092f23..d4ab4c8bb711 100644
--- a/unit-tests/varname-make_print_var_on_error-jobs.mk
+++ b/unit-tests/varname-make_print_var_on_error-jobs.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varname-make_print_var_on_error-jobs.mk,v 1.1 2020/10/23 06:18:23 rillig Exp $
+# $NetBSD: varname-make_print_var_on_error-jobs.mk,v 1.2 2020/12/13 19:08:20 rillig Exp $
#
# Tests for the special MAKE_PRINT_VAR_ON_ERROR variable, which prints the
# values of selected variables on error.
@@ -9,6 +9,13 @@
# The commands in .ERROR_CMD are space-separated. Since each command usually
# contains spaces as well, this value is only intended as a first hint to what
# happened. For more details, use the debug options -de, -dj, -dl, -dn, -dx.
+#
+# See also:
+# compat-error.mk
+
+# XXX: As of 2020-12-13, PrintOnError calls Var_Subst with VAR_GLOBAL, which
+# does not expand the node-local variables like .TARGET. This results in the
+# double '${.TARGET}' in the output.
# As of 2020-10-23, .ERROR_CMD only works in parallel mode.
.MAKEFLAGS: -j1
@@ -16,6 +23,6 @@
MAKE_PRINT_VAR_ON_ERROR= .ERROR_TARGET .ERROR_CMD
all:
- @: command before
- @echo fail; false
- @: command after${:U, with variable expressions expanded}
+ @: before '${.TARGET}' '$${.TARGET}' '$$$${.TARGET}'
+ echo fail ${.TARGET}; false '${.TARGET}' '$${.TARGET}' '$$$${.TARGET}'
+ @: after '${.TARGET}' '$${.TARGET}' '$$$${.TARGET}'
diff --git a/unit-tests/varname-make_print_var_on_error.exp b/unit-tests/varname-make_print_var_on_error.exp
index e2f6a429a9fc..f23deb3568d6 100644
--- a/unit-tests/varname-make_print_var_on_error.exp
+++ b/unit-tests/varname-make_print_var_on_error.exp
@@ -1,4 +1,5 @@
-fail
+echo fail all; false 'all' '${.TARGET}' '$${.TARGET}'
+fail all
*** Error code 1 (continuing)
Stop.
diff --git a/unit-tests/varname-make_print_var_on_error.mk b/unit-tests/varname-make_print_var_on_error.mk
index 9ea78cb2cb4a..3c498febc386 100644
--- a/unit-tests/varname-make_print_var_on_error.mk
+++ b/unit-tests/varname-make_print_var_on_error.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varname-make_print_var_on_error.mk,v 1.4 2020/10/23 06:18:23 rillig Exp $
+# $NetBSD: varname-make_print_var_on_error.mk,v 1.5 2020/12/13 19:08:20 rillig Exp $
#
# Tests for the special MAKE_PRINT_VAR_ON_ERROR variable, which prints the
# values of selected variables on error.
@@ -7,10 +7,17 @@
# since at the point where it is filled in PrintOnError, the first command in
# gn->commands has been set to NULL already. This leaves .ERROR_CMD an empty
# list.
+#
+# See also:
+# compat-error.mk
+
+# XXX: As of 2020-12-13, PrintOnError calls Var_Subst with VAR_GLOBAL, which
+# does not expand the node-local variables like .TARGET. This results in the
+# double '${.TARGET}' in the output.
MAKE_PRINT_VAR_ON_ERROR= .ERROR_TARGET .ERROR_CMD
all:
- @: command before
- @echo fail; false
- @: command after
+ @: before '${.TARGET}' '$${.TARGET}' '$$$${.TARGET}'
+ echo fail ${.TARGET}; false '${.TARGET}' '$${.TARGET}' '$$$${.TARGET}'
+ @: after '${.TARGET}' '$${.TARGET}' '$$$${.TARGET}'
diff --git a/unit-tests/varname-makeflags.mk b/unit-tests/varname-makeflags.mk
index b2e5f68b4e08..3b4fd91c3f57 100644
--- a/unit-tests/varname-makeflags.mk
+++ b/unit-tests/varname-makeflags.mk
@@ -1,8 +1,26 @@
-# $NetBSD: varname-makeflags.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-makeflags.mk,v 1.3 2020/12/01 20:37:30 rillig Exp $
#
-# Tests for the special MAKEFLAGS variable.
+# Tests for the special MAKEFLAGS variable, which is basically just a normal
+# environment variable. It is closely related to .MAKEFLAGS but captures the
+# state of .MAKEFLAGS at the very beginning of make, before any makefiles are
+# read.
# TODO: Implementation
+.MAKEFLAGS: -d0
+
+# The unit tests are run with an almost empty environment. In particular,
+# the variable MAKEFLAGS is not set. The '.MAKEFLAGS:' above also doesn't
+# influence the environment variable MAKEFLAGS, therefore it is still
+# undefined at this point.
+.if ${MAKEFLAGS:Uundefined} != "undefined"
+. error
+.endif
+
+# The special variable .MAKEFLAGS is influenced though.
+# See varname-dot-makeflags.mk for more details.
+.if ${.MAKEFLAGS} != " -r -k -d 0"
+. error
+.endif
+
all:
- @:;
diff --git a/unit-tests/varparse-dynamic.mk b/unit-tests/varparse-dynamic.mk
index 228eb17475b0..29051d31eeea 100644
--- a/unit-tests/varparse-dynamic.mk
+++ b/unit-tests/varparse-dynamic.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varparse-dynamic.mk,v 1.2 2020/09/13 21:00:34 rillig Exp $
+# $NetBSD: varparse-dynamic.mk,v 1.3 2020/11/21 15:48:05 rillig Exp $
# Before 2020-07-27, there was an off-by-one error in Var_Parse that skipped
# the last character in the variable name.
@@ -21,5 +21,15 @@
. error
.endif
+# If a dynamic variable is expanded in a non-local context, the expression
+# based on this variable is not expanded. But there may be nested variable
+# expressions in the modifiers, and these are kept unexpanded as well.
+.if ${.TARGET:M${:Ufallback}} != "\${.TARGET:M\${:Ufallback}}"
+. error
+.endif
+.if ${.TARGET:M${UNDEF}} != "\${.TARGET:M\${UNDEF}}"
+. error
+.endif
+
all:
@:
diff --git a/unit-tests/varparse-errors.exp b/unit-tests/varparse-errors.exp
index 39a9383953dd..50a0766c7d70 100644
--- a/unit-tests/varparse-errors.exp
+++ b/unit-tests/varparse-errors.exp
@@ -1 +1,5 @@
-exit status 0
+make: "varparse-errors.mk" line 38: Unknown modifier 'Z'
+make: "varparse-errors.mk" line 46: Unknown modifier 'Z'
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/varparse-errors.mk b/unit-tests/varparse-errors.mk
index 42f5b65a728e..113c7a292a79 100644
--- a/unit-tests/varparse-errors.mk
+++ b/unit-tests/varparse-errors.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varparse-errors.mk,v 1.1 2020/11/08 16:44:47 rillig Exp $
+# $NetBSD: varparse-errors.mk,v 1.3 2020/12/20 19:47:34 rillig Exp $
# Tests for parsing and evaluating all kinds of variable expressions.
#
@@ -32,4 +32,20 @@ ERR_EVAL= An evaluation error ${:Uvalue:C,.,\3,}.
. error
.endif
+# As of 2020-12-01, errors in the variable name are silently ignored.
+# Since var.c 1.754 from 2020-12-20, unknown modifiers at parse time result
+# in an error message and a non-zero exit status.
+VAR.${:U:Z}= unknown modifier in the variable name
+.if ${VAR.} != "unknown modifier in the variable name"
+. error
+.endif
+
+# As of 2020-12-01, errors in the variable name are silently ignored.
+# Since var.c 1.754 from 2020-12-20, unknown modifiers at parse time result
+# in an error message and a non-zero exit status.
+VAR.${:U:Z}post= unknown modifier with text in the variable name
+.if ${VAR.post} != "unknown modifier with text in the variable name"
+. error
+.endif
+
all:
diff --git a/util.c b/util.c
index a5d867d9df91..6143e4c462d2 100644
--- a/util.c
+++ b/util.c
@@ -1,9 +1,9 @@
-/* $NetBSD: util.c,v 1.68 2020/11/16 18:29:49 rillig Exp $ */
+/* $NetBSD: util.c,v 1.73 2020/12/30 10:03:16 rillig Exp $ */
/*
* Missing stuff from OS's
*
- * $Id: util.c,v 1.41 2020/11/18 03:58:32 sjg Exp $
+ * $Id: util.c,v 1.45 2021/01/01 22:55:09 sjg Exp $
*/
#include <sys/param.h>
@@ -13,7 +13,7 @@
#include "make.h"
-MAKE_RCSID("$NetBSD: util.c,v 1.68 2020/11/16 18:29:49 rillig Exp $");
+MAKE_RCSID("$NetBSD: util.c,v 1.73 2020/12/30 10:03:16 rillig Exp $");
#if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR)
extern int errno, sys_nerr;
@@ -153,7 +153,8 @@ main(int argc, char *argv[])
#if defined(__hpux__) || defined(__hpux)
-/* strrcpy():
+/*
+ * strrcpy():
* Like strcpy, going backwards and returning the new pointer
*/
static char *
@@ -161,7 +162,7 @@ strrcpy(char *ptr, char *str)
{
int len = strlen(str);
- while (len)
+ while (len != 0)
*--ptr = str[--len];
return ptr;
@@ -170,38 +171,38 @@ strrcpy(char *ptr, char *str)
char *sys_siglist[] = {
"Signal 0",
- "Hangup", /* SIGHUP */
- "Interrupt", /* SIGINT */
- "Quit", /* SIGQUIT */
- "Illegal instruction", /* SIGILL */
- "Trace/BPT trap", /* SIGTRAP */
- "IOT trap", /* SIGIOT */
- "EMT trap", /* SIGEMT */
- "Floating point exception", /* SIGFPE */
- "Killed", /* SIGKILL */
- "Bus error", /* SIGBUS */
- "Segmentation fault", /* SIGSEGV */
- "Bad system call", /* SIGSYS */
- "Broken pipe", /* SIGPIPE */
- "Alarm clock", /* SIGALRM */
- "Terminated", /* SIGTERM */
- "User defined signal 1", /* SIGUSR1 */
- "User defined signal 2", /* SIGUSR2 */
- "Child exited", /* SIGCLD */
- "Power-fail restart", /* SIGPWR */
- "Virtual timer expired", /* SIGVTALRM */
- "Profiling timer expired", /* SIGPROF */
- "I/O possible", /* SIGIO */
- "Window size changes", /* SIGWINDOW */
- "Stopped (signal)", /* SIGSTOP */
- "Stopped", /* SIGTSTP */
- "Continued", /* SIGCONT */
- "Stopped (tty input)", /* SIGTTIN */
- "Stopped (tty output)", /* SIGTTOU */
- "Urgent I/O condition", /* SIGURG */
- "Remote lock lost (NFS)", /* SIGLOST */
- "Signal 31", /* reserved */
- "DIL signal" /* SIGDIL */
+ "Hangup", /* SIGHUP */
+ "Interrupt", /* SIGINT */
+ "Quit", /* SIGQUIT */
+ "Illegal instruction", /* SIGILL */
+ "Trace/BPT trap", /* SIGTRAP */
+ "IOT trap", /* SIGIOT */
+ "EMT trap", /* SIGEMT */
+ "Floating point exception", /* SIGFPE */
+ "Killed", /* SIGKILL */
+ "Bus error", /* SIGBUS */
+ "Segmentation fault", /* SIGSEGV */
+ "Bad system call", /* SIGSYS */
+ "Broken pipe", /* SIGPIPE */
+ "Alarm clock", /* SIGALRM */
+ "Terminated", /* SIGTERM */
+ "User defined signal 1", /* SIGUSR1 */
+ "User defined signal 2", /* SIGUSR2 */
+ "Child exited", /* SIGCLD */
+ "Power-fail restart", /* SIGPWR */
+ "Virtual timer expired", /* SIGVTALRM */
+ "Profiling timer expired", /* SIGPROF */
+ "I/O possible", /* SIGIO */
+ "Window size changes", /* SIGWINDOW */
+ "Stopped (signal)", /* SIGSTOP */
+ "Stopped", /* SIGTSTP */
+ "Continued", /* SIGCONT */
+ "Stopped (tty input)", /* SIGTTIN */
+ "Stopped (tty output)", /* SIGTTOU */
+ "Urgent I/O condition", /* SIGURG */
+ "Remote lock lost (NFS)", /* SIGLOST */
+ "Signal 31", /* reserved */
+ "DIL signal" /* SIGDIL */
};
#endif /* __hpux__ || __hpux */
@@ -343,16 +344,16 @@ getcwd(path, sz)
SignalProc
bmake_signal(int s, SignalProc a)
{
- struct sigaction sa, osa;
+ struct sigaction sa, osa;
- sa.sa_handler = a;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
+ sa.sa_handler = a;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
- if (sigaction(s, &sa, &osa) == -1)
- return SIG_ERR;
- else
- return osa.sa_handler;
+ if (sigaction(s, &sa, &osa) == -1)
+ return SIG_ERR;
+ else
+ return osa.sa_handler;
}
#if !defined(HAVE_VSNPRINTF) || !defined(HAVE_VASPRINTF)
diff --git a/var.c b/var.c
index 80b8d95d82ef..cecd217b47be 100644
--- a/var.c
+++ b/var.c
@@ -1,4 +1,4 @@
-/* $NetBSD: var.c,v 1.689 2020/11/17 20:11:02 rillig Exp $ */
+/* $NetBSD: var.c,v 1.781 2021/01/10 23:59:53 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -98,7 +98,8 @@
*
* Var_Delete Delete a variable.
*
- * Var_ExportVars Export some or even all variables to the environment
+ * Var_ReexportVars
+ * Export some or even all variables to the environment
* of this process and its child processes.
*
* Var_Export Export the variable to the environment of this process
@@ -138,15 +139,122 @@
#include "metachar.h"
/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: var.c,v 1.689 2020/11/17 20:11:02 rillig Exp $");
+MAKE_RCSID("$NetBSD: var.c,v 1.781 2021/01/10 23:59:53 rillig Exp $");
-#define VAR_DEBUG1(fmt, arg1) DEBUG1(VAR, fmt, arg1)
-#define VAR_DEBUG2(fmt, arg1, arg2) DEBUG2(VAR, fmt, arg1, arg2)
-#define VAR_DEBUG3(fmt, arg1, arg2, arg3) DEBUG3(VAR, fmt, arg1, arg2, arg3)
-#define VAR_DEBUG4(fmt, arg1, arg2, arg3, arg4) DEBUG4(VAR, fmt, arg1, arg2, arg3, arg4)
+typedef enum VarFlags {
+ VAR_NONE = 0,
+
+ /*
+ * The variable's value is currently being used by Var_Parse or
+ * Var_Subst. This marker is used to avoid endless recursion.
+ */
+ VAR_IN_USE = 0x01,
+
+ /*
+ * The variable comes from the environment.
+ * These variables are not registered in any GNode, therefore they
+ * must be freed as soon as they are not used anymore.
+ */
+ VAR_FROM_ENV = 0x02,
+
+ /*
+ * The variable is exported to the environment, to be used by child
+ * processes.
+ */
+ VAR_EXPORTED = 0x10,
+
+ /*
+ * At the point where this variable was exported, it contained an
+ * unresolved reference to another variable. Before any child
+ * process is started, it needs to be exported again, in the hope
+ * that the referenced variable can then be resolved.
+ */
+ VAR_REEXPORT = 0x20,
+
+ /* The variable came from the command line. */
+ VAR_FROM_CMD = 0x40,
+
+ /*
+ * The variable value cannot be changed anymore, and the variable
+ * cannot be deleted. Any attempts to do so are silently ignored,
+ * they are logged with -dv though.
+ */
+ VAR_READONLY = 0x80
+} VarFlags;
+
+/*
+ * Variables are defined using one of the VAR=value assignments. Their
+ * value can be queried by expressions such as $V, ${VAR}, or with modifiers
+ * such as ${VAR:S,from,to,g:Q}.
+ *
+ * There are 3 kinds of variables: context variables, environment variables,
+ * undefined variables.
+ *
+ * Context variables are stored in a GNode.context. The only way to undefine
+ * a context variable is using the .undef directive. In particular, it must
+ * not be possible to undefine a variable during the evaluation of an
+ * expression, or Var.name might point nowhere.
+ *
+ * Environment variables are temporary. They are returned by VarFind, and
+ * after using them, they must be freed using VarFreeEnv.
+ *
+ * Undefined variables occur during evaluation of variable expressions such
+ * as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers.
+ */
+typedef struct Var {
+ /*
+ * The name of the variable, once set, doesn't change anymore.
+ * For context variables, it aliases the corresponding HashEntry name.
+ * For environment and undefined variables, it is allocated.
+ */
+ FStr name;
+
+ /* The unexpanded value of the variable. */
+ Buffer val;
+ /* Miscellaneous status flags. */
+ VarFlags flags;
+} Var;
+
+/*
+ * Exporting vars is expensive so skip it if we can
+ */
+typedef enum VarExportedMode {
+ VAR_EXPORTED_NONE,
+ VAR_EXPORTED_SOME,
+ VAR_EXPORTED_ALL
+} VarExportedMode;
-ENUM_FLAGS_RTTI_3(VarEvalFlags,
- VARE_UNDEFERR, VARE_WANTRES, VARE_KEEP_DOLLAR);
+typedef enum UnexportWhat {
+ UNEXPORT_NAMED,
+ UNEXPORT_ALL,
+ UNEXPORT_ENV
+} UnexportWhat;
+
+/* Flags for pattern matching in the :S and :C modifiers */
+typedef enum VarPatternFlags {
+ VARP_NONE = 0,
+ /* Replace as often as possible ('g') */
+ VARP_SUB_GLOBAL = 1 << 0,
+ /* Replace only once ('1') */
+ VARP_SUB_ONE = 1 << 1,
+ /* Match at start of word ('^') */
+ VARP_ANCHOR_START = 1 << 2,
+ /* Match at end of word ('$') */
+ VARP_ANCHOR_END = 1 << 3
+} VarPatternFlags;
+
+/* SepBuf is a string being built from words, interleaved with separators. */
+typedef struct SepBuf {
+ Buffer buf;
+ Boolean needSep;
+ /* Usually ' ', but see the ':ts' modifier. */
+ char sep;
+} SepBuf;
+
+
+ENUM_FLAGS_RTTI_4(VarEvalFlags,
+ VARE_UNDEFERR, VARE_WANTRES, VARE_KEEP_DOLLAR,
+ VARE_KEEP_UNDEF);
/*
* This lets us tell if we have replaced the original environ
@@ -154,15 +262,19 @@ ENUM_FLAGS_RTTI_3(VarEvalFlags,
*/
char **savedEnv = NULL;
-/* Special return value for Var_Parse, indicating a parse error. It may be
+/*
+ * Special return value for Var_Parse, indicating a parse error. It may be
* caused by an undefined variable, a syntax error in a modifier or
- * something entirely different. */
+ * something entirely different.
+ */
char var_Error[] = "";
-/* Special return value for Var_Parse, indicating an undefined variable in
+/*
+ * Special return value for Var_Parse, indicating an undefined variable in
* a case where VARE_UNDEFERR is not set. This undefined variable is
* typically a dynamic variable such as ${.TARGET}, whose expansion needs to
- * be deferred until it is defined in an actual target. */
+ * be deferred until it is defined in an actual target.
+ */
static char varUndefined[] = "";
/*
@@ -195,165 +307,78 @@ GNode *VAR_INTERNAL; /* variables from make itself */
GNode *VAR_GLOBAL; /* variables from the makefile */
GNode *VAR_CMDLINE; /* variables defined on the command-line */
-typedef enum VarFlags {
-
- /* The variable's value is currently being used by Var_Parse or Var_Subst.
- * This marker is used to avoid endless recursion. */
- VAR_IN_USE = 0x01,
-
- /* The variable comes from the environment.
- * These variables are not registered in any GNode, therefore they must
- * be freed as soon as they are not used anymore. */
- VAR_FROM_ENV = 0x02,
-
- /* The variable is exported to the environment, to be used by child
- * processes. */
- VAR_EXPORTED = 0x10,
-
- /* At the point where this variable was exported, it contained an
- * unresolved reference to another variable. Before any child process is
- * started, it needs to be exported again, in the hope that the referenced
- * variable can then be resolved. */
- VAR_REEXPORT = 0x20,
-
- /* The variable came from the command line. */
- VAR_FROM_CMD = 0x40,
-
- /* The variable value cannot be changed anymore, and the variable cannot
- * be deleted. Any attempts to do so are ignored. */
- VAR_READONLY = 0x80
-} VarFlags;
-
ENUM_FLAGS_RTTI_6(VarFlags,
VAR_IN_USE, VAR_FROM_ENV,
VAR_EXPORTED, VAR_REEXPORT, VAR_FROM_CMD, VAR_READONLY);
-/* Variables are defined using one of the VAR=value assignments. Their
- * value can be queried by expressions such as $V, ${VAR}, or with modifiers
- * such as ${VAR:S,from,to,g:Q}.
- *
- * There are 3 kinds of variables: context variables, environment variables,
- * undefined variables.
- *
- * Context variables are stored in a GNode.context. The only way to undefine
- * a context variable is using the .undef directive. In particular, it must
- * not be possible to undefine a variable during the evaluation of an
- * expression, or Var.name might point nowhere.
- *
- * Environment variables are temporary. They are returned by VarFind, and
- * after using them, they must be freed using VarFreeEnv.
- *
- * Undefined variables occur during evaluation of variable expressions such
- * as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers.
- */
-typedef struct Var {
- /* The name of the variable, once set, doesn't change anymore.
- * For context variables, it aliases the corresponding HashEntry name.
- * For environment and undefined variables, it is allocated. */
- const char *name;
- void *name_freeIt;
-
- Buffer val; /* its value */
- VarFlags flags; /* miscellaneous status flags */
-} Var;
-
-/*
- * Exporting vars is expensive so skip it if we can
- */
-typedef enum VarExportedMode {
- VAR_EXPORTED_NONE,
- VAR_EXPORTED_SOME,
- VAR_EXPORTED_ALL
-} VarExportedMode;
-
static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE;
-typedef enum VarExportFlags {
- VAR_EXPORT_NORMAL = 0,
- /*
- * We pass this to Var_Export when doing the initial export
- * or after updating an exported var.
- */
- VAR_EXPORT_PARENT = 0x01,
- /*
- * We pass this to Var_Export1 to tell it to leave the value alone.
- */
- VAR_EXPORT_LITERAL = 0x02
-} VarExportFlags;
-
-/* Flags for pattern matching in the :S and :C modifiers */
-typedef enum VarPatternFlags {
- VARP_SUB_GLOBAL = 0x01, /* Replace as often as possible ('g') */
- VARP_SUB_ONE = 0x02, /* Replace only once ('1') */
- VARP_ANCHOR_START = 0x04, /* Match at start of word ('^') */
- VARP_ANCHOR_END = 0x08 /* Match at end of word ('$') */
-} VarPatternFlags;
static Var *
-VarNew(const char *name, void *name_freeIt, const char *value, VarFlags flags)
+VarNew(FStr name, const char *value, VarFlags flags)
{
- size_t value_len = strlen(value);
- Var *var = bmake_malloc(sizeof *var);
- var->name = name;
- var->name_freeIt = name_freeIt;
- Buf_InitSize(&var->val, value_len + 1);
- Buf_AddBytes(&var->val, value, value_len);
- var->flags = flags;
- return var;
+ size_t value_len = strlen(value);
+ Var *var = bmake_malloc(sizeof *var);
+ var->name = name;
+ Buf_InitSize(&var->val, value_len + 1);
+ Buf_AddBytes(&var->val, value, value_len);
+ var->flags = flags;
+ return var;
}
static const char *
CanonicalVarname(const char *name)
{
- if (*name == '.' && ch_isupper(name[1])) {
- switch (name[1]) {
- case 'A':
- if (strcmp(name, ".ALLSRC") == 0)
- name = ALLSRC;
- if (strcmp(name, ".ARCHIVE") == 0)
- name = ARCHIVE;
- break;
- case 'I':
- if (strcmp(name, ".IMPSRC") == 0)
- name = IMPSRC;
- break;
- case 'M':
- if (strcmp(name, ".MEMBER") == 0)
- name = MEMBER;
- break;
- case 'O':
- if (strcmp(name, ".OODATE") == 0)
- name = OODATE;
- break;
- case 'P':
- if (strcmp(name, ".PREFIX") == 0)
- name = PREFIX;
- break;
- case 'S':
- if (strcmp(name, ".SHELL") == 0) {
- if (!shellPath)
- Shell_Init();
- }
- break;
- case 'T':
- if (strcmp(name, ".TARGET") == 0)
- name = TARGET;
- break;
+ if (*name == '.' && ch_isupper(name[1])) {
+ switch (name[1]) {
+ case 'A':
+ if (strcmp(name, ".ALLSRC") == 0)
+ name = ALLSRC;
+ if (strcmp(name, ".ARCHIVE") == 0)
+ name = ARCHIVE;
+ break;
+ case 'I':
+ if (strcmp(name, ".IMPSRC") == 0)
+ name = IMPSRC;
+ break;
+ case 'M':
+ if (strcmp(name, ".MEMBER") == 0)
+ name = MEMBER;
+ break;
+ case 'O':
+ if (strcmp(name, ".OODATE") == 0)
+ name = OODATE;
+ break;
+ case 'P':
+ if (strcmp(name, ".PREFIX") == 0)
+ name = PREFIX;
+ break;
+ case 'S':
+ if (strcmp(name, ".SHELL") == 0) {
+ if (shellPath == NULL)
+ Shell_Init();
+ }
+ break;
+ case 'T':
+ if (strcmp(name, ".TARGET") == 0)
+ name = TARGET;
+ break;
+ }
}
- }
- /* GNU make has an additional alias $^ == ${.ALLSRC}. */
+ /* GNU make has an additional alias $^ == ${.ALLSRC}. */
- return name;
+ return name;
}
static Var *
GNode_FindVar(GNode *ctxt, const char *varname, unsigned int hash)
{
- return HashTable_FindValueHash(&ctxt->context, varname, hash);
+ return HashTable_FindValueHash(&ctxt->vars, varname, hash);
}
-/* Find the variable in the context, and maybe in other contexts as well.
+/*
+ * Find the variable in the context, and maybe in other contexts as well.
*
* Input:
* name name to find, is not expanded any further
@@ -368,58 +393,62 @@ GNode_FindVar(GNode *ctxt, const char *varname, unsigned int hash)
static Var *
VarFind(const char *name, GNode *ctxt, Boolean elsewhere)
{
- Var *var;
- unsigned int nameHash;
-
- /*
- * If the variable name begins with a '.', it could very well be one of
- * the local ones. We check the name against all the local variables
- * and substitute the short version in for 'name' if it matches one of
- * them.
- */
- name = CanonicalVarname(name);
- nameHash = Hash_Hash(name);
-
- /* First look for the variable in the given context. */
- var = GNode_FindVar(ctxt, name, nameHash);
- if (!elsewhere)
- return var;
+ Var *var;
+ unsigned int nameHash;
- /* The variable was not found in the given context. Now look for it in
- * the other contexts as well. */
- if (var == NULL && ctxt != VAR_CMDLINE)
- var = GNode_FindVar(VAR_CMDLINE, name, nameHash);
+ /*
+ * If the variable name begins with a '.', it could very well be
+ * one of the local ones. We check the name against all the local
+ * variables and substitute the short version in for 'name' if it
+ * matches one of them.
+ */
+ name = CanonicalVarname(name);
+ nameHash = Hash_Hash(name);
- if (!opts.checkEnvFirst && var == NULL && ctxt != VAR_GLOBAL) {
- var = GNode_FindVar(VAR_GLOBAL, name, nameHash);
- if (var == NULL && ctxt != VAR_INTERNAL) {
- /* VAR_INTERNAL is subordinate to VAR_GLOBAL */
- var = GNode_FindVar(VAR_INTERNAL, name, nameHash);
+ /* First look for the variable in the given context. */
+ var = GNode_FindVar(ctxt, name, nameHash);
+ if (!elsewhere)
+ return var;
+
+ /*
+ * The variable was not found in the given context.
+ * Now look for it in the other contexts as well.
+ */
+ if (var == NULL && ctxt != VAR_CMDLINE)
+ var = GNode_FindVar(VAR_CMDLINE, name, nameHash);
+
+ if (!opts.checkEnvFirst && var == NULL && ctxt != VAR_GLOBAL) {
+ var = GNode_FindVar(VAR_GLOBAL, name, nameHash);
+ if (var == NULL && ctxt != VAR_INTERNAL) {
+ /* VAR_INTERNAL is subordinate to VAR_GLOBAL */
+ var = GNode_FindVar(VAR_INTERNAL, name, nameHash);
+ }
}
- }
- if (var == NULL) {
- char *env;
+ if (var == NULL) {
+ char *env;
- if ((env = getenv(name)) != NULL) {
- char *varname = bmake_strdup(name);
- return VarNew(varname, varname, env, VAR_FROM_ENV);
- }
+ if ((env = getenv(name)) != NULL) {
+ char *varname = bmake_strdup(name);
+ return VarNew(FStr_InitOwn(varname), env, VAR_FROM_ENV);
+ }
- if (opts.checkEnvFirst && ctxt != VAR_GLOBAL) {
- var = GNode_FindVar(VAR_GLOBAL, name, nameHash);
- if (var == NULL && ctxt != VAR_INTERNAL)
- var = GNode_FindVar(VAR_INTERNAL, name, nameHash);
- return var;
- }
+ if (opts.checkEnvFirst && ctxt != VAR_GLOBAL) {
+ var = GNode_FindVar(VAR_GLOBAL, name, nameHash);
+ if (var == NULL && ctxt != VAR_INTERNAL)
+ var = GNode_FindVar(VAR_INTERNAL, name,
+ nameHash);
+ return var;
+ }
- return NULL;
- }
+ return NULL;
+ }
- return var;
+ return var;
}
-/* If the variable is an environment variable, free it.
+/*
+ * If the variable is an environment variable, free it.
*
* Input:
* v the variable
@@ -431,128 +460,163 @@ VarFind(const char *name, GNode *ctxt, Boolean elsewhere)
static Boolean
VarFreeEnv(Var *v, Boolean freeValue)
{
- if (!(v->flags & VAR_FROM_ENV))
- return FALSE;
+ if (!(v->flags & VAR_FROM_ENV))
+ return FALSE;
- free(v->name_freeIt);
- Buf_Destroy(&v->val, freeValue);
- free(v);
- return TRUE;
+ FStr_Done(&v->name);
+ Buf_Destroy(&v->val, freeValue);
+ free(v);
+ return TRUE;
}
-/* Add a new variable of the given name and value to the given context.
- * The name and val arguments are duplicated so they may safely be freed. */
+/*
+ * Add a new variable of the given name and value to the given context.
+ * The name and val arguments are duplicated so they may safely be freed.
+ */
static void
VarAdd(const char *name, const char *val, GNode *ctxt, VarSetFlags flags)
{
- HashEntry *he = HashTable_CreateEntry(&ctxt->context, name, NULL);
- Var *v = VarNew(he->key /* aliased */, NULL, val,
- flags & VAR_SET_READONLY ? VAR_READONLY : 0);
- HashEntry_Set(he, v);
- if (!(ctxt->flags & INTERNAL)) {
- VAR_DEBUG3("%s:%s = %s\n", ctxt->name, name, val);
- }
+ HashEntry *he = HashTable_CreateEntry(&ctxt->vars, name, NULL);
+ Var *v = VarNew(FStr_InitRefer(/* aliased to */ he->key), val,
+ flags & VAR_SET_READONLY ? VAR_READONLY : VAR_NONE);
+ HashEntry_Set(he, v);
+ if (!(ctxt->flags & INTERNAL))
+ DEBUG3(VAR, "%s:%s = %s\n", ctxt->name, name, val);
}
-/* Remove a variable from a context, freeing all related memory as well.
- * The variable name is expanded once. */
+/*
+ * Remove a variable from a context, freeing all related memory as well.
+ * The variable name is kept as-is, it is not expanded.
+ */
void
-Var_Delete(const char *name, GNode *ctxt)
+Var_DeleteVar(const char *varname, GNode *ctxt)
{
- char *name_freeIt = NULL;
- HashEntry *he;
+ HashEntry *he = HashTable_FindEntry(&ctxt->vars, varname);
+ Var *v;
- if (strchr(name, '$') != NULL) {
- (void)Var_Subst(name, VAR_GLOBAL, VARE_WANTRES, &name_freeIt);
- /* TODO: handle errors */
- name = name_freeIt;
- }
- he = HashTable_FindEntry(&ctxt->context, name);
- VAR_DEBUG3("%s:delete %s%s\n",
- ctxt->name, name, he != NULL ? "" : " (not found)");
- free(name_freeIt);
-
- if (he != NULL) {
- Var *v = HashEntry_Get(he);
+ if (he == NULL) {
+ DEBUG2(VAR, "%s:delete %s (not found)\n", ctxt->name, varname);
+ return;
+ }
+
+ DEBUG2(VAR, "%s:delete %s\n", ctxt->name, varname);
+ v = HashEntry_Get(he);
if (v->flags & VAR_EXPORTED)
- unsetenv(v->name);
- if (strcmp(v->name, MAKE_EXPORTED) == 0)
- var_exportedVars = VAR_EXPORTED_NONE;
- assert(v->name_freeIt == NULL);
- HashTable_DeleteEntry(&ctxt->context, he);
+ unsetenv(v->name.str);
+ if (strcmp(v->name.str, MAKE_EXPORTED) == 0)
+ var_exportedVars = VAR_EXPORTED_NONE;
+ assert(v->name.freeIt == NULL);
+ HashTable_DeleteEntry(&ctxt->vars, he);
Buf_Destroy(&v->val, TRUE);
free(v);
- }
}
-static Boolean
-MayExport(const char *name)
+/*
+ * Remove a variable from a context, freeing all related memory as well.
+ * The variable name is expanded once.
+ */
+void
+Var_Delete(const char *name, GNode *ctxt)
{
- if (name[0] == '.')
- return FALSE; /* skip internals */
- if (name[0] == '-')
- return FALSE; /* skip misnamed variables */
- if (name[1] == '\0') {
- /*
- * A single char.
- * If it is one of the vars that should only appear in
- * local context, skip it, else we can get Var_Subst
- * into a loop.
- */
- switch (name[0]) {
- case '@':
- case '%':
- case '*':
- case '!':
- return FALSE;
+ FStr varname = FStr_InitRefer(name);
+
+ if (strchr(varname.str, '$') != NULL) {
+ char *expanded;
+ (void)Var_Subst(varname.str, VAR_GLOBAL, VARE_WANTRES,
+ &expanded);
+ /* TODO: handle errors */
+ varname = FStr_InitOwn(expanded);
}
- }
- return TRUE;
+
+ Var_DeleteVar(varname.str, ctxt);
+ FStr_Done(&varname);
}
/*
- * Export a single variable.
- * We ignore make internal variables (those which start with '.').
- * Also we jump through some hoops to avoid calling setenv
- * more than necessary since it can leak.
- * We only manipulate flags of vars if 'parent' is set.
+ * Undefine one or more variables from the global scope.
+ * The argument is expanded exactly once and then split into words.
*/
-static Boolean
-Var_Export1(const char *name, VarExportFlags flags)
+void
+Var_Undef(const char *arg)
{
- VarExportFlags parent = flags & VAR_EXPORT_PARENT;
- Var *v;
- char *val;
+ VarParseResult vpr;
+ char *expanded;
+ Words varnames;
+ size_t i;
- if (!MayExport(name))
- return FALSE;
+ if (arg[0] == '\0') {
+ Parse_Error(PARSE_FATAL,
+ "The .undef directive requires an argument");
+ return;
+ }
- v = VarFind(name, VAR_GLOBAL, FALSE);
- if (v == NULL)
- return FALSE;
+ vpr = Var_Subst(arg, VAR_GLOBAL, VARE_WANTRES, &expanded);
+ if (vpr != VPR_OK) {
+ Parse_Error(PARSE_FATAL,
+ "Error in variable names to be undefined");
+ return;
+ }
+
+ varnames = Str_Words(expanded, FALSE);
+ if (varnames.len == 1 && varnames.words[0][0] == '\0')
+ varnames.len = 0;
+
+ for (i = 0; i < varnames.len; i++) {
+ const char *varname = varnames.words[i];
+ Var_DeleteVar(varname, VAR_GLOBAL);
+ }
+
+ Words_Free(varnames);
+ free(expanded);
+}
- if (!parent && (v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT))
- return FALSE; /* nothing to do */
+static Boolean
+MayExport(const char *name)
+{
+ if (name[0] == '.')
+ return FALSE; /* skip internals */
+ if (name[0] == '-')
+ return FALSE; /* skip misnamed variables */
+ if (name[1] == '\0') {
+ /*
+ * A single char.
+ * If it is one of the vars that should only appear in
+ * local context, skip it, else we can get Var_Subst
+ * into a loop.
+ */
+ switch (name[0]) {
+ case '@':
+ case '%':
+ case '*':
+ case '!':
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
- val = Buf_GetAll(&v->val, NULL);
- if (!(flags & VAR_EXPORT_LITERAL) && strchr(val, '$') != NULL) {
+static Boolean
+ExportVarEnv(Var *v)
+{
+ const char *name = v->name.str;
+ char *val = v->val.data;
char *expr;
- if (parent) {
- /*
- * Flag the variable as something we need to re-export.
- * No point actually exporting it now though,
- * the child process can do it at the last minute.
- */
- v->flags |= VAR_EXPORTED | VAR_REEXPORT;
- return TRUE;
+ if ((v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT))
+ return FALSE; /* nothing to do */
+
+ if (strchr(val, '$') == NULL) {
+ if (!(v->flags & VAR_EXPORTED))
+ setenv(name, val, 1);
+ return TRUE;
}
+
if (v->flags & VAR_IN_USE) {
- /*
- * We recursed while exporting in a child.
- * This isn't going to end well, just skip it.
- */
- return FALSE;
+ /*
+ * We recursed while exporting in a child.
+ * This isn't going to end well, just skip it.
+ */
+ return FALSE;
}
/* XXX: name is injected without escaping it */
@@ -562,353 +626,423 @@ Var_Export1(const char *name, VarExportFlags flags)
setenv(name, val, 1);
free(val);
free(expr);
- } else {
- if (parent)
- v->flags &= ~(unsigned)VAR_REEXPORT; /* once will do */
- if (parent || !(v->flags & VAR_EXPORTED))
- setenv(name, val, 1);
- }
-
- /*
- * This is so Var_Set knows to call Var_Export again...
- */
- if (parent) {
- v->flags |= VAR_EXPORTED;
- }
- return TRUE;
+ return TRUE;
+}
+
+static Boolean
+ExportVarPlain(Var *v)
+{
+ if (strchr(v->val.data, '$') == NULL) {
+ setenv(v->name.str, v->val.data, 1);
+ v->flags |= VAR_EXPORTED;
+ v->flags &= ~(unsigned)VAR_REEXPORT;
+ return TRUE;
+ }
+
+ /*
+ * Flag the variable as something we need to re-export.
+ * No point actually exporting it now though,
+ * the child process can do it at the last minute.
+ */
+ v->flags |= VAR_EXPORTED | VAR_REEXPORT;
+ return TRUE;
+}
+
+static Boolean
+ExportVarLiteral(Var *v)
+{
+ if ((v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT))
+ return FALSE;
+
+ if (!(v->flags & VAR_EXPORTED))
+ setenv(v->name.str, v->val.data, 1);
+
+ return TRUE;
}
/*
- * This gets called from our child processes.
+ * Export a single variable.
+ *
+ * We ignore make internal variables (those which start with '.').
+ * Also we jump through some hoops to avoid calling setenv
+ * more than necessary since it can leak.
+ * We only manipulate flags of vars if 'parent' is set.
*/
-void
-Var_ExportVars(void)
+static Boolean
+ExportVar(const char *name, VarExportMode mode)
{
- char *val;
-
- /*
- * Several make's support this sort of mechanism for tracking
- * recursion - but each uses a different name.
- * We allow the makefiles to update MAKELEVEL and ensure
- * children see a correctly incremented value.
- */
- char tmp[BUFSIZ];
- snprintf(tmp, sizeof tmp, "%d", makelevel + 1);
- setenv(MAKE_LEVEL_ENV, tmp, 1);
-
- if (var_exportedVars == VAR_EXPORTED_NONE)
- return;
-
- if (var_exportedVars == VAR_EXPORTED_ALL) {
- HashIter hi;
+ Var *v;
- /* Ouch! Exporting all variables at once is crazy... */
- HashIter_Init(&hi, &VAR_GLOBAL->context);
- while (HashIter_Next(&hi) != NULL) {
- Var *var = hi.entry->value;
- Var_Export1(var->name, VAR_EXPORT_NORMAL);
- }
- return;
- }
+ if (!MayExport(name))
+ return FALSE;
- (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", VAR_GLOBAL, VARE_WANTRES, &val);
- /* TODO: handle errors */
- if (*val) {
- Words words = Str_Words(val, FALSE);
- size_t i;
+ v = VarFind(name, VAR_GLOBAL, FALSE);
+ if (v == NULL)
+ return FALSE;
- for (i = 0; i < words.len; i++)
- Var_Export1(words.words[i], VAR_EXPORT_NORMAL);
- Words_Free(words);
- }
- free(val);
+ if (mode == VEM_ENV)
+ return ExportVarEnv(v);
+ else if (mode == VEM_PLAIN)
+ return ExportVarPlain(v);
+ else
+ return ExportVarLiteral(v);
}
/*
- * This is called when .export is seen or .MAKE.EXPORTED is modified.
- *
- * It is also called when any exported variable is modified.
- * XXX: Is it really?
- *
- * str has the format "[-env|-literal] varname...".
+ * Actually export the variables that have been marked as needing to be
+ * re-exported.
*/
void
-Var_Export(const char *str, Boolean isExport)
+Var_ReexportVars(void)
{
- VarExportFlags flags;
- char *val;
-
- if (isExport && str[0] == '\0') {
- var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */
- return;
- }
-
- if (isExport && strncmp(str, "-env", 4) == 0) {
- str += 4;
- flags = 0;
- } else if (isExport && strncmp(str, "-literal", 8) == 0) {
- str += 8;
- flags = VAR_EXPORT_LITERAL;
- } else {
- flags = VAR_EXPORT_PARENT;
- }
-
- (void)Var_Subst(str, VAR_GLOBAL, VARE_WANTRES, &val);
- /* TODO: handle errors */
- if (val[0] != '\0') {
- Words words = Str_Words(val, FALSE);
+ char *xvarnames;
+
+ /*
+ * Several make implementations support this sort of mechanism for
+ * tracking recursion - but each uses a different name.
+ * We allow the makefiles to update MAKELEVEL and ensure
+ * children see a correctly incremented value.
+ */
+ char tmp[BUFSIZ];
+ snprintf(tmp, sizeof tmp, "%d", makelevel + 1);
+ setenv(MAKE_LEVEL_ENV, tmp, 1);
+
+ if (var_exportedVars == VAR_EXPORTED_NONE)
+ return;
+
+ if (var_exportedVars == VAR_EXPORTED_ALL) {
+ HashIter hi;
+
+ /* Ouch! Exporting all variables at once is crazy... */
+ HashIter_Init(&hi, &VAR_GLOBAL->vars);
+ while (HashIter_Next(&hi) != NULL) {
+ Var *var = hi.entry->value;
+ ExportVar(var->name.str, VEM_ENV);
+ }
+ return;
+ }
+ (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", VAR_GLOBAL, VARE_WANTRES,
+ &xvarnames);
+ /* TODO: handle errors */
+ if (xvarnames[0] != '\0') {
+ Words varnames = Str_Words(xvarnames, FALSE);
+ size_t i;
+
+ for (i = 0; i < varnames.len; i++)
+ ExportVar(varnames.words[i], VEM_ENV);
+ Words_Free(varnames);
+ }
+ free(xvarnames);
+}
+
+static void
+ExportVars(const char *varnames, Boolean isExport, VarExportMode mode)
+{
+ Words words = Str_Words(varnames, FALSE);
size_t i;
+
+ if (words.len == 1 && words.words[0][0] == '\0')
+ words.len = 0;
+
for (i = 0; i < words.len; i++) {
- const char *name = words.words[i];
- if (Var_Export1(name, flags)) {
+ const char *varname = words.words[i];
+ if (!ExportVar(varname, mode))
+ continue;
+
if (var_exportedVars == VAR_EXPORTED_NONE)
- var_exportedVars = VAR_EXPORTED_SOME;
- if (isExport && (flags & VAR_EXPORT_PARENT)) {
- Var_Append(MAKE_EXPORTED, name, VAR_GLOBAL);
- }
- }
+ var_exportedVars = VAR_EXPORTED_SOME;
+
+ if (isExport && mode == VEM_PLAIN)
+ Var_Append(MAKE_EXPORTED, varname, VAR_GLOBAL);
}
Words_Free(words);
- }
- free(val);
}
+static void
+ExportVarsExpand(const char *uvarnames, Boolean isExport, VarExportMode mode)
+{
+ char *xvarnames;
-extern char **environ;
+ (void)Var_Subst(uvarnames, VAR_GLOBAL, VARE_WANTRES, &xvarnames);
+ /* TODO: handle errors */
+ ExportVars(xvarnames, isExport, mode);
+ free(xvarnames);
+}
-/*
- * This is called when .unexport[-env] is seen.
- *
- * str must have the form "unexport[-env] varname...".
- */
+/* Export the named variables, or all variables. */
void
-Var_UnExport(const char *str)
+Var_Export(VarExportMode mode, const char *varnames)
{
- const char *varnames;
- char *varnames_freeIt;
- Boolean unexport_env;
+ if (mode == VEM_PLAIN && varnames[0] == '\0') {
+ var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */
+ return;
+ }
- varnames = NULL;
- varnames_freeIt = NULL;
+ ExportVarsExpand(varnames, TRUE, mode);
+}
+
+void
+Var_ExportVars(const char *varnames)
+{
+ ExportVarsExpand(varnames, FALSE, VEM_PLAIN);
+}
+
+
+extern char **environ;
- str += strlen("unexport");
- unexport_env = strncmp(str, "-env", 4) == 0;
- if (unexport_env) {
+static void
+ClearEnv(void)
+{
const char *cp;
char **newenv;
cp = getenv(MAKE_LEVEL_ENV); /* we should preserve this */
if (environ == savedEnv) {
- /* we have been here before! */
- newenv = bmake_realloc(environ, 2 * sizeof(char *));
+ /* we have been here before! */
+ newenv = bmake_realloc(environ, 2 * sizeof(char *));
} else {
- if (savedEnv) {
- free(savedEnv);
- savedEnv = NULL;
- }
- newenv = bmake_malloc(2 * sizeof(char *));
+ if (savedEnv != NULL) {
+ free(savedEnv);
+ savedEnv = NULL;
+ }
+ newenv = bmake_malloc(2 * sizeof(char *));
}
/* Note: we cannot safely free() the original environ. */
environ = savedEnv = newenv;
newenv[0] = NULL;
newenv[1] = NULL;
- if (cp && *cp)
- setenv(MAKE_LEVEL_ENV, cp, 1);
- } else {
- cpp_skip_whitespace(&str);
- if (str[0] != '\0')
- varnames = str;
- }
-
- if (varnames == NULL) {
- /* Using .MAKE.EXPORTED */
- (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", VAR_GLOBAL, VARE_WANTRES,
- &varnames_freeIt);
- /* TODO: handle errors */
- varnames = varnames_freeIt;
- }
+ if (cp != NULL && *cp != '\0')
+ setenv(MAKE_LEVEL_ENV, cp, 1);
+}
- {
- Var *v;
- size_t i;
+static void
+GetVarnamesToUnexport(Boolean isEnv, const char *arg,
+ FStr *out_varnames, UnexportWhat *out_what)
+{
+ UnexportWhat what;
+ FStr varnames = FStr_InitRefer("");
+
+ if (isEnv) {
+ if (arg[0] != '\0') {
+ Parse_Error(PARSE_FATAL,
+ "The directive .unexport-env does not take "
+ "arguments");
+ }
+ what = UNEXPORT_ENV;
- Words words = Str_Words(varnames, FALSE);
- for (i = 0; i < words.len; i++) {
- const char *varname = words.words[i];
- v = VarFind(varname, VAR_GLOBAL, FALSE);
- if (v == NULL) {
- VAR_DEBUG1("Not unexporting \"%s\" (not found)\n", varname);
- continue;
- }
-
- VAR_DEBUG1("Unexporting \"%s\"\n", varname);
- if (!unexport_env && (v->flags & VAR_EXPORTED) &&
- !(v->flags & VAR_REEXPORT))
- unsetenv(v->name);
- v->flags &= ~(unsigned)(VAR_EXPORTED | VAR_REEXPORT);
-
- /*
- * If we are unexporting a list,
- * remove each one from .MAKE.EXPORTED.
- * If we are removing them all,
- * just delete .MAKE.EXPORTED below.
- */
- if (varnames == str) {
+ } else {
+ what = arg[0] != '\0' ? UNEXPORT_NAMED : UNEXPORT_ALL;
+ if (what == UNEXPORT_NAMED)
+ varnames = FStr_InitRefer(arg);
+ }
+
+ if (what != UNEXPORT_NAMED) {
+ char *expanded;
+ /* Using .MAKE.EXPORTED */
+ (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", VAR_GLOBAL,
+ VARE_WANTRES, &expanded);
+ /* TODO: handle errors */
+ varnames = FStr_InitOwn(expanded);
+ }
+
+ *out_varnames = varnames;
+ *out_what = what;
+}
+
+static void
+UnexportVar(const char *varname, UnexportWhat what)
+{
+ Var *v = VarFind(varname, VAR_GLOBAL, FALSE);
+ if (v == NULL) {
+ DEBUG1(VAR, "Not unexporting \"%s\" (not found)\n", varname);
+ return;
+ }
+
+ DEBUG1(VAR, "Unexporting \"%s\"\n", varname);
+ if (what != UNEXPORT_ENV &&
+ (v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT))
+ unsetenv(v->name.str);
+ v->flags &= ~(unsigned)(VAR_EXPORTED | VAR_REEXPORT);
+
+ if (what == UNEXPORT_NAMED) {
+ /* Remove the variable names from .MAKE.EXPORTED. */
/* XXX: v->name is injected without escaping it */
- char *expr = str_concat3("${" MAKE_EXPORTED ":N", v->name, "}");
+ char *expr = str_concat3("${" MAKE_EXPORTED ":N",
+ v->name.str, "}");
char *cp;
(void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &cp);
/* TODO: handle errors */
Var_Set(MAKE_EXPORTED, cp, VAR_GLOBAL);
free(cp);
free(expr);
- }
}
- Words_Free(words);
- if (varnames != str) {
- Var_Delete(MAKE_EXPORTED, VAR_GLOBAL);
- free(varnames_freeIt);
+}
+
+static void
+UnexportVars(FStr *varnames, UnexportWhat what)
+{
+ size_t i;
+ Words words;
+
+ if (what == UNEXPORT_ENV)
+ ClearEnv();
+
+ words = Str_Words(varnames->str, FALSE);
+ for (i = 0; i < words.len; i++) {
+ const char *varname = words.words[i];
+ UnexportVar(varname, what);
}
- }
+ Words_Free(words);
+
+ if (what != UNEXPORT_NAMED)
+ Var_Delete(MAKE_EXPORTED, VAR_GLOBAL);
}
-/* See Var_Set for documentation. */
+/*
+ * This is called when .unexport[-env] is seen.
+ *
+ * str must have the form "unexport[-env] varname...".
+ */
void
-Var_SetWithFlags(const char *name, const char *val, GNode *ctxt,
- VarSetFlags flags)
+Var_UnExport(Boolean isEnv, const char *arg)
{
- const char *unexpanded_name = name;
- char *name_freeIt = NULL;
- Var *v;
+ UnexportWhat what;
+ FStr varnames;
- assert(val != NULL);
+ GetVarnamesToUnexport(isEnv, arg, &varnames, &what);
+ UnexportVars(&varnames, what);
+ FStr_Done(&varnames);
+}
- if (strchr(name, '$') != NULL) {
- (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt);
- /* TODO: handle errors */
- name = name_freeIt;
- }
+/* Set the variable to the value; the name is not expanded. */
+static void
+SetVar(const char *name, const char *val, GNode *ctxt, VarSetFlags flags)
+{
+ Var *v;
- if (name[0] == '\0') {
- VAR_DEBUG2("Var_Set(\"%s\", \"%s\", ...) "
- "name expands to empty string - ignored\n",
- unexpanded_name, val);
- free(name_freeIt);
- return;
- }
-
- if (ctxt == VAR_GLOBAL) {
- v = VarFind(name, VAR_CMDLINE, FALSE);
- if (v != NULL) {
- if (v->flags & VAR_FROM_CMD) {
- VAR_DEBUG3("%s:%s = %s ignored!\n", ctxt->name, name, val);
- goto out;
- }
- VarFreeEnv(v, TRUE);
+ if (ctxt == VAR_GLOBAL) {
+ v = VarFind(name, VAR_CMDLINE, FALSE);
+ if (v != NULL) {
+ if (v->flags & VAR_FROM_CMD) {
+ DEBUG3(VAR, "%s:%s = %s ignored!\n",
+ ctxt->name, name, val);
+ return;
+ }
+ VarFreeEnv(v, TRUE);
+ }
}
- }
-
- /*
- * We only look for a variable in the given context since anything set
- * here will override anything in a lower context, so there's not much
- * point in searching them all just to save a bit of memory...
- */
- v = VarFind(name, ctxt, FALSE);
- if (v == NULL) {
- if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT)) {
- /*
- * This var would normally prevent the same name being added
- * to VAR_GLOBAL, so delete it from there if needed.
- * Otherwise -V name may show the wrong value.
- */
- /* XXX: name is expanded for the second time */
- Var_Delete(name, VAR_GLOBAL);
+
+ /*
+ * We only look for a variable in the given context since anything set
+ * here will override anything in a lower context, so there's not much
+ * point in searching them all just to save a bit of memory...
+ */
+ v = VarFind(name, ctxt, FALSE);
+ if (v == NULL) {
+ if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT)) {
+ /*
+ * This var would normally prevent the same name being
+ * added to VAR_GLOBAL, so delete it from there if
+ * needed. Otherwise -V name may show the wrong value.
+ */
+ /* XXX: name is expanded for the second time */
+ Var_Delete(name, VAR_GLOBAL);
+ }
+ VarAdd(name, val, ctxt, flags);
+ } else {
+ if ((v->flags & VAR_READONLY) && !(flags & VAR_SET_READONLY)) {
+ DEBUG3(VAR, "%s:%s = %s ignored (read-only)\n",
+ ctxt->name, name, val);
+ return;
+ }
+ Buf_Empty(&v->val);
+ Buf_AddStr(&v->val, val);
+
+ DEBUG3(VAR, "%s:%s = %s\n", ctxt->name, name, val);
+ if (v->flags & VAR_EXPORTED)
+ ExportVar(name, VEM_PLAIN);
}
- VarAdd(name, val, ctxt, flags);
- } else {
- if ((v->flags & VAR_READONLY) && !(flags & VAR_SET_READONLY)) {
- VAR_DEBUG3("%s:%s = %s ignored (read-only)\n",
- ctxt->name, name, val);
- goto out;
+ /*
+ * Any variables given on the command line are automatically exported
+ * to the environment (as per POSIX standard)
+ * Other than internals.
+ */
+ if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT) &&
+ name[0] != '.') {
+ if (v == NULL)
+ v = VarFind(name, ctxt, FALSE); /* we just added it */
+ v->flags |= VAR_FROM_CMD;
+
+ /*
+ * If requested, don't export these in the environment
+ * individually. We still put them in MAKEOVERRIDES so
+ * that the command-line settings continue to override
+ * Makefile settings.
+ */
+ if (!opts.varNoExportEnv)
+ setenv(name, val, 1);
+
+ Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL);
}
- Buf_Empty(&v->val);
- Buf_AddStr(&v->val, val);
+ if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0)
+ save_dollars = ParseBoolean(val, save_dollars);
+
+ if (v != NULL)
+ VarFreeEnv(v, TRUE);
+}
- VAR_DEBUG3("%s:%s = %s\n", ctxt->name, name, val);
- if (v->flags & VAR_EXPORTED) {
- Var_Export1(name, VAR_EXPORT_PARENT);
+/* See Var_Set for documentation. */
+void
+Var_SetWithFlags(const char *name, const char *val, GNode *ctxt,
+ 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, ctxt, VARE_WANTRES, &expanded);
+ /* TODO: handle errors */
+ varname = FStr_InitOwn(expanded);
}
- }
- /*
- * Any variables given on the command line are automatically exported
- * to the environment (as per POSIX standard)
- * Other than internals.
- */
- if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT) && name[0] != '.') {
- if (v == NULL)
- v = VarFind(name, ctxt, FALSE); /* we just added it */
- v->flags |= VAR_FROM_CMD;
- /*
- * If requested, don't export these in the environment
- * individually. We still put them in MAKEOVERRIDES so
- * that the command-line settings continue to override
- * Makefile settings.
- */
- if (!opts.varNoExportEnv)
- setenv(name, val, 1);
-
- Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL);
- }
- if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0)
- save_dollars = ParseBoolean(val, save_dollars);
-
-out:
- free(name_freeIt);
- if (v != NULL)
- VarFreeEnv(v, TRUE);
+ if (varname.str[0] == '\0') {
+ DEBUG2(VAR, "Var_Set(\"%s\", \"%s\", ...) "
+ "name expands to empty string - ignored\n",
+ unexpanded_name, val);
+ } else
+ SetVar(varname.str, val, ctxt, flags);
+
+ FStr_Done(&varname);
}
-/*-
- *-----------------------------------------------------------------------
- * Var_Set --
- * Set the variable name to the value val in the given context.
+/*
+ * Set the variable name to the value val in the given context.
*
- * If the variable doesn't yet exist, it is created.
- * Otherwise the new value overwrites and replaces the old value.
+ * If the variable doesn't yet exist, it is created.
+ * Otherwise the new value overwrites and replaces the old value.
*
* Input:
* name name of the variable to set, is expanded once
* val value to give to the variable
* ctxt context in which to set it
- *
- * Notes:
- * The variable is searched for only in its context before being
- * created in that context. I.e. if the context is VAR_GLOBAL,
- * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMDLINE,
- * only VAR_CMDLINE->context is searched. This is done to avoid the
- * literally thousands of unnecessary strcmp's that used to be done to
- * set, say, $(@) or $(<).
- * If the context is VAR_GLOBAL though, we check if the variable
- * was set in VAR_CMDLINE from the command line and skip it if so.
- *-----------------------------------------------------------------------
*/
void
Var_Set(const char *name, const char *val, GNode *ctxt)
{
- Var_SetWithFlags(name, val, ctxt, VAR_SET_NONE);
+ Var_SetWithFlags(name, val, ctxt, VAR_SET_NONE);
}
-/*-
- *-----------------------------------------------------------------------
- * Var_Append --
- * The variable of the given name has the given value appended to it in
- * the given context.
+/*
+ * The variable of the given name has the given value appended to it in the
+ * given context.
*
- * If the variable doesn't exist, it is created. Otherwise the strings
- * are concatenated, with a space in between.
+ * If the variable doesn't exist, it is created. Otherwise the strings are
+ * concatenated, with a space in between.
*
* Input:
* name name of the variable to modify, is expanded once
@@ -922,61 +1056,65 @@ Var_Set(const char *name, const char *val, GNode *ctxt)
* an actual target, it will only search that context since only
* a local variable could be being appended to. This is actually
* a big win and must be tolerated.
- *-----------------------------------------------------------------------
*/
void
Var_Append(const char *name, const char *val, GNode *ctxt)
{
- char *name_freeIt = NULL;
- Var *v;
+ char *name_freeIt = NULL;
+ Var *v;
- assert(val != NULL);
+ assert(val != NULL);
- if (strchr(name, '$') != NULL) {
- const char *unexpanded_name = name;
- (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt);
- /* TODO: handle errors */
- name = name_freeIt;
- if (name[0] == '\0') {
- VAR_DEBUG2("Var_Append(\"%s\", \"%s\", ...) "
- "name expands to empty string - ignored\n",
- unexpanded_name, val);
- free(name_freeIt);
- return;
+ if (strchr(name, '$') != NULL) {
+ const char *unexpanded_name = name;
+ (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt);
+ /* TODO: handle errors */
+ name = name_freeIt;
+ if (name[0] == '\0') {
+ DEBUG2(VAR, "Var_Append(\"%s\", \"%s\", ...) "
+ "name expands to empty string - ignored\n",
+ unexpanded_name, val);
+ free(name_freeIt);
+ return;
+ }
}
- }
-
- v = VarFind(name, ctxt, ctxt == VAR_GLOBAL);
- if (v == NULL) {
- /* XXX: name is expanded for the second time */
- Var_Set(name, val, ctxt);
- } else if (v->flags & VAR_READONLY) {
- VAR_DEBUG1("Ignoring append to %s since it is read-only\n", name);
- } else if (ctxt == VAR_CMDLINE || !(v->flags & VAR_FROM_CMD)) {
- Buf_AddByte(&v->val, ' ');
- Buf_AddStr(&v->val, val);
-
- VAR_DEBUG3("%s:%s = %s\n",
- ctxt->name, name, Buf_GetAll(&v->val, NULL));
-
- if (v->flags & VAR_FROM_ENV) {
- /*
- * If the original variable came from the environment, we
- * have to install it in the global context (we could place
- * it in the environment, but then we should provide a way to
- * export other variables...)
- */
- v->flags &= ~(unsigned)VAR_FROM_ENV;
- /* This is the only place where a variable is created whose
- * v->name is not the same as ctxt->context->key. */
- HashTable_Set(&ctxt->context, name, v);
+ v = VarFind(name, ctxt, ctxt == VAR_GLOBAL);
+
+ if (v == NULL) {
+ /* XXX: name is expanded for the second time */
+ Var_Set(name, val, ctxt);
+ } else if (v->flags & VAR_READONLY) {
+ DEBUG1(VAR, "Ignoring append to %s since it is read-only\n",
+ name);
+ } else if (ctxt == VAR_CMDLINE || !(v->flags & VAR_FROM_CMD)) {
+ Buf_AddByte(&v->val, ' ');
+ Buf_AddStr(&v->val, val);
+
+ DEBUG3(VAR, "%s:%s = %s\n",
+ ctxt->name, name, Buf_GetAll(&v->val, NULL));
+
+ if (v->flags & VAR_FROM_ENV) {
+ /*
+ * If the original variable came from the environment,
+ * we have to install it in the global context (we
+ * could place it in the environment, but then we
+ * should provide a way to export other variables...)
+ */
+ v->flags &= ~(unsigned)VAR_FROM_ENV;
+ /*
+ * This is the only place where a variable is
+ * created whose v->name is not the same as
+ * ctxt->context->key.
+ */
+ HashTable_Set(&ctxt->vars, name, v);
+ }
}
- }
- free(name_freeIt);
+ free(name_freeIt);
}
-/* See if the given variable exists, in the given context or in other
+/*
+ * See if the given variable exists, in the given context or in other
* fallback contexts.
*
* Input:
@@ -986,29 +1124,28 @@ Var_Append(const char *name, const char *val, GNode *ctxt)
Boolean
Var_Exists(const char *name, GNode *ctxt)
{
- char *name_freeIt = NULL;
- Var *v;
+ FStr varname = FStr_InitRefer(name);
+ Var *v;
- if (strchr(name, '$') != NULL) {
- (void)Var_Subst(name, ctxt, VARE_WANTRES, &name_freeIt);
- /* TODO: handle errors */
- name = name_freeIt;
- }
+ if (strchr(varname.str, '$') != NULL) {
+ char *expanded;
+ (void)Var_Subst(varname.str, ctxt, VARE_WANTRES, &expanded);
+ /* TODO: handle errors */
+ varname = FStr_InitOwn(expanded);
+ }
- v = VarFind(name, ctxt, TRUE);
- free(name_freeIt);
- if (v == NULL)
- return FALSE;
+ v = VarFind(varname.str, ctxt, TRUE);
+ FStr_Done(&varname);
+ if (v == NULL)
+ return FALSE;
- (void)VarFreeEnv(v, TRUE);
- return TRUE;
+ (void)VarFreeEnv(v, TRUE);
+ return TRUE;
}
-/*-
- *-----------------------------------------------------------------------
- * Var_Value --
- * Return the unexpanded value of the given variable in the given
- * context, or the usual contexts.
+/*
+ * Return the unexpanded value of the given variable in the given context,
+ * or the usual contexts.
*
* Input:
* name name to find, is not expanded any further
@@ -1018,160 +1155,171 @@ Var_Exists(const char *name, GNode *ctxt)
* The value if the variable exists, NULL if it doesn't.
* If the returned value is not NULL, the caller must free
* out_freeIt when the returned value is no longer needed.
- *-----------------------------------------------------------------------
*/
-const char *
-Var_Value(const char *name, GNode *ctxt, void **out_freeIt)
+FStr
+Var_Value(const char *name, GNode *ctxt)
{
- Var *v = VarFind(name, ctxt, TRUE);
- char *value;
+ Var *v = VarFind(name, ctxt, TRUE);
+ char *value;
- *out_freeIt = NULL;
- if (v == NULL)
- return NULL;
+ if (v == NULL)
+ return FStr_InitRefer(NULL);
- value = Buf_GetAll(&v->val, NULL);
- if (VarFreeEnv(v, FALSE))
- *out_freeIt = value;
- return value;
+ value = Buf_GetAll(&v->val, NULL);
+ return VarFreeEnv(v, FALSE)
+ ? FStr_InitOwn(value)
+ : FStr_InitRefer(value);
}
-/* Return the unexpanded variable value from this node, without trying to look
- * up the variable in any other context. */
+/*
+ * Return the unexpanded variable value from this node, without trying to look
+ * up the variable in any other context.
+ */
const char *
Var_ValueDirect(const char *name, GNode *ctxt)
{
- Var *v = VarFind(name, ctxt, FALSE);
- return v != NULL ? Buf_GetAll(&v->val, NULL) : NULL;
+ Var *v = VarFind(name, ctxt, FALSE);
+ return v != NULL ? Buf_GetAll(&v->val, NULL) : NULL;
}
-/* SepBuf is a string being built from words, interleaved with separators. */
-typedef struct SepBuf {
- Buffer buf;
- Boolean needSep;
- char sep; /* usually ' ', but see the :ts modifier */
-} SepBuf;
-
static void
SepBuf_Init(SepBuf *buf, char sep)
{
- Buf_InitSize(&buf->buf, 32);
- buf->needSep = FALSE;
- buf->sep = sep;
+ Buf_InitSize(&buf->buf, 32);
+ buf->needSep = FALSE;
+ buf->sep = sep;
}
static void
SepBuf_Sep(SepBuf *buf)
{
- buf->needSep = TRUE;
+ buf->needSep = TRUE;
}
static void
SepBuf_AddBytes(SepBuf *buf, const char *mem, size_t mem_size)
{
- if (mem_size == 0)
- return;
- if (buf->needSep && buf->sep != '\0') {
- Buf_AddByte(&buf->buf, buf->sep);
- buf->needSep = FALSE;
- }
- Buf_AddBytes(&buf->buf, mem, mem_size);
+ if (mem_size == 0)
+ return;
+ if (buf->needSep && buf->sep != '\0') {
+ Buf_AddByte(&buf->buf, buf->sep);
+ buf->needSep = FALSE;
+ }
+ Buf_AddBytes(&buf->buf, mem, mem_size);
}
static void
SepBuf_AddBytesBetween(SepBuf *buf, const char *start, const char *end)
{
- SepBuf_AddBytes(buf, start, (size_t)(end - start));
+ SepBuf_AddBytes(buf, start, (size_t)(end - start));
}
static void
SepBuf_AddStr(SepBuf *buf, const char *str)
{
- SepBuf_AddBytes(buf, str, strlen(str));
+ SepBuf_AddBytes(buf, str, strlen(str));
}
static char *
SepBuf_Destroy(SepBuf *buf, Boolean free_buf)
{
- return Buf_Destroy(&buf->buf, free_buf);
+ return Buf_Destroy(&buf->buf, free_buf);
}
-/* This callback for ModifyWords gets a single word from a variable expression
+/*
+ * This callback for ModifyWords gets a single word from a variable expression
* and typically adds a modification of this word to the buffer. It may also
* do nothing or add several words.
*
* For example, in ${:Ua b c:M*2}, the callback is called 3 times, once for
- * each word of "a b c". */
+ * each word of "a b c".
+ */
typedef void (*ModifyWordsCallback)(const char *word, SepBuf *buf, void *data);
-/* Callback for ModifyWords to implement the :H modifier.
- * Add the dirname of the given word to the buffer. */
+/*
+ * Callback for ModifyWords to implement the :H modifier.
+ * Add the dirname of the given word to the buffer.
+ */
+/*ARGSUSED*/
static void
ModifyWord_Head(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
{
- const char *slash = strrchr(word, '/');
- if (slash != NULL)
- SepBuf_AddBytesBetween(buf, word, slash);
- else
- SepBuf_AddStr(buf, ".");
+ const char *slash = strrchr(word, '/');
+ if (slash != NULL)
+ SepBuf_AddBytesBetween(buf, word, slash);
+ else
+ SepBuf_AddStr(buf, ".");
}
-/* Callback for ModifyWords to implement the :T modifier.
- * Add the basename of the given word to the buffer. */
+/*
+ * Callback for ModifyWords to implement the :T modifier.
+ * Add the basename of the given word to the buffer.
+ */
+/*ARGSUSED*/
static void
ModifyWord_Tail(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
{
- const char *slash = strrchr(word, '/');
- const char *base = slash != NULL ? slash + 1 : word;
- SepBuf_AddStr(buf, base);
+ SepBuf_AddStr(buf, str_basename(word));
}
-/* Callback for ModifyWords to implement the :E modifier.
- * Add the filename suffix of the given word to the buffer, if it exists. */
+/*
+ * Callback for ModifyWords to implement the :E modifier.
+ * Add the filename suffix of the given word to the buffer, if it exists.
+ */
+/*ARGSUSED*/
static void
ModifyWord_Suffix(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
{
- const char *dot = strrchr(word, '.');
- if (dot != NULL)
- SepBuf_AddStr(buf, dot + 1);
+ const char *lastDot = strrchr(word, '.');
+ if (lastDot != NULL)
+ SepBuf_AddStr(buf, lastDot + 1);
}
-/* Callback for ModifyWords to implement the :R modifier.
- * Add the basename of the given word to the buffer. */
+/*
+ * Callback for ModifyWords to implement the :R modifier.
+ * Add the basename of the given word to the buffer.
+ */
+/*ARGSUSED*/
static void
ModifyWord_Root(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
{
- const char *dot = strrchr(word, '.');
- size_t len = dot != NULL ? (size_t)(dot - word) : strlen(word);
- SepBuf_AddBytes(buf, word, len);
+ const char *lastDot = strrchr(word, '.');
+ size_t len = lastDot != NULL ? (size_t)(lastDot - word) : strlen(word);
+ SepBuf_AddBytes(buf, word, len);
}
-/* Callback for ModifyWords to implement the :M modifier.
- * Place the word in the buffer if it matches the given pattern. */
+/*
+ * Callback for ModifyWords to implement the :M modifier.
+ * Place the word in the buffer if it matches the given pattern.
+ */
static void
ModifyWord_Match(const char *word, SepBuf *buf, void *data)
{
- const char *pattern = data;
- VAR_DEBUG2("VarMatch [%s] [%s]\n", word, pattern);
- if (Str_Match(word, pattern))
- SepBuf_AddStr(buf, word);
+ const char *pattern = data;
+ DEBUG2(VAR, "VarMatch [%s] [%s]\n", word, pattern);
+ if (Str_Match(word, pattern))
+ SepBuf_AddStr(buf, word);
}
-/* Callback for ModifyWords to implement the :N modifier.
- * Place the word in the buffer if it doesn't match the given pattern. */
+/*
+ * Callback for ModifyWords to implement the :N modifier.
+ * Place the word in the buffer if it doesn't match the given pattern.
+ */
static void
ModifyWord_NoMatch(const char *word, SepBuf *buf, void *data)
{
- const char *pattern = data;
- if (!Str_Match(word, pattern))
- SepBuf_AddStr(buf, word);
+ const char *pattern = data;
+ if (!Str_Match(word, pattern))
+ SepBuf_AddStr(buf, word);
}
#ifdef SYSVVARSUB
-/* Check word against pattern for a match (% is a wildcard).
+
+/*
+ * Check word against pattern for a match (% is a wildcard).
*
* Input:
* word Word to examine
@@ -1184,166 +1332,171 @@ ModifyWord_NoMatch(const char *word, SepBuf *buf, void *data)
*/
static const char *
SysVMatch(const char *word, const char *pattern,
- size_t *out_match_len, Boolean *out_hasPercent)
+ size_t *out_match_len, Boolean *out_hasPercent)
{
- const char *p = pattern;
- const char *w = word;
- const char *percent;
- size_t w_len;
- size_t p_len;
- const char *w_tail;
-
- *out_hasPercent = FALSE;
- percent = strchr(p, '%');
- if (percent != NULL) { /* ${VAR:...%...=...} */
- *out_hasPercent = TRUE;
- if (*w == '\0')
- return NULL; /* empty word does not match pattern */
-
- /* check that the prefix matches */
- for (; p != percent && *w != '\0' && *w == *p; w++, p++)
- continue;
- if (p != percent)
- return NULL; /* No match */
-
- p++; /* Skip the percent */
- if (*p == '\0') {
- /* No more pattern, return the rest of the string */
- *out_match_len = strlen(w);
- return w;
+ const char *p = pattern;
+ const char *w = word;
+ const char *percent;
+ size_t w_len;
+ size_t p_len;
+ const char *w_tail;
+
+ *out_hasPercent = FALSE;
+ percent = strchr(p, '%');
+ if (percent != NULL) { /* ${VAR:...%...=...} */
+ *out_hasPercent = TRUE;
+ if (w[0] == '\0')
+ return NULL; /* empty word does not match pattern */
+
+ /* check that the prefix matches */
+ for (; p != percent && *w != '\0' && *w == *p; w++, p++)
+ continue;
+ if (p != percent)
+ return NULL; /* No match */
+
+ p++; /* Skip the percent */
+ if (*p == '\0') {
+ /* No more pattern, return the rest of the string */
+ *out_match_len = strlen(w);
+ return w;
+ }
}
- }
- /* Test whether the tail matches */
- w_len = strlen(w);
- p_len = strlen(p);
- if (w_len < p_len)
- return NULL;
+ /* Test whether the tail matches */
+ w_len = strlen(w);
+ p_len = strlen(p);
+ if (w_len < p_len)
+ return NULL;
- w_tail = w + w_len - p_len;
- if (memcmp(p, w_tail, p_len) != 0)
- return NULL;
+ w_tail = w + w_len - p_len;
+ if (memcmp(p, w_tail, p_len) != 0)
+ return NULL;
- *out_match_len = (size_t)(w_tail - w);
- return w;
+ *out_match_len = (size_t)(w_tail - w);
+ return w;
}
struct ModifyWord_SYSVSubstArgs {
- GNode *ctx;
- const char *lhs;
- const char *rhs;
+ GNode *ctx;
+ const char *lhs;
+ const char *rhs;
};
/* Callback for ModifyWords to implement the :%.from=%.to modifier. */
static void
ModifyWord_SYSVSubst(const char *word, SepBuf *buf, void *data)
{
- const struct ModifyWord_SYSVSubstArgs *args = data;
- char *rhs_expanded;
- const char *rhs;
- const char *percent;
-
- size_t match_len;
- Boolean lhsPercent;
- const char *match = SysVMatch(word, args->lhs, &match_len, &lhsPercent);
- if (match == NULL) {
- SepBuf_AddStr(buf, word);
- return;
- }
+ const struct ModifyWord_SYSVSubstArgs *args = data;
+ char *rhs_expanded;
+ const char *rhs;
+ const char *percent;
+
+ size_t match_len;
+ Boolean lhsPercent;
+ const char *match = SysVMatch(word, args->lhs, &match_len, &lhsPercent);
+ if (match == NULL) {
+ SepBuf_AddStr(buf, word);
+ return;
+ }
- /* Append rhs to the buffer, substituting the first '%' with the
- * match, but only if the lhs had a '%' as well. */
+ /*
+ * Append rhs to the buffer, substituting the first '%' with the
+ * match, but only if the lhs had a '%' as well.
+ */
- (void)Var_Subst(args->rhs, args->ctx, VARE_WANTRES, &rhs_expanded);
- /* TODO: handle errors */
+ (void)Var_Subst(args->rhs, args->ctx, VARE_WANTRES, &rhs_expanded);
+ /* TODO: handle errors */
- rhs = rhs_expanded;
- percent = strchr(rhs, '%');
+ rhs = rhs_expanded;
+ percent = strchr(rhs, '%');
- if (percent != NULL && lhsPercent) {
- /* Copy the prefix of the replacement pattern */
- SepBuf_AddBytesBetween(buf, rhs, percent);
- rhs = percent + 1;
- }
- if (percent != NULL || !lhsPercent)
- SepBuf_AddBytes(buf, match, match_len);
+ if (percent != NULL && lhsPercent) {
+ /* Copy the prefix of the replacement pattern */
+ SepBuf_AddBytesBetween(buf, rhs, percent);
+ rhs = percent + 1;
+ }
+ if (percent != NULL || !lhsPercent)
+ SepBuf_AddBytes(buf, match, match_len);
- /* Append the suffix of the replacement pattern */
- SepBuf_AddStr(buf, rhs);
+ /* Append the suffix of the replacement pattern */
+ SepBuf_AddStr(buf, rhs);
- free(rhs_expanded);
+ free(rhs_expanded);
}
#endif
struct ModifyWord_SubstArgs {
- const char *lhs;
- size_t lhsLen;
- const char *rhs;
- size_t rhsLen;
- VarPatternFlags pflags;
- Boolean matched;
+ const char *lhs;
+ size_t lhsLen;
+ const char *rhs;
+ size_t rhsLen;
+ VarPatternFlags pflags;
+ Boolean matched;
};
-/* Callback for ModifyWords to implement the :S,from,to, modifier.
- * Perform a string substitution on the given word. */
+/*
+ * Callback for ModifyWords to implement the :S,from,to, modifier.
+ * Perform a string substitution on the given word.
+ */
static void
ModifyWord_Subst(const char *word, SepBuf *buf, void *data)
{
- size_t wordLen = strlen(word);
- struct ModifyWord_SubstArgs *args = data;
- const char *match;
-
- if ((args->pflags & VARP_SUB_ONE) && args->matched)
- goto nosub;
-
- if (args->pflags & VARP_ANCHOR_START) {
- if (wordLen < args->lhsLen ||
- memcmp(word, args->lhs, args->lhsLen) != 0)
- goto nosub;
-
- if ((args->pflags & VARP_ANCHOR_END) && wordLen != args->lhsLen)
- goto nosub;
-
- /* :S,^prefix,replacement, or :S,^whole$,replacement, */
- SepBuf_AddBytes(buf, args->rhs, args->rhsLen);
- SepBuf_AddBytes(buf, word + args->lhsLen, wordLen - args->lhsLen);
- args->matched = TRUE;
- return;
- }
-
- if (args->pflags & VARP_ANCHOR_END) {
- const char *start;
-
- if (wordLen < args->lhsLen)
- goto nosub;
-
- start = word + (wordLen - args->lhsLen);
- if (memcmp(start, args->lhs, args->lhsLen) != 0)
- goto nosub;
-
- /* :S,suffix$,replacement, */
- SepBuf_AddBytesBetween(buf, word, start);
- SepBuf_AddBytes(buf, args->rhs, args->rhsLen);
- args->matched = TRUE;
- return;
- }
-
- if (args->lhs[0] == '\0')
- goto nosub;
-
- /* unanchored case, may match more than once */
- while ((match = strstr(word, args->lhs)) != NULL) {
- SepBuf_AddBytesBetween(buf, word, match);
- SepBuf_AddBytes(buf, args->rhs, args->rhsLen);
- args->matched = TRUE;
- wordLen -= (size_t)(match - word) + args->lhsLen;
- word += (size_t)(match - word) + args->lhsLen;
- if (wordLen == 0 || !(args->pflags & VARP_SUB_GLOBAL))
- break;
- }
+ size_t wordLen = strlen(word);
+ struct ModifyWord_SubstArgs *args = data;
+ const char *match;
+
+ if ((args->pflags & VARP_SUB_ONE) && args->matched)
+ goto nosub;
+
+ if (args->pflags & VARP_ANCHOR_START) {
+ if (wordLen < args->lhsLen ||
+ memcmp(word, args->lhs, args->lhsLen) != 0)
+ goto nosub;
+
+ if ((args->pflags & VARP_ANCHOR_END) && wordLen != args->lhsLen)
+ goto nosub;
+
+ /* :S,^prefix,replacement, or :S,^whole$,replacement, */
+ SepBuf_AddBytes(buf, args->rhs, args->rhsLen);
+ SepBuf_AddBytes(buf, word + args->lhsLen,
+ wordLen - args->lhsLen);
+ args->matched = TRUE;
+ return;
+ }
+
+ if (args->pflags & VARP_ANCHOR_END) {
+ const char *start;
+
+ if (wordLen < args->lhsLen)
+ goto nosub;
+
+ start = word + (wordLen - args->lhsLen);
+ if (memcmp(start, args->lhs, args->lhsLen) != 0)
+ goto nosub;
+
+ /* :S,suffix$,replacement, */
+ SepBuf_AddBytesBetween(buf, word, start);
+ SepBuf_AddBytes(buf, args->rhs, args->rhsLen);
+ args->matched = TRUE;
+ return;
+ }
+
+ if (args->lhs[0] == '\0')
+ goto nosub;
+
+ /* unanchored case, may match more than once */
+ while ((match = strstr(word, args->lhs)) != NULL) {
+ SepBuf_AddBytesBetween(buf, word, match);
+ SepBuf_AddBytes(buf, args->rhs, args->rhsLen);
+ args->matched = TRUE;
+ wordLen -= (size_t)(match - word) + args->lhsLen;
+ word += (size_t)(match - word) + args->lhsLen;
+ if (wordLen == 0 || !(args->pflags & VARP_SUB_GLOBAL))
+ break;
+ }
nosub:
- SepBuf_AddBytes(buf, word, wordLen);
+ SepBuf_AddBytes(buf, word, wordLen);
}
#ifndef NO_REGEX
@@ -1351,210 +1504,220 @@ nosub:
static void
VarREError(int reerr, const regex_t *pat, const char *str)
{
- size_t errlen = regerror(reerr, pat, NULL, 0);
- char *errbuf = bmake_malloc(errlen);
- regerror(reerr, pat, errbuf, errlen);
- Error("%s: %s", str, errbuf);
- free(errbuf);
+ size_t errlen = regerror(reerr, pat, NULL, 0);
+ char *errbuf = bmake_malloc(errlen);
+ regerror(reerr, pat, errbuf, errlen);
+ Error("%s: %s", str, errbuf);
+ free(errbuf);
}
struct ModifyWord_SubstRegexArgs {
- regex_t re;
- size_t nsub;
- char *replace;
- VarPatternFlags pflags;
- Boolean matched;
+ regex_t re;
+ size_t nsub;
+ char *replace;
+ VarPatternFlags pflags;
+ Boolean matched;
};
-/* Callback for ModifyWords to implement the :C/from/to/ modifier.
- * Perform a regex substitution on the given word. */
+/*
+ * Callback for ModifyWords to implement the :C/from/to/ modifier.
+ * Perform a regex substitution on the given word.
+ */
static void
ModifyWord_SubstRegex(const char *word, SepBuf *buf, void *data)
{
- struct ModifyWord_SubstRegexArgs *args = data;
- int xrv;
- const char *wp = word;
- char *rp;
- int flags = 0;
- regmatch_t m[10];
+ struct ModifyWord_SubstRegexArgs *args = data;
+ int xrv;
+ const char *wp = word;
+ char *rp;
+ int flags = 0;
+ regmatch_t m[10];
- if ((args->pflags & VARP_SUB_ONE) && args->matched)
- goto nosub;
+ if ((args->pflags & VARP_SUB_ONE) && args->matched)
+ goto nosub;
tryagain:
- xrv = regexec(&args->re, wp, args->nsub, m, flags);
-
- switch (xrv) {
- case 0:
- args->matched = TRUE;
- SepBuf_AddBytes(buf, wp, (size_t)m[0].rm_so);
-
- for (rp = args->replace; *rp; rp++) {
- if (*rp == '\\' && (rp[1] == '&' || rp[1] == '\\')) {
- SepBuf_AddBytes(buf, rp + 1, 1);
- rp++;
- continue;
- }
-
- if (*rp == '&') {
- SepBuf_AddBytesBetween(buf, wp + m[0].rm_so, wp + m[0].rm_eo);
- continue;
- }
-
- if (*rp != '\\' || !ch_isdigit(rp[1])) {
- SepBuf_AddBytes(buf, rp, 1);
- continue;
- }
-
- { /* \0 to \9 backreference */
- size_t n = (size_t)(rp[1] - '0');
- rp++;
-
- if (n >= args->nsub) {
- Error("No subexpression \\%zu", n);
- } else if (m[n].rm_so == -1) {
- Error("No match for subexpression \\%zu", n);
- } else {
- SepBuf_AddBytesBetween(buf, wp + m[n].rm_so,
- wp + m[n].rm_eo);
+ xrv = regexec(&args->re, wp, args->nsub, m, flags);
+
+ switch (xrv) {
+ case 0:
+ args->matched = TRUE;
+ SepBuf_AddBytes(buf, wp, (size_t)m[0].rm_so);
+
+ for (rp = args->replace; *rp != '\0'; rp++) {
+ if (*rp == '\\' && (rp[1] == '&' || rp[1] == '\\')) {
+ SepBuf_AddBytes(buf, rp + 1, 1);
+ rp++;
+ continue;
+ }
+
+ if (*rp == '&') {
+ SepBuf_AddBytesBetween(buf,
+ wp + m[0].rm_so, wp + m[0].rm_eo);
+ continue;
+ }
+
+ if (*rp != '\\' || !ch_isdigit(rp[1])) {
+ SepBuf_AddBytes(buf, rp, 1);
+ continue;
+ }
+
+ { /* \0 to \9 backreference */
+ size_t n = (size_t)(rp[1] - '0');
+ rp++;
+
+ if (n >= args->nsub) {
+ Error("No subexpression \\%u",
+ (unsigned)n);
+ } else if (m[n].rm_so == -1) {
+ Error(
+ "No match for subexpression \\%u",
+ (unsigned)n);
+ } else {
+ SepBuf_AddBytesBetween(buf,
+ wp + m[n].rm_so, wp + m[n].rm_eo);
+ }
+ }
}
- }
- }
- wp += m[0].rm_eo;
- if (args->pflags & VARP_SUB_GLOBAL) {
- flags |= REG_NOTBOL;
- if (m[0].rm_so == 0 && m[0].rm_eo == 0) {
- SepBuf_AddBytes(buf, wp, 1);
- wp++;
- }
- if (*wp)
- goto tryagain;
- }
- if (*wp) {
- SepBuf_AddStr(buf, wp);
+ wp += m[0].rm_eo;
+ if (args->pflags & VARP_SUB_GLOBAL) {
+ flags |= REG_NOTBOL;
+ if (m[0].rm_so == 0 && m[0].rm_eo == 0) {
+ SepBuf_AddBytes(buf, wp, 1);
+ wp++;
+ }
+ if (*wp != '\0')
+ goto tryagain;
+ }
+ if (*wp != '\0')
+ SepBuf_AddStr(buf, wp);
+ break;
+ default:
+ VarREError(xrv, &args->re, "Unexpected regex error");
+ /* FALLTHROUGH */
+ case REG_NOMATCH:
+ nosub:
+ SepBuf_AddStr(buf, wp);
+ break;
}
- break;
- default:
- VarREError(xrv, &args->re, "Unexpected regex error");
- /* FALLTHROUGH */
- case REG_NOMATCH:
- nosub:
- SepBuf_AddStr(buf, wp);
- break;
- }
}
#endif
struct ModifyWord_LoopArgs {
- GNode *ctx;
- char *tvar; /* name of temporary variable */
- char *str; /* string to expand */
- VarEvalFlags eflags;
+ GNode *ctx;
+ char *tvar; /* name of temporary variable */
+ char *str; /* string to expand */
+ VarEvalFlags eflags;
};
/* Callback for ModifyWords to implement the :@var@...@ modifier of ODE make. */
static void
ModifyWord_Loop(const char *word, SepBuf *buf, void *data)
{
- const struct ModifyWord_LoopArgs *args;
- char *s;
+ const struct ModifyWord_LoopArgs *args;
+ char *s;
- if (word[0] == '\0')
- return;
+ if (word[0] == '\0')
+ return;
- args = data;
- Var_SetWithFlags(args->tvar, word, args->ctx, VAR_SET_NO_EXPORT);
- (void)Var_Subst(args->str, args->ctx, args->eflags, &s);
- /* TODO: handle errors */
+ args = data;
+ Var_SetWithFlags(args->tvar, word, args->ctx, VAR_SET_NO_EXPORT);
+ (void)Var_Subst(args->str, args->ctx, args->eflags, &s);
+ /* TODO: handle errors */
- VAR_DEBUG4("ModifyWord_Loop: "
- "in \"%s\", replace \"%s\" with \"%s\" to \"%s\"\n",
- word, args->tvar, args->str, s);
+ DEBUG4(VAR, "ModifyWord_Loop: "
+ "in \"%s\", replace \"%s\" with \"%s\" to \"%s\"\n",
+ word, args->tvar, args->str, s);
- if (s[0] == '\n' || Buf_EndsWith(&buf->buf, '\n'))
- buf->needSep = FALSE;
- SepBuf_AddStr(buf, s);
- free(s);
+ if (s[0] == '\n' || Buf_EndsWith(&buf->buf, '\n'))
+ buf->needSep = FALSE;
+ SepBuf_AddStr(buf, s);
+ free(s);
}
-/* The :[first..last] modifier selects words from the expression.
- * It can also reverse the words. */
+/*
+ * The :[first..last] modifier selects words from the expression.
+ * It can also reverse the words.
+ */
static char *
VarSelectWords(char sep, Boolean oneBigWord, const char *str, int first,
int last)
{
- Words words;
- int len, start, end, step;
- int i;
-
- SepBuf buf;
- SepBuf_Init(&buf, sep);
-
- if (oneBigWord) {
- /* fake what Str_Words() would do if there were only one word */
- words.len = 1;
- words.words = bmake_malloc((words.len + 1) * sizeof(words.words[0]));
- words.freeIt = bmake_strdup(str);
- words.words[0] = words.freeIt;
- words.words[1] = NULL;
- } else {
- words = Str_Words(str, FALSE);
- }
-
- /*
- * Now sanitize the given range.
- * If first or last are negative, convert them to the positive equivalents
- * (-1 gets converted to ac, -2 gets converted to (ac - 1), etc.).
- */
- len = (int)words.len;
- if (first < 0)
- first += len + 1;
- if (last < 0)
- last += len + 1;
-
- /*
- * We avoid scanning more of the list than we need to.
- */
- if (first > last) {
- start = (first > len ? len : first) - 1;
- end = last < 1 ? 0 : last - 1;
- step = -1;
- } else {
- start = first < 1 ? 0 : first - 1;
- end = last > len ? len : last;
- step = 1;
- }
-
- for (i = start; (step < 0) == (i >= end); i += step) {
- SepBuf_AddStr(&buf, words.words[i]);
- SepBuf_Sep(&buf);
- }
-
- Words_Free(words);
-
- return SepBuf_Destroy(&buf, FALSE);
+ Words words;
+ int len, start, end, step;
+ int i;
+
+ SepBuf buf;
+ SepBuf_Init(&buf, sep);
+
+ if (oneBigWord) {
+ /* fake what Str_Words() would do if there were only one word */
+ words.len = 1;
+ words.words = bmake_malloc(
+ (words.len + 1) * sizeof(words.words[0]));
+ words.freeIt = bmake_strdup(str);
+ words.words[0] = words.freeIt;
+ words.words[1] = NULL;
+ } else {
+ words = Str_Words(str, FALSE);
+ }
+
+ /*
+ * Now sanitize the given range. If first or last are negative,
+ * convert them to the positive equivalents (-1 gets converted to len,
+ * -2 gets converted to (len - 1), etc.).
+ */
+ len = (int)words.len;
+ if (first < 0)
+ first += len + 1;
+ if (last < 0)
+ last += len + 1;
+
+ /* We avoid scanning more of the list than we need to. */
+ if (first > last) {
+ start = (first > len ? len : first) - 1;
+ end = last < 1 ? 0 : last - 1;
+ step = -1;
+ } else {
+ start = first < 1 ? 0 : first - 1;
+ end = last > len ? len : last;
+ step = 1;
+ }
+
+ for (i = start; (step < 0) == (i >= end); i += step) {
+ SepBuf_AddStr(&buf, words.words[i]);
+ SepBuf_Sep(&buf);
+ }
+
+ Words_Free(words);
+
+ return SepBuf_Destroy(&buf, FALSE);
}
-/* Callback for ModifyWords to implement the :tA modifier.
- * Replace each word with the result of realpath() if successful. */
+/*
+ * Callback for ModifyWords to implement the :tA modifier.
+ * Replace each word with the result of realpath() if successful.
+ */
+/*ARGSUSED*/
static void
ModifyWord_Realpath(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED)
{
- struct stat st;
- char rbuf[MAXPATHLEN];
+ struct stat st;
+ char rbuf[MAXPATHLEN];
- const char *rp = cached_realpath(word, rbuf);
- if (rp != NULL && *rp == '/' && stat(rp, &st) == 0)
- word = rp;
+ const char *rp = cached_realpath(word, rbuf);
+ if (rp != NULL && *rp == '/' && stat(rp, &st) == 0)
+ word = rp;
- SepBuf_AddStr(buf, word);
+ SepBuf_AddStr(buf, word);
}
-/* Modify each of the words of the passed string using the given function.
+/*
+ * Modify each of the words of the passed string using the given function.
*
* Input:
* str String whose words should be modified
@@ -1563,179 +1726,186 @@ ModifyWord_Realpath(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED)
*
* Results:
* A string of all the words modified appropriately.
- *-----------------------------------------------------------------------
*/
static char *
ModifyWords(const char *str,
ModifyWordsCallback modifyWord, void *modifyWord_args,
Boolean oneBigWord, char sep)
{
- SepBuf result;
- Words words;
- size_t i;
+ SepBuf result;
+ Words words;
+ size_t i;
- if (oneBigWord) {
- SepBuf_Init(&result, sep);
- modifyWord(str, &result, modifyWord_args);
- return SepBuf_Destroy(&result, FALSE);
- }
+ if (oneBigWord) {
+ SepBuf_Init(&result, sep);
+ modifyWord(str, &result, modifyWord_args);
+ return SepBuf_Destroy(&result, FALSE);
+ }
- SepBuf_Init(&result, sep);
+ SepBuf_Init(&result, sep);
- words = Str_Words(str, FALSE);
+ words = Str_Words(str, FALSE);
- VAR_DEBUG2("ModifyWords: split \"%s\" into %zu words\n", str, words.len);
+ DEBUG2(VAR, "ModifyWords: split \"%s\" into %u words\n",
+ str, (unsigned)words.len);
- for (i = 0; i < words.len; i++) {
- modifyWord(words.words[i], &result, modifyWord_args);
- if (Buf_Len(&result.buf) > 0)
- SepBuf_Sep(&result);
- }
+ for (i = 0; i < words.len; i++) {
+ modifyWord(words.words[i], &result, modifyWord_args);
+ if (Buf_Len(&result.buf) > 0)
+ SepBuf_Sep(&result);
+ }
- Words_Free(words);
+ Words_Free(words);
- return SepBuf_Destroy(&result, FALSE);
+ return SepBuf_Destroy(&result, FALSE);
}
static char *
Words_JoinFree(Words words)
{
- Buffer buf;
- size_t i;
+ Buffer buf;
+ size_t i;
- Buf_Init(&buf);
+ Buf_Init(&buf);
- for (i = 0; i < words.len; i++) {
- if (i != 0)
- Buf_AddByte(&buf, ' '); /* XXX: st->sep, for consistency */
- Buf_AddStr(&buf, words.words[i]);
- }
+ for (i = 0; i < words.len; i++) {
+ if (i != 0) {
+ /* XXX: Use st->sep instead of ' ', for consistency. */
+ Buf_AddByte(&buf, ' ');
+ }
+ Buf_AddStr(&buf, words.words[i]);
+ }
- Words_Free(words);
+ Words_Free(words);
- return Buf_Destroy(&buf, FALSE);
+ return Buf_Destroy(&buf, FALSE);
}
/* Remove adjacent duplicate words. */
static char *
VarUniq(const char *str)
{
- Words words = Str_Words(str, FALSE);
-
- if (words.len > 1) {
- size_t i, j;
- for (j = 0, i = 1; i < words.len; i++)
- if (strcmp(words.words[i], words.words[j]) != 0 && (++j != i))
- words.words[j] = words.words[i];
- words.len = j + 1;
- }
+ Words words = Str_Words(str, FALSE);
+
+ if (words.len > 1) {
+ size_t i, j;
+ for (j = 0, i = 1; i < words.len; i++)
+ if (strcmp(words.words[i], words.words[j]) != 0 &&
+ (++j != i))
+ words.words[j] = words.words[i];
+ words.len = j + 1;
+ }
- return Words_JoinFree(words);
+ return Words_JoinFree(words);
}
-/* Quote shell meta-characters and space characters in the string.
- * If quoteDollar is set, also quote and double any '$' characters. */
+/*
+ * Quote shell meta-characters and space characters in the string.
+ * If quoteDollar is set, also quote and double any '$' characters.
+ */
static char *
VarQuote(const char *str, Boolean quoteDollar)
{
- Buffer buf;
- Buf_Init(&buf);
-
- for (; *str != '\0'; str++) {
- if (*str == '\n') {
- const char *newline = Shell_GetNewline();
- if (newline == NULL)
- newline = "\\\n";
- Buf_AddStr(&buf, newline);
- continue;
+ Buffer buf;
+ Buf_Init(&buf);
+
+ for (; *str != '\0'; str++) {
+ if (*str == '\n') {
+ const char *newline = Shell_GetNewline();
+ if (newline == NULL)
+ newline = "\\\n";
+ Buf_AddStr(&buf, newline);
+ continue;
+ }
+ if (ch_isspace(*str) || is_shell_metachar((unsigned char)*str))
+ Buf_AddByte(&buf, '\\');
+ Buf_AddByte(&buf, *str);
+ if (quoteDollar && *str == '$')
+ Buf_AddStr(&buf, "\\$");
}
- if (ch_isspace(*str) || is_shell_metachar((unsigned char)*str))
- Buf_AddByte(&buf, '\\');
- Buf_AddByte(&buf, *str);
- if (quoteDollar && *str == '$')
- Buf_AddStr(&buf, "\\$");
- }
-
- return Buf_Destroy(&buf, FALSE);
+
+ return Buf_Destroy(&buf, FALSE);
}
-/* Compute the 32-bit hash of the given string, using the MurmurHash3
- * algorithm. Output is encoded as 8 hex digits, in Little Endian order. */
+/*
+ * Compute the 32-bit hash of the given string, using the MurmurHash3
+ * algorithm. Output is encoded as 8 hex digits, in Little Endian order.
+ */
static char *
VarHash(const char *str)
{
- static const char hexdigits[16] = "0123456789abcdef";
- const unsigned char *ustr = (const unsigned char *)str;
+ static const char hexdigits[16] = "0123456789abcdef";
+ const unsigned char *ustr = (const unsigned char *)str;
- uint32_t h = 0x971e137bU;
- uint32_t c1 = 0x95543787U;
- uint32_t c2 = 0x2ad7eb25U;
- size_t len2 = strlen(str);
+ uint32_t h = 0x971e137bU;
+ uint32_t c1 = 0x95543787U;
+ uint32_t c2 = 0x2ad7eb25U;
+ size_t len2 = strlen(str);
- char *buf;
- size_t i;
+ char *buf;
+ size_t i;
- size_t len;
- for (len = len2; len; ) {
- uint32_t k = 0;
- switch (len) {
- default:
- k = ((uint32_t)ustr[3] << 24) |
- ((uint32_t)ustr[2] << 16) |
- ((uint32_t)ustr[1] << 8) |
- (uint32_t)ustr[0];
- len -= 4;
- ustr += 4;
- break;
- case 3:
- k |= (uint32_t)ustr[2] << 16;
- /* FALLTHROUGH */
- case 2:
- k |= (uint32_t)ustr[1] << 8;
- /* FALLTHROUGH */
- case 1:
- k |= (uint32_t)ustr[0];
- len = 0;
+ size_t len;
+ for (len = len2; len != 0;) {
+ uint32_t k = 0;
+ switch (len) {
+ default:
+ k = ((uint32_t)ustr[3] << 24) |
+ ((uint32_t)ustr[2] << 16) |
+ ((uint32_t)ustr[1] << 8) |
+ (uint32_t)ustr[0];
+ len -= 4;
+ ustr += 4;
+ break;
+ case 3:
+ k |= (uint32_t)ustr[2] << 16;
+ /* FALLTHROUGH */
+ case 2:
+ k |= (uint32_t)ustr[1] << 8;
+ /* FALLTHROUGH */
+ case 1:
+ k |= (uint32_t)ustr[0];
+ len = 0;
+ }
+ c1 = c1 * 5 + 0x7b7d159cU;
+ c2 = c2 * 5 + 0x6bce6396U;
+ k *= c1;
+ k = (k << 11) ^ (k >> 21);
+ k *= c2;
+ h = (h << 13) ^ (h >> 19);
+ h = h * 5 + 0x52dce729U;
+ h ^= k;
}
- c1 = c1 * 5 + 0x7b7d159cU;
- c2 = c2 * 5 + 0x6bce6396U;
- k *= c1;
- k = (k << 11) ^ (k >> 21);
- k *= c2;
- h = (h << 13) ^ (h >> 19);
- h = h * 5 + 0x52dce729U;
- h ^= k;
- }
- h ^= (uint32_t)len2;
- h *= 0x85ebca6b;
- h ^= h >> 13;
- h *= 0xc2b2ae35;
- h ^= h >> 16;
-
- buf = bmake_malloc(9);
- for (i = 0; i < 8; i++) {
- buf[i] = hexdigits[h & 0x0f];
- h >>= 4;
- }
- buf[8] = '\0';
- return buf;
+ h ^= (uint32_t)len2;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+
+ buf = bmake_malloc(9);
+ for (i = 0; i < 8; i++) {
+ buf[i] = hexdigits[h & 0x0f];
+ h >>= 4;
+ }
+ buf[8] = '\0';
+ return buf;
}
static char *
VarStrftime(const char *fmt, Boolean zulu, time_t tim)
{
- char buf[BUFSIZ];
+ char buf[BUFSIZ];
- if (tim == 0)
- time(&tim);
- if (*fmt == '\0')
- fmt = "%c";
- strftime(buf, sizeof buf, fmt, zulu ? gmtime(&tim) : localtime(&tim));
+ if (tim == 0)
+ time(&tim);
+ if (*fmt == '\0')
+ fmt = "%c";
+ strftime(buf, sizeof buf, fmt, zulu ? gmtime(&tim) : localtime(&tim));
- buf[sizeof buf - 1] = '\0';
- return bmake_strdup(buf);
+ buf[sizeof buf - 1] = '\0';
+ return bmake_strdup(buf);
}
/*
@@ -1808,12 +1978,15 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim)
*/
typedef enum VarExprFlags {
- /* The variable expression is based on an undefined variable. */
- VEF_UNDEF = 0x01,
- /* The variable expression started as an undefined expression, but one
- * of the modifiers (such as :D or :U) has turned the expression from
- * undefined to defined. */
- VEF_DEF = 0x02
+ VEF_NONE = 0,
+ /* The variable expression is based on an undefined variable. */
+ VEF_UNDEF = 0x01,
+ /*
+ * The variable expression started as an undefined expression, but one
+ * of the modifiers (such as :D or :U) has turned the expression from
+ * undefined to defined.
+ */
+ VEF_DEF = 0x02
} VarExprFlags;
ENUM_FLAGS_RTTI_2(VarExprFlags,
@@ -1821,50 +1994,187 @@ ENUM_FLAGS_RTTI_2(VarExprFlags,
typedef struct ApplyModifiersState {
- const char startc; /* '\0' or '{' or '(' */
- const char endc; /* '\0' or '}' or ')' */
- Var * const var;
- GNode * const ctxt;
- const VarEvalFlags eflags;
-
- char *val; /* The old value of the expression,
- * before applying the modifier, never NULL */
- char *newVal; /* The new value of the expression,
- * after applying the modifier, never NULL */
- char sep; /* Word separator in expansions
- * (see the :ts modifier) */
- Boolean oneBigWord; /* TRUE if some modifiers that otherwise split
- * the variable value into words, like :S and
- * :C, treat the variable value as a single big
- * word, possibly containing spaces. */
- VarExprFlags exprFlags;
+ /* '\0' or '{' or '(' */
+ const char startc;
+ /* '\0' or '}' or ')' */
+ const char endc;
+ Var *const var;
+ GNode *const ctxt;
+ const VarEvalFlags eflags;
+ /*
+ * The new value of the expression, after applying the modifier,
+ * never NULL.
+ */
+ FStr newVal;
+ /* Word separator in expansions (see the :ts modifier). */
+ char sep;
+ /*
+ * TRUE if some modifiers that otherwise split the variable value
+ * into words, like :S and :C, treat the variable value as a single
+ * big word, possibly containing spaces.
+ */
+ Boolean oneBigWord;
+ VarExprFlags exprFlags;
} ApplyModifiersState;
static void
ApplyModifiersState_Define(ApplyModifiersState *st)
{
- if (st->exprFlags & VEF_UNDEF)
- st->exprFlags |= VEF_DEF;
+ if (st->exprFlags & VEF_UNDEF)
+ st->exprFlags |= VEF_DEF;
}
typedef enum ApplyModifierResult {
- AMR_OK, /* Continue parsing */
- AMR_UNKNOWN, /* Not a match, try other modifiers as well */
- AMR_BAD, /* Error out with "Bad modifier" message */
- AMR_CLEANUP /* Error out without error message */
+ /* Continue parsing */
+ AMR_OK,
+ /* Not a match, try other modifiers as well */
+ AMR_UNKNOWN,
+ /* Error out with "Bad modifier" message */
+ AMR_BAD,
+ /* Error out without error message */
+ AMR_CLEANUP
} ApplyModifierResult;
-/* Allow backslashes to escape the delimiter, $, and \, but don't touch other
- * backslashes. */
+/*
+ * Allow backslashes to escape the delimiter, $, and \, but don't touch other
+ * backslashes.
+ */
static Boolean
IsEscapedModifierPart(const char *p, char delim,
struct ModifyWord_SubstArgs *subst)
{
- if (p[0] != '\\')
- return FALSE;
- if (p[1] == delim || p[1] == '\\' || p[1] == '$')
- return TRUE;
- return p[1] == '&' && subst != NULL;
+ if (p[0] != '\\')
+ return FALSE;
+ if (p[1] == delim || p[1] == '\\' || p[1] == '$')
+ return TRUE;
+ return p[1] == '&' && subst != NULL;
+}
+
+/* See ParseModifierPart */
+static VarParseResult
+ParseModifierPartSubst(
+ const char **pp,
+ char delim,
+ VarEvalFlags eflags,
+ ApplyModifiersState *st,
+ char **out_part,
+ /* Optionally stores the length of the returned string, just to save
+ * another strlen call. */
+ size_t *out_length,
+ /* For the first part of the :S modifier, sets the VARP_ANCHOR_END flag
+ * if the last character of the pattern is a $. */
+ VarPatternFlags *out_pflags,
+ /* For the second part of the :S modifier, allow ampersands to be
+ * escaped and replace unescaped ampersands with subst->lhs. */
+ struct ModifyWord_SubstArgs *subst
+)
+{
+ Buffer buf;
+ const char *p;
+
+ Buf_Init(&buf);
+
+ /*
+ * Skim through until the matching delimiter is found; pick up
+ * variable expressions on the way.
+ */
+ p = *pp;
+ while (*p != '\0' && *p != delim) {
+ const char *varstart;
+
+ if (IsEscapedModifierPart(p, delim, subst)) {
+ Buf_AddByte(&buf, p[1]);
+ p += 2;
+ continue;
+ }
+
+ if (*p != '$') { /* Unescaped, simple text */
+ if (subst != NULL && *p == '&')
+ Buf_AddBytes(&buf, subst->lhs, subst->lhsLen);
+ else
+ Buf_AddByte(&buf, *p);
+ p++;
+ continue;
+ }
+
+ if (p[1] == delim) { /* Unescaped $ at end of pattern */
+ if (out_pflags != NULL)
+ *out_pflags |= VARP_ANCHOR_END;
+ else
+ Buf_AddByte(&buf, *p);
+ p++;
+ continue;
+ }
+
+ if (eflags & VARE_WANTRES) { /* Nested variable, evaluated */
+ const char *nested_p = p;
+ FStr nested_val;
+ VarEvalFlags nested_eflags =
+ eflags & ~(unsigned)VARE_KEEP_DOLLAR;
+
+ (void)Var_Parse(&nested_p, st->ctxt, nested_eflags,
+ &nested_val);
+ /* TODO: handle errors */
+ Buf_AddStr(&buf, nested_val.str);
+ FStr_Done(&nested_val);
+ p += nested_p - p;
+ continue;
+ }
+
+ /*
+ * XXX: This whole block is very similar to Var_Parse without
+ * VARE_WANTRES. There may be subtle edge cases though that
+ * are not yet covered in the unit tests and that are parsed
+ * differently, depending on whether they are evaluated or
+ * not.
+ *
+ * This subtle difference is not documented in the manual
+ * page, neither is the difference between parsing :D and
+ * :M documented. No code should ever depend on these
+ * details, but who knows.
+ */
+
+ varstart = p; /* Nested variable, only parsed */
+ if (p[1] == '(' || p[1] == '{') {
+ /*
+ * Find the end of this variable reference
+ * and suck it in without further ado.
+ * It will be interpreted later.
+ */
+ char startc = p[1];
+ int endc = startc == '(' ? ')' : '}';
+ int depth = 1;
+
+ for (p += 2; *p != '\0' && depth > 0; p++) {
+ if (p[-1] != '\\') {
+ if (*p == startc)
+ depth++;
+ if (*p == endc)
+ depth--;
+ }
+ }
+ Buf_AddBytesBetween(&buf, varstart, p);
+ } else {
+ Buf_AddByte(&buf, *varstart);
+ p++;
+ }
+ }
+
+ if (*p != delim) {
+ *pp = p;
+ Error("Unfinished modifier for %s ('%c' missing)",
+ st->var->name.str, delim);
+ *out_part = NULL;
+ return VPR_ERR;
+ }
+
+ *pp = ++p;
+ if (out_length != NULL)
+ *out_length = Buf_Len(&buf);
+
+ *out_part = Buf_Destroy(&buf, FALSE);
+ DEBUG1(VAR, "Modifier part: \"%s\"\n", *out_part);
+ return VPR_OK;
}
/*
@@ -1880,1061 +2190,978 @@ IsEscapedModifierPart(const char *p, char delim,
*/
static VarParseResult
ParseModifierPart(
- const char **pp, /* The parsing position, updated upon return */
- char delim, /* Parsing stops at this delimiter */
- VarEvalFlags eflags, /* Flags for evaluating nested variables;
- * if VARE_WANTRES is not set, the text is
- * only parsed */
+ /* The parsing position, updated upon return */
+ const char **pp,
+ /* Parsing stops at this delimiter */
+ char delim,
+ /* Flags for evaluating nested variables; if VARE_WANTRES is not set,
+ * the text is only parsed. */
+ VarEvalFlags eflags,
ApplyModifiersState *st,
- char **out_part,
- size_t *out_length, /* Optionally stores the length of the returned
- * string, just to save another strlen call. */
- VarPatternFlags *out_pflags,/* For the first part of the :S modifier,
- * sets the VARP_ANCHOR_END flag if the last
- * character of the pattern is a $. */
- struct ModifyWord_SubstArgs *subst
- /* For the second part of the :S modifier,
- * allow ampersands to be escaped and replace
- * unescaped ampersands with subst->lhs. */
-) {
- Buffer buf;
- const char *p;
-
- Buf_Init(&buf);
-
- /*
- * Skim through until the matching delimiter is found; pick up variable
- * expressions on the way.
- */
- p = *pp;
- while (*p != '\0' && *p != delim) {
- const char *varstart;
-
- if (IsEscapedModifierPart(p, delim, subst)) {
- Buf_AddByte(&buf, p[1]);
- p += 2;
- continue;
- }
-
- if (*p != '$') { /* Unescaped, simple text */
- if (subst != NULL && *p == '&')
- Buf_AddBytes(&buf, subst->lhs, subst->lhsLen);
- else
- Buf_AddByte(&buf, *p);
- p++;
- continue;
- }
-
- if (p[1] == delim) { /* Unescaped $ at end of pattern */
- if (out_pflags != NULL)
- *out_pflags |= VARP_ANCHOR_END;
- else
- Buf_AddByte(&buf, *p);
- p++;
- continue;
- }
-
- if (eflags & VARE_WANTRES) { /* Nested variable, evaluated */
- const char *nested_p = p;
- const char *nested_val;
- void *nested_val_freeIt;
- VarEvalFlags nested_eflags = eflags & ~(unsigned)VARE_KEEP_DOLLAR;
-
- (void)Var_Parse(&nested_p, st->ctxt, nested_eflags,
- &nested_val, &nested_val_freeIt);
- /* TODO: handle errors */
- Buf_AddStr(&buf, nested_val);
- free(nested_val_freeIt);
- p += nested_p - p;
- continue;
- }
-
- /* XXX: This whole block is very similar to Var_Parse without
- * VARE_WANTRES. There may be subtle edge cases though that are
- * not yet covered in the unit tests and that are parsed differently,
- * depending on whether they are evaluated or not.
- *
- * This subtle difference is not documented in the manual page,
- * neither is the difference between parsing :D and :M documented.
- * No code should ever depend on these details, but who knows. */
-
- varstart = p; /* Nested variable, only parsed */
- if (p[1] == '(' || p[1] == '{') {
- /*
- * Find the end of this variable reference
- * and suck it in without further ado.
- * It will be interpreted later.
- */
- char startc = p[1];
- int endc = startc == '(' ? ')' : '}';
- int depth = 1;
-
- for (p += 2; *p != '\0' && depth > 0; p++) {
- if (p[-1] != '\\') {
- if (*p == startc)
- depth++;
- if (*p == endc)
- depth--;
- }
- }
- Buf_AddBytesBetween(&buf, varstart, p);
- } else {
- Buf_AddByte(&buf, *varstart);
- p++;
- }
- }
-
- if (*p != delim) {
- *pp = p;
- Error("Unfinished modifier for %s ('%c' missing)",
- st->var->name, delim);
- *out_part = NULL;
- return VPR_PARSE_MSG;
- }
-
- *pp = ++p;
- if (out_length != NULL)
- *out_length = Buf_Len(&buf);
-
- *out_part = Buf_Destroy(&buf, FALSE);
- VAR_DEBUG1("Modifier part: \"%s\"\n", *out_part);
- return VPR_OK;
+ char **out_part
+)
+{
+ return ParseModifierPartSubst(pp, delim, eflags, st, out_part,
+ NULL, NULL, NULL);
}
/* Test whether mod starts with modname, followed by a delimiter. */
MAKE_INLINE Boolean
ModMatch(const char *mod, const char *modname, char endc)
{
- size_t n = strlen(modname);
- return strncmp(mod, modname, n) == 0 &&
- (mod[n] == endc || mod[n] == ':');
+ size_t n = strlen(modname);
+ return strncmp(mod, modname, n) == 0 &&
+ (mod[n] == endc || mod[n] == ':');
}
/* Test whether mod starts with modname, followed by a delimiter or '='. */
MAKE_INLINE Boolean
ModMatchEq(const char *mod, const char *modname, char endc)
{
- size_t n = strlen(modname);
- return strncmp(mod, modname, n) == 0 &&
- (mod[n] == endc || mod[n] == ':' || mod[n] == '=');
+ size_t n = strlen(modname);
+ return strncmp(mod, modname, n) == 0 &&
+ (mod[n] == endc || mod[n] == ':' || mod[n] == '=');
}
static Boolean
TryParseIntBase0(const char **pp, int *out_num)
{
- char *end;
- long n;
-
- errno = 0;
- n = strtol(*pp, &end, 0);
- if ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE)
- return FALSE;
- if (n < INT_MIN || n > INT_MAX)
- return FALSE;
-
- *pp = end;
- *out_num = (int)n;
- return TRUE;
+ char *end;
+ long n;
+
+ errno = 0;
+ n = strtol(*pp, &end, 0);
+ if ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE)
+ return FALSE;
+ if (n < INT_MIN || n > INT_MAX)
+ return FALSE;
+
+ *pp = end;
+ *out_num = (int)n;
+ return TRUE;
}
static Boolean
TryParseSize(const char **pp, size_t *out_num)
{
- char *end;
- unsigned long n;
+ char *end;
+ unsigned long n;
- if (!ch_isdigit(**pp))
- return FALSE;
+ if (!ch_isdigit(**pp))
+ return FALSE;
- errno = 0;
- n = strtoul(*pp, &end, 10);
- if (n == ULONG_MAX && errno == ERANGE)
- return FALSE;
- if (n > SIZE_MAX)
- return FALSE;
+ errno = 0;
+ n = strtoul(*pp, &end, 10);
+ if (n == ULONG_MAX && errno == ERANGE)
+ return FALSE;
+ if (n > SIZE_MAX)
+ return FALSE;
- *pp = end;
- *out_num = (size_t)n;
- return TRUE;
+ *pp = end;
+ *out_num = (size_t)n;
+ return TRUE;
}
static Boolean
TryParseChar(const char **pp, int base, char *out_ch)
{
- char *end;
- unsigned long n;
+ char *end;
+ unsigned long n;
- if (!ch_isalnum(**pp))
- return FALSE;
+ if (!ch_isalnum(**pp))
+ return FALSE;
- errno = 0;
- n = strtoul(*pp, &end, base);
- if (n == ULONG_MAX && errno == ERANGE)
- return FALSE;
- if (n > UCHAR_MAX)
- return FALSE;
+ errno = 0;
+ n = strtoul(*pp, &end, base);
+ if (n == ULONG_MAX && errno == ERANGE)
+ return FALSE;
+ if (n > UCHAR_MAX)
+ return FALSE;
- *pp = end;
- *out_ch = (char)n;
- return TRUE;
+ *pp = end;
+ *out_ch = (char)n;
+ return TRUE;
}
/* :@var@...${var}...@ */
static ApplyModifierResult
-ApplyModifier_Loop(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Loop(const char **pp, const char *val, ApplyModifiersState *st)
{
- struct ModifyWord_LoopArgs args;
- char prev_sep;
- VarParseResult res;
-
- args.ctx = st->ctxt;
-
- (*pp)++; /* Skip the first '@' */
- res = ParseModifierPart(pp, '@', VARE_NONE, st,
- &args.tvar, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
- if (opts.lint && strchr(args.tvar, '$') != NULL) {
- Parse_Error(PARSE_FATAL,
+ struct ModifyWord_LoopArgs args;
+ char prev_sep;
+ VarParseResult res;
+
+ args.ctx = st->ctxt;
+
+ (*pp)++; /* Skip the first '@' */
+ res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.tvar);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+ if (opts.strict && strchr(args.tvar, '$') != NULL) {
+ Parse_Error(PARSE_FATAL,
"In the :@ modifier of \"%s\", the variable name \"%s\" "
"must not contain a dollar.",
- st->var->name, args.tvar);
- return AMR_CLEANUP;
- }
-
- res = ParseModifierPart(pp, '@', VARE_NONE, st,
- &args.str, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
-
- args.eflags = st->eflags & ~(unsigned)VARE_KEEP_DOLLAR;
- prev_sep = st->sep;
- st->sep = ' '; /* XXX: should be st->sep for consistency */
- st->newVal = ModifyWords(st->val, ModifyWord_Loop, &args,
- st->oneBigWord, st->sep);
- st->sep = prev_sep;
- /* XXX: Consider restoring the previous variable instead of deleting. */
- Var_Delete(args.tvar, st->ctxt);
- free(args.tvar);
- free(args.str);
- return AMR_OK;
+ st->var->name.str, args.tvar);
+ return AMR_CLEANUP;
+ }
+
+ res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.str);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+
+ args.eflags = st->eflags & ~(unsigned)VARE_KEEP_DOLLAR;
+ prev_sep = st->sep;
+ st->sep = ' '; /* XXX: should be st->sep for consistency */
+ st->newVal = FStr_InitOwn(
+ ModifyWords(val, ModifyWord_Loop, &args, st->oneBigWord, st->sep));
+ st->sep = prev_sep;
+ /* XXX: Consider restoring the previous variable instead of deleting. */
+ Var_Delete(args.tvar, st->ctxt);
+ free(args.tvar);
+ free(args.str);
+ return AMR_OK;
}
/* :Ddefined or :Uundefined */
static ApplyModifierResult
-ApplyModifier_Defined(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Defined(const char **pp, const char *val, ApplyModifiersState *st)
{
- Buffer buf;
- const char *p;
-
- VarEvalFlags eflags = VARE_NONE;
- if (st->eflags & VARE_WANTRES)
- if ((**pp == 'D') == !(st->exprFlags & VEF_UNDEF))
- eflags = st->eflags;
+ Buffer buf;
+ const char *p;
+
+ VarEvalFlags eflags = VARE_NONE;
+ if (st->eflags & VARE_WANTRES)
+ if ((**pp == 'D') == !(st->exprFlags & VEF_UNDEF))
+ eflags = st->eflags;
+
+ Buf_Init(&buf);
+ p = *pp + 1;
+ while (*p != st->endc && *p != ':' && *p != '\0') {
+
+ /* XXX: This code is similar to the one in Var_Parse.
+ * See if the code can be merged.
+ * See also ApplyModifier_Match. */
+
+ /* Escaped delimiter or other special character */
+ if (*p == '\\') {
+ char c = p[1];
+ if (c == st->endc || c == ':' || c == '$' ||
+ c == '\\') {
+ Buf_AddByte(&buf, c);
+ p += 2;
+ continue;
+ }
+ }
- Buf_Init(&buf);
- p = *pp + 1;
- while (*p != st->endc && *p != ':' && *p != '\0') {
+ /* Nested variable expression */
+ if (*p == '$') {
+ FStr nested_val;
- /* XXX: This code is similar to the one in Var_Parse.
- * See if the code can be merged.
- * See also ApplyModifier_Match. */
+ (void)Var_Parse(&p, st->ctxt, eflags, &nested_val);
+ /* TODO: handle errors */
+ Buf_AddStr(&buf, nested_val.str);
+ FStr_Done(&nested_val);
+ continue;
+ }
- /* Escaped delimiter or other special character */
- if (*p == '\\') {
- char c = p[1];
- if (c == st->endc || c == ':' || c == '$' || c == '\\') {
- Buf_AddByte(&buf, c);
- p += 2;
- continue;
- }
+ /* Ordinary text */
+ Buf_AddByte(&buf, *p);
+ p++;
}
+ *pp = p;
- /* Nested variable expression */
- if (*p == '$') {
- const char *nested_val;
- void *nested_val_freeIt;
-
- (void)Var_Parse(&p, st->ctxt, eflags,
- &nested_val, &nested_val_freeIt);
- /* TODO: handle errors */
- Buf_AddStr(&buf, nested_val);
- free(nested_val_freeIt);
- continue;
- }
+ ApplyModifiersState_Define(st);
- /* Ordinary text */
- Buf_AddByte(&buf, *p);
- p++;
- }
- *pp = p;
-
- ApplyModifiersState_Define(st);
-
- if (eflags & VARE_WANTRES) {
- st->newVal = Buf_Destroy(&buf, FALSE);
- } else {
- st->newVal = st->val;
- Buf_Destroy(&buf, TRUE);
- }
- return AMR_OK;
+ if (eflags & VARE_WANTRES) {
+ st->newVal = FStr_InitOwn(Buf_Destroy(&buf, FALSE));
+ } else {
+ st->newVal = FStr_InitRefer(val);
+ Buf_Destroy(&buf, TRUE);
+ }
+ return AMR_OK;
}
/* :L */
static ApplyModifierResult
ApplyModifier_Literal(const char **pp, ApplyModifiersState *st)
{
- ApplyModifiersState_Define(st);
- st->newVal = bmake_strdup(st->var->name);
- (*pp)++;
- return AMR_OK;
+ ApplyModifiersState_Define(st);
+ st->newVal = FStr_InitOwn(bmake_strdup(st->var->name.str));
+ (*pp)++;
+ return AMR_OK;
}
static Boolean
TryParseTime(const char **pp, time_t *out_time)
{
- char *end;
- unsigned long n;
+ char *end;
+ unsigned long n;
- if (!ch_isdigit(**pp))
- return FALSE;
+ if (!ch_isdigit(**pp))
+ return FALSE;
- errno = 0;
- n = strtoul(*pp, &end, 10);
- if (n == ULONG_MAX && errno == ERANGE)
- return FALSE;
+ errno = 0;
+ n = strtoul(*pp, &end, 10);
+ if (n == ULONG_MAX && errno == ERANGE)
+ return FALSE;
- *pp = end;
- *out_time = (time_t)n; /* ignore possible truncation for now */
- return TRUE;
+ *pp = end;
+ *out_time = (time_t)n; /* ignore possible truncation for now */
+ return TRUE;
}
/* :gmtime */
static ApplyModifierResult
-ApplyModifier_Gmtime(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Gmtime(const char **pp, const char *val, ApplyModifiersState *st)
{
- time_t utc;
-
- const char *mod = *pp;
- if (!ModMatchEq(mod, "gmtime", st->endc))
- return AMR_UNKNOWN;
-
- if (mod[6] == '=') {
- const char *arg = mod + 7;
- if (!TryParseTime(&arg, &utc)) {
- Parse_Error(PARSE_FATAL, "Invalid time value: %s\n", mod + 7);
- return AMR_CLEANUP;
+ time_t utc;
+
+ const char *mod = *pp;
+ if (!ModMatchEq(mod, "gmtime", st->endc))
+ return AMR_UNKNOWN;
+
+ if (mod[6] == '=') {
+ const char *arg = mod + 7;
+ if (!TryParseTime(&arg, &utc)) {
+ Parse_Error(PARSE_FATAL,
+ "Invalid time value: %s", mod + 7);
+ return AMR_CLEANUP;
+ }
+ *pp = arg;
+ } else {
+ utc = 0;
+ *pp = mod + 6;
}
- *pp = arg;
- } else {
- utc = 0;
- *pp = mod + 6;
- }
- st->newVal = VarStrftime(st->val, TRUE, utc);
- return AMR_OK;
+ st->newVal = FStr_InitOwn(VarStrftime(val, TRUE, utc));
+ return AMR_OK;
}
/* :localtime */
static ApplyModifierResult
-ApplyModifier_Localtime(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Localtime(const char **pp, const char *val,
+ ApplyModifiersState *st)
{
- time_t utc;
-
- const char *mod = *pp;
- if (!ModMatchEq(mod, "localtime", st->endc))
- return AMR_UNKNOWN;
-
- if (mod[9] == '=') {
- const char *arg = mod + 10;
- if (!TryParseTime(&arg, &utc)) {
- Parse_Error(PARSE_FATAL, "Invalid time value: %s\n", mod + 10);
- return AMR_CLEANUP;
+ time_t utc;
+
+ const char *mod = *pp;
+ if (!ModMatchEq(mod, "localtime", st->endc))
+ return AMR_UNKNOWN;
+
+ if (mod[9] == '=') {
+ const char *arg = mod + 10;
+ if (!TryParseTime(&arg, &utc)) {
+ Parse_Error(PARSE_FATAL,
+ "Invalid time value: %s", mod + 10);
+ return AMR_CLEANUP;
+ }
+ *pp = arg;
+ } else {
+ utc = 0;
+ *pp = mod + 9;
}
- *pp = arg;
- } else {
- utc = 0;
- *pp = mod + 9;
- }
- st->newVal = VarStrftime(st->val, FALSE, utc);
- return AMR_OK;
+ st->newVal = FStr_InitOwn(VarStrftime(val, FALSE, utc));
+ return AMR_OK;
}
/* :hash */
static ApplyModifierResult
-ApplyModifier_Hash(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Hash(const char **pp, const char *val, ApplyModifiersState *st)
{
- if (!ModMatch(*pp, "hash", st->endc))
- return AMR_UNKNOWN;
+ if (!ModMatch(*pp, "hash", st->endc))
+ return AMR_UNKNOWN;
- st->newVal = VarHash(st->val);
- *pp += 4;
- return AMR_OK;
+ st->newVal = FStr_InitOwn(VarHash(val));
+ *pp += 4;
+ return AMR_OK;
}
/* :P */
static ApplyModifierResult
ApplyModifier_Path(const char **pp, ApplyModifiersState *st)
{
- GNode *gn;
- char *path;
-
- ApplyModifiersState_Define(st);
-
- gn = Targ_FindNode(st->var->name);
- if (gn == NULL || gn->type & OP_NOPATH) {
- path = NULL;
- } else if (gn->path != NULL) {
- path = bmake_strdup(gn->path);
- } else {
- SearchPath *searchPath = Suff_FindPath(gn);
- path = Dir_FindFile(st->var->name, searchPath);
- }
- if (path == NULL)
- path = bmake_strdup(st->var->name);
- st->newVal = path;
-
- (*pp)++;
- return AMR_OK;
+ GNode *gn;
+ char *path;
+
+ ApplyModifiersState_Define(st);
+
+ gn = Targ_FindNode(st->var->name.str);
+ if (gn == NULL || gn->type & OP_NOPATH) {
+ path = NULL;
+ } else if (gn->path != NULL) {
+ path = bmake_strdup(gn->path);
+ } else {
+ SearchPath *searchPath = Suff_FindPath(gn);
+ path = Dir_FindFile(st->var->name.str, searchPath);
+ }
+ if (path == NULL)
+ path = bmake_strdup(st->var->name.str);
+ st->newVal = FStr_InitOwn(path);
+
+ (*pp)++;
+ return AMR_OK;
}
/* :!cmd! */
static ApplyModifierResult
ApplyModifier_ShellCommand(const char **pp, ApplyModifiersState *st)
{
- char *cmd;
- const char *errfmt;
- VarParseResult res;
-
- (*pp)++;
- res = ParseModifierPart(pp, '!', st->eflags, st,
- &cmd, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
-
- errfmt = NULL;
- if (st->eflags & VARE_WANTRES)
- st->newVal = Cmd_Exec(cmd, &errfmt);
- else
- st->newVal = bmake_strdup("");
- if (errfmt != NULL)
- Error(errfmt, cmd); /* XXX: why still return AMR_OK? */
- free(cmd);
-
- ApplyModifiersState_Define(st);
- return AMR_OK;
+ char *cmd;
+ const char *errfmt;
+ VarParseResult res;
+
+ (*pp)++;
+ res = ParseModifierPart(pp, '!', st->eflags, st, &cmd);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+
+ errfmt = NULL;
+ if (st->eflags & VARE_WANTRES)
+ st->newVal = FStr_InitOwn(Cmd_Exec(cmd, &errfmt));
+ else
+ st->newVal = FStr_InitRefer("");
+ if (errfmt != NULL)
+ Error(errfmt, cmd); /* XXX: why still return AMR_OK? */
+ free(cmd);
+
+ ApplyModifiersState_Define(st);
+ return AMR_OK;
}
-/* The :range modifier generates an integer sequence as long as the words.
- * The :range=7 modifier generates an integer sequence from 1 to 7. */
+/*
+ * The :range modifier generates an integer sequence as long as the words.
+ * The :range=7 modifier generates an integer sequence from 1 to 7.
+ */
static ApplyModifierResult
-ApplyModifier_Range(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Range(const char **pp, const char *val, ApplyModifiersState *st)
{
- size_t n;
- Buffer buf;
- size_t i;
-
- const char *mod = *pp;
- if (!ModMatchEq(mod, "range", st->endc))
- return AMR_UNKNOWN;
-
- if (mod[5] == '=') {
- const char *p = mod + 6;
- if (!TryParseSize(&p, &n)) {
- Parse_Error(PARSE_FATAL, "Invalid number: %s\n", mod + 6);
- return AMR_CLEANUP;
+ size_t n;
+ Buffer buf;
+ size_t i;
+
+ const char *mod = *pp;
+ if (!ModMatchEq(mod, "range", st->endc))
+ return AMR_UNKNOWN;
+
+ if (mod[5] == '=') {
+ const char *p = mod + 6;
+ if (!TryParseSize(&p, &n)) {
+ Parse_Error(PARSE_FATAL,
+ "Invalid number: %s", mod + 6);
+ return AMR_CLEANUP;
+ }
+ *pp = p;
+ } else {
+ n = 0;
+ *pp = mod + 5;
}
- *pp = p;
- } else {
- n = 0;
- *pp = mod + 5;
- }
-
- if (n == 0) {
- Words words = Str_Words(st->val, FALSE);
- n = words.len;
- Words_Free(words);
- }
- Buf_Init(&buf);
+ if (n == 0) {
+ Words words = Str_Words(val, FALSE);
+ n = words.len;
+ Words_Free(words);
+ }
- for (i = 0; i < n; i++) {
- if (i != 0)
- Buf_AddByte(&buf, ' '); /* XXX: st->sep, for consistency */
- Buf_AddInt(&buf, 1 + (int)i);
- }
+ Buf_Init(&buf);
- st->newVal = Buf_Destroy(&buf, FALSE);
- return AMR_OK;
+ for (i = 0; i < n; i++) {
+ if (i != 0) {
+ /* XXX: Use st->sep instead of ' ', for consistency. */
+ Buf_AddByte(&buf, ' ');
+ }
+ Buf_AddInt(&buf, 1 + (int)i);
+ }
+
+ st->newVal = FStr_InitOwn(Buf_Destroy(&buf, FALSE));
+ return AMR_OK;
}
/* :Mpattern or :Npattern */
static ApplyModifierResult
-ApplyModifier_Match(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Match(const char **pp, const char *val, ApplyModifiersState *st)
{
- const char *mod = *pp;
- Boolean copy = FALSE; /* pattern should be, or has been, copied */
- Boolean needSubst = FALSE;
- const char *endpat;
- char *pattern;
- ModifyWordsCallback callback;
-
- /*
- * In the loop below, ignore ':' unless we are at (or back to) the
- * original brace level.
- * XXX: This will likely not work right if $() and ${} are intermixed.
- */
- /* XXX: This code is similar to the one in Var_Parse.
- * See if the code can be merged.
- * See also ApplyModifier_Defined. */
- int nest = 0;
- const char *p;
- for (p = mod + 1; *p != '\0' && !(*p == ':' && nest == 0); p++) {
- if (*p == '\\' &&
- (p[1] == ':' || p[1] == st->endc || p[1] == st->startc)) {
- if (!needSubst)
- copy = TRUE;
- p++;
- continue;
+ const char *mod = *pp;
+ Boolean copy = FALSE; /* pattern should be, or has been, copied */
+ Boolean needSubst = FALSE;
+ const char *endpat;
+ char *pattern;
+ ModifyWordsCallback callback;
+
+ /*
+ * In the loop below, ignore ':' unless we are at (or back to) the
+ * original brace level.
+ * XXX: This will likely not work right if $() and ${} are intermixed.
+ */
+ /* XXX: This code is similar to the one in Var_Parse.
+ * See if the code can be merged.
+ * See also ApplyModifier_Defined. */
+ int nest = 0;
+ const char *p;
+ for (p = mod + 1; *p != '\0' && !(*p == ':' && nest == 0); p++) {
+ if (*p == '\\' &&
+ (p[1] == ':' || p[1] == st->endc || p[1] == st->startc)) {
+ if (!needSubst)
+ copy = TRUE;
+ p++;
+ continue;
+ }
+ if (*p == '$')
+ needSubst = TRUE;
+ if (*p == '(' || *p == '{')
+ nest++;
+ if (*p == ')' || *p == '}') {
+ nest--;
+ if (nest < 0)
+ break;
+ }
}
- if (*p == '$')
- needSubst = TRUE;
- if (*p == '(' || *p == '{')
- nest++;
- if (*p == ')' || *p == '}') {
- nest--;
- if (nest < 0)
- break;
+ *pp = p;
+ endpat = p;
+
+ if (copy) {
+ char *dst;
+ const char *src;
+
+ /* Compress the \:'s out of the pattern. */
+ pattern = bmake_malloc((size_t)(endpat - (mod + 1)) + 1);
+ dst = pattern;
+ src = mod + 1;
+ for (; src < endpat; src++, dst++) {
+ if (src[0] == '\\' && src + 1 < endpat &&
+ /* XXX: st->startc is missing here; see above */
+ (src[1] == ':' || src[1] == st->endc))
+ src++;
+ *dst = *src;
+ }
+ *dst = '\0';
+ } else {
+ pattern = bmake_strsedup(mod + 1, endpat);
}
- }
- *pp = p;
- endpat = p;
-
- if (copy) {
- char *dst;
- const char *src;
-
- /* Compress the \:'s out of the pattern. */
- pattern = bmake_malloc((size_t)(endpat - (mod + 1)) + 1);
- dst = pattern;
- src = mod + 1;
- for (; src < endpat; src++, dst++) {
- if (src[0] == '\\' && src + 1 < endpat &&
- /* XXX: st->startc is missing here; see above */
- (src[1] == ':' || src[1] == st->endc))
- src++;
- *dst = *src;
+
+ if (needSubst) {
+ char *old_pattern = pattern;
+ (void)Var_Subst(pattern, st->ctxt, st->eflags, &pattern);
+ /* TODO: handle errors */
+ free(old_pattern);
}
- *dst = '\0';
- endpat = dst;
- } else {
- pattern = bmake_strsedup(mod + 1, endpat);
- }
-
- if (needSubst) {
- /* pattern contains embedded '$', so use Var_Subst to expand it. */
- char *old_pattern = pattern;
- (void)Var_Subst(pattern, st->ctxt, st->eflags, &pattern);
- /* TODO: handle errors */
- free(old_pattern);
- }
- VAR_DEBUG3("Pattern[%s] for [%s] is [%s]\n",
- st->var->name, st->val, pattern);
+ DEBUG3(VAR, "Pattern[%s] for [%s] is [%s]\n",
+ st->var->name.str, val, pattern);
- callback = mod[0] == 'M' ? ModifyWord_Match : ModifyWord_NoMatch;
- st->newVal = ModifyWords(st->val, callback, pattern,
- st->oneBigWord, st->sep);
- free(pattern);
- return AMR_OK;
+ callback = mod[0] == 'M' ? ModifyWord_Match : ModifyWord_NoMatch;
+ st->newVal = FStr_InitOwn(ModifyWords(val, callback, pattern,
+ st->oneBigWord, st->sep));
+ free(pattern);
+ return AMR_OK;
}
/* :S,from,to, */
static ApplyModifierResult
-ApplyModifier_Subst(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Subst(const char **pp, const char *val, ApplyModifiersState *st)
{
- struct ModifyWord_SubstArgs args;
- char *lhs, *rhs;
- Boolean oneBigWord;
- VarParseResult res;
-
- char delim = (*pp)[1];
- if (delim == '\0') {
- Error("Missing delimiter for :S modifier");
- (*pp)++;
- return AMR_CLEANUP;
- }
+ struct ModifyWord_SubstArgs args;
+ char *lhs, *rhs;
+ Boolean oneBigWord;
+ VarParseResult res;
- *pp += 2;
+ char delim = (*pp)[1];
+ if (delim == '\0') {
+ Error("Missing delimiter for :S modifier");
+ (*pp)++;
+ return AMR_CLEANUP;
+ }
- args.pflags = 0;
- args.matched = FALSE;
+ *pp += 2;
- /*
- * If pattern begins with '^', it is anchored to the
- * start of the word -- skip over it and flag pattern.
- */
- if (**pp == '^') {
- args.pflags |= VARP_ANCHOR_START;
- (*pp)++;
- }
-
- res = ParseModifierPart(pp, delim, st->eflags, st,
- &lhs, &args.lhsLen, &args.pflags, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
- args.lhs = lhs;
-
- res = ParseModifierPart(pp, delim, st->eflags, st,
- &rhs, &args.rhsLen, NULL, &args);
- if (res != VPR_OK)
- return AMR_CLEANUP;
- args.rhs = rhs;
-
- oneBigWord = st->oneBigWord;
- for (;; (*pp)++) {
- switch (**pp) {
- case 'g':
- args.pflags |= VARP_SUB_GLOBAL;
- continue;
- case '1':
- args.pflags |= VARP_SUB_ONE;
- continue;
- case 'W':
- oneBigWord = TRUE;
- continue;
+ args.pflags = VARP_NONE;
+ args.matched = FALSE;
+
+ /*
+ * If pattern begins with '^', it is anchored to the
+ * start of the word -- skip over it and flag pattern.
+ */
+ if (**pp == '^') {
+ args.pflags |= VARP_ANCHOR_START;
+ (*pp)++;
}
- break;
- }
- st->newVal = ModifyWords(st->val, ModifyWord_Subst, &args,
- oneBigWord, st->sep);
+ res = ParseModifierPartSubst(pp, delim, st->eflags, st, &lhs,
+ &args.lhsLen, &args.pflags, NULL);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+ args.lhs = lhs;
+
+ res = ParseModifierPartSubst(pp, delim, st->eflags, st, &rhs,
+ &args.rhsLen, NULL, &args);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+ args.rhs = rhs;
+
+ oneBigWord = st->oneBigWord;
+ for (;; (*pp)++) {
+ switch (**pp) {
+ case 'g':
+ args.pflags |= VARP_SUB_GLOBAL;
+ continue;
+ case '1':
+ args.pflags |= VARP_SUB_ONE;
+ continue;
+ case 'W':
+ oneBigWord = TRUE;
+ continue;
+ }
+ break;
+ }
- free(lhs);
- free(rhs);
- return AMR_OK;
+ st->newVal = FStr_InitOwn(ModifyWords(val, ModifyWord_Subst, &args,
+ oneBigWord, st->sep));
+
+ free(lhs);
+ free(rhs);
+ return AMR_OK;
}
#ifndef NO_REGEX
/* :C,from,to, */
static ApplyModifierResult
-ApplyModifier_Regex(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Regex(const char **pp, const char *val, ApplyModifiersState *st)
{
- char *re;
- struct ModifyWord_SubstRegexArgs args;
- Boolean oneBigWord;
- int error;
- VarParseResult res;
-
- char delim = (*pp)[1];
- if (delim == '\0') {
- Error("Missing delimiter for :C modifier");
- (*pp)++;
- return AMR_CLEANUP;
- }
+ char *re;
+ struct ModifyWord_SubstRegexArgs args;
+ Boolean oneBigWord;
+ int error;
+ VarParseResult res;
- *pp += 2;
+ char delim = (*pp)[1];
+ if (delim == '\0') {
+ Error("Missing delimiter for :C modifier");
+ (*pp)++;
+ return AMR_CLEANUP;
+ }
- res = ParseModifierPart(pp, delim, st->eflags, st,
- &re, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
+ *pp += 2;
- res = ParseModifierPart(pp, delim, st->eflags, st,
- &args.replace, NULL, NULL, NULL);
- if (args.replace == NULL) {
- free(re);
- return AMR_CLEANUP;
- }
+ res = ParseModifierPart(pp, delim, st->eflags, st, &re);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
- args.pflags = 0;
- args.matched = FALSE;
- oneBigWord = st->oneBigWord;
- for (;; (*pp)++) {
- switch (**pp) {
- case 'g':
- args.pflags |= VARP_SUB_GLOBAL;
- continue;
- case '1':
- args.pflags |= VARP_SUB_ONE;
- continue;
- case 'W':
- oneBigWord = TRUE;
- continue;
+ res = ParseModifierPart(pp, delim, st->eflags, st, &args.replace);
+ if (args.replace == NULL) {
+ free(re);
+ return AMR_CLEANUP;
+ }
+
+ args.pflags = VARP_NONE;
+ args.matched = FALSE;
+ oneBigWord = st->oneBigWord;
+ for (;; (*pp)++) {
+ switch (**pp) {
+ case 'g':
+ args.pflags |= VARP_SUB_GLOBAL;
+ continue;
+ case '1':
+ args.pflags |= VARP_SUB_ONE;
+ continue;
+ case 'W':
+ oneBigWord = TRUE;
+ continue;
+ }
+ break;
+ }
+
+ error = regcomp(&args.re, re, REG_EXTENDED);
+ free(re);
+ if (error != 0) {
+ VarREError(error, &args.re, "Regex compilation error");
+ free(args.replace);
+ return AMR_CLEANUP;
}
- break;
- }
- error = regcomp(&args.re, re, REG_EXTENDED);
- free(re);
- if (error) {
- VarREError(error, &args.re, "Regex compilation error");
+ args.nsub = args.re.re_nsub + 1;
+ if (args.nsub > 10)
+ args.nsub = 10;
+ st->newVal = FStr_InitOwn(
+ ModifyWords(val, ModifyWord_SubstRegex, &args,
+ oneBigWord, st->sep));
+ regfree(&args.re);
free(args.replace);
- return AMR_CLEANUP;
- }
-
- args.nsub = args.re.re_nsub + 1;
- if (args.nsub > 10)
- args.nsub = 10;
- st->newVal = ModifyWords(st->val, ModifyWord_SubstRegex, &args,
- oneBigWord, st->sep);
- regfree(&args.re);
- free(args.replace);
- return AMR_OK;
+ return AMR_OK;
}
+
#endif
/* :Q, :q */
static ApplyModifierResult
-ApplyModifier_Quote(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Quote(const char **pp, const char *val, ApplyModifiersState *st)
{
- if ((*pp)[1] == st->endc || (*pp)[1] == ':') {
- st->newVal = VarQuote(st->val, **pp == 'q');
- (*pp)++;
- return AMR_OK;
- } else
- return AMR_UNKNOWN;
+ if ((*pp)[1] == st->endc || (*pp)[1] == ':') {
+ st->newVal = FStr_InitOwn(VarQuote(val, **pp == 'q'));
+ (*pp)++;
+ return AMR_OK;
+ } else
+ return AMR_UNKNOWN;
}
+/*ARGSUSED*/
static void
ModifyWord_Copy(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED)
{
- SepBuf_AddStr(buf, word);
+ SepBuf_AddStr(buf, word);
}
/* :ts<separator> */
static ApplyModifierResult
-ApplyModifier_ToSep(const char **pp, ApplyModifiersState *st)
+ApplyModifier_ToSep(const char **pp, const char *val, ApplyModifiersState *st)
{
- const char *sep = *pp + 2;
-
- /* ":ts<any><endc>" or ":ts<any>:" */
- if (sep[0] != st->endc && (sep[1] == st->endc || sep[1] == ':')) {
- st->sep = sep[0];
- *pp = sep + 1;
- goto ok;
- }
-
- /* ":ts<endc>" or ":ts:" */
- if (sep[0] == st->endc || sep[0] == ':') {
- st->sep = '\0'; /* no separator */
- *pp = sep;
- goto ok;
- }
-
- /* ":ts<unrecognised><unrecognised>". */
- if (sep[0] != '\\') {
- (*pp)++; /* just for backwards compatibility */
- return AMR_BAD;
- }
-
- /* ":ts\n" */
- if (sep[1] == 'n') {
- st->sep = '\n';
- *pp = sep + 2;
- goto ok;
- }
-
- /* ":ts\t" */
- if (sep[1] == 't') {
- st->sep = '\t';
- *pp = sep + 2;
- goto ok;
- }
-
- /* ":ts\x40" or ":ts\100" */
- {
- const char *p = sep + 1;
- int base = 8; /* assume octal */
-
- if (sep[1] == 'x') {
- base = 16;
- p++;
- } else if (!ch_isdigit(sep[1])) {
- (*pp)++; /* just for backwards compatibility */
- return AMR_BAD; /* ":ts<backslash><unrecognised>". */
+ const char *sep = *pp + 2;
+
+ /* ":ts<any><endc>" or ":ts<any>:" */
+ if (sep[0] != st->endc && (sep[1] == st->endc || sep[1] == ':')) {
+ st->sep = sep[0];
+ *pp = sep + 1;
+ goto ok;
}
- if (!TryParseChar(&p, base, &st->sep)) {
- Parse_Error(PARSE_FATAL, "Invalid character number: %s\n", p);
- return AMR_CLEANUP;
+ /* ":ts<endc>" or ":ts:" */
+ if (sep[0] == st->endc || sep[0] == ':') {
+ st->sep = '\0'; /* no separator */
+ *pp = sep;
+ goto ok;
}
- if (*p != ':' && *p != st->endc) {
- (*pp)++; /* just for backwards compatibility */
- return AMR_BAD;
+
+ /* ":ts<unrecognised><unrecognised>". */
+ if (sep[0] != '\\') {
+ (*pp)++; /* just for backwards compatibility */
+ return AMR_BAD;
}
- *pp = p;
- }
+ /* ":ts\n" */
+ if (sep[1] == 'n') {
+ st->sep = '\n';
+ *pp = sep + 2;
+ goto ok;
+ }
+
+ /* ":ts\t" */
+ if (sep[1] == 't') {
+ st->sep = '\t';
+ *pp = sep + 2;
+ goto ok;
+ }
+
+ /* ":ts\x40" or ":ts\100" */
+ {
+ const char *p = sep + 1;
+ int base = 8; /* assume octal */
+
+ if (sep[1] == 'x') {
+ base = 16;
+ p++;
+ } else if (!ch_isdigit(sep[1])) {
+ (*pp)++; /* just for backwards compatibility */
+ return AMR_BAD; /* ":ts<backslash><unrecognised>". */
+ }
+
+ if (!TryParseChar(&p, base, &st->sep)) {
+ Parse_Error(PARSE_FATAL,
+ "Invalid character number: %s", p);
+ return AMR_CLEANUP;
+ }
+ if (*p != ':' && *p != st->endc) {
+ (*pp)++; /* just for backwards compatibility */
+ return AMR_BAD;
+ }
+
+ *pp = p;
+ }
ok:
- st->newVal = ModifyWords(st->val, ModifyWord_Copy, NULL,
- st->oneBigWord, st->sep);
- return AMR_OK;
+ st->newVal = FStr_InitOwn(
+ ModifyWords(val, ModifyWord_Copy, NULL, st->oneBigWord, st->sep));
+ return AMR_OK;
+}
+
+static char *
+str_toupper(const char *str)
+{
+ char *res;
+ size_t i, len;
+
+ len = strlen(str);
+ res = bmake_malloc(len + 1);
+ for (i = 0; i < len + 1; i++)
+ res[i] = ch_toupper(str[i]);
+
+ return res;
+}
+
+static char *
+str_tolower(const char *str)
+{
+ char *res;
+ size_t i, len;
+
+ len = strlen(str);
+ res = bmake_malloc(len + 1);
+ for (i = 0; i < len + 1; i++)
+ res[i] = ch_tolower(str[i]);
+
+ return res;
}
/* :tA, :tu, :tl, :ts<separator>, etc. */
static ApplyModifierResult
-ApplyModifier_To(const char **pp, ApplyModifiersState *st)
+ApplyModifier_To(const char **pp, const char *val, ApplyModifiersState *st)
{
- const char *mod = *pp;
- assert(mod[0] == 't');
+ const char *mod = *pp;
+ assert(mod[0] == 't');
- if (mod[1] == st->endc || mod[1] == ':' || mod[1] == '\0') {
- *pp = mod + 1;
- return AMR_BAD; /* Found ":t<endc>" or ":t:". */
- }
+ if (mod[1] == st->endc || mod[1] == ':' || mod[1] == '\0') {
+ *pp = mod + 1;
+ return AMR_BAD; /* Found ":t<endc>" or ":t:". */
+ }
- if (mod[1] == 's')
- return ApplyModifier_ToSep(pp, st);
+ if (mod[1] == 's')
+ return ApplyModifier_ToSep(pp, val, st);
- if (mod[2] != st->endc && mod[2] != ':') {
- *pp = mod + 1;
- return AMR_BAD; /* Found ":t<unrecognised><unrecognised>". */
- }
-
- /* Check for two-character options: ":tu", ":tl" */
- if (mod[1] == 'A') { /* absolute path */
- st->newVal = ModifyWords(st->val, ModifyWord_Realpath, NULL,
- st->oneBigWord, st->sep);
- *pp = mod + 2;
- return AMR_OK;
- }
+ if (mod[2] != st->endc && mod[2] != ':') {
+ *pp = mod + 1;
+ return AMR_BAD; /* Found ":t<unrecognised><unrecognised>". */
+ }
- if (mod[1] == 'u') { /* :tu */
- size_t i;
- size_t len = strlen(st->val);
- st->newVal = bmake_malloc(len + 1);
- for (i = 0; i < len + 1; i++)
- st->newVal[i] = ch_toupper(st->val[i]);
- *pp = mod + 2;
- return AMR_OK;
- }
+ /* Check for two-character options: ":tu", ":tl" */
+ if (mod[1] == 'A') { /* absolute path */
+ st->newVal = FStr_InitOwn(
+ ModifyWords(val, ModifyWord_Realpath, NULL,
+ st->oneBigWord, st->sep));
+ *pp = mod + 2;
+ return AMR_OK;
+ }
- if (mod[1] == 'l') { /* :tl */
- size_t i;
- size_t len = strlen(st->val);
- st->newVal = bmake_malloc(len + 1);
- for (i = 0; i < len + 1; i++)
- st->newVal[i] = ch_tolower(st->val[i]);
- *pp = mod + 2;
- return AMR_OK;
- }
+ if (mod[1] == 'u') { /* :tu */
+ st->newVal = FStr_InitOwn(str_toupper(val));
+ *pp = mod + 2;
+ return AMR_OK;
+ }
- if (mod[1] == 'W' || mod[1] == 'w') { /* :tW, :tw */
- st->oneBigWord = mod[1] == 'W';
- st->newVal = st->val;
- *pp = mod + 2;
- return AMR_OK;
- }
+ if (mod[1] == 'l') { /* :tl */
+ st->newVal = FStr_InitOwn(str_tolower(val));
+ *pp = mod + 2;
+ return AMR_OK;
+ }
+
+ if (mod[1] == 'W' || mod[1] == 'w') { /* :tW, :tw */
+ st->oneBigWord = mod[1] == 'W';
+ st->newVal = FStr_InitRefer(val);
+ *pp = mod + 2;
+ return AMR_OK;
+ }
- /* Found ":t<unrecognised>:" or ":t<unrecognised><endc>". */
- *pp = mod + 1;
- return AMR_BAD;
+ /* Found ":t<unrecognised>:" or ":t<unrecognised><endc>". */
+ *pp = mod + 1;
+ return AMR_BAD;
}
/* :[#], :[1], :[-1..1], etc. */
static ApplyModifierResult
-ApplyModifier_Words(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Words(const char **pp, const char *val, ApplyModifiersState *st)
{
- char *estr;
- int first, last;
- VarParseResult res;
- const char *p;
-
- (*pp)++; /* skip the '[' */
- res = ParseModifierPart(pp, ']', st->eflags, st,
- &estr, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
-
- /* now *pp points just after the closing ']' */
- if (**pp != ':' && **pp != st->endc)
- goto bad_modifier; /* Found junk after ']' */
-
- if (estr[0] == '\0')
- goto bad_modifier; /* empty square brackets in ":[]". */
-
- if (estr[0] == '#' && estr[1] == '\0') { /* Found ":[#]" */
- if (st->oneBigWord) {
- st->newVal = bmake_strdup("1");
- } else {
- Buffer buf;
+ char *estr;
+ int first, last;
+ VarParseResult res;
+ const char *p;
+
+ (*pp)++; /* skip the '[' */
+ res = ParseModifierPart(pp, ']', st->eflags, st, &estr);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+
+ /* now *pp points just after the closing ']' */
+ if (**pp != ':' && **pp != st->endc)
+ goto bad_modifier; /* Found junk after ']' */
+
+ if (estr[0] == '\0')
+ goto bad_modifier; /* empty square brackets in ":[]". */
+
+ if (estr[0] == '#' && estr[1] == '\0') { /* Found ":[#]" */
+ if (st->oneBigWord) {
+ st->newVal = FStr_InitRefer("1");
+ } else {
+ Buffer buf;
+
+ Words words = Str_Words(val, FALSE);
+ size_t ac = words.len;
+ Words_Free(words);
+
+ /* 3 digits + '\0' is usually enough */
+ Buf_InitSize(&buf, 4);
+ Buf_AddInt(&buf, (int)ac);
+ st->newVal = FStr_InitOwn(Buf_Destroy(&buf, FALSE));
+ }
+ goto ok;
+ }
+
+ if (estr[0] == '*' && estr[1] == '\0') {
+ /* Found ":[*]" */
+ st->oneBigWord = TRUE;
+ st->newVal = FStr_InitRefer(val);
+ goto ok;
+ }
- Words words = Str_Words(st->val, FALSE);
- size_t ac = words.len;
- Words_Free(words);
+ if (estr[0] == '@' && estr[1] == '\0') {
+ /* Found ":[@]" */
+ st->oneBigWord = FALSE;
+ st->newVal = FStr_InitRefer(val);
+ goto ok;
+ }
+
+ /*
+ * We expect estr to contain a single integer for :[N], or two
+ * integers separated by ".." for :[start..end].
+ */
+ p = estr;
+ if (!TryParseIntBase0(&p, &first))
+ goto bad_modifier; /* Found junk instead of a number */
+
+ if (p[0] == '\0') { /* Found only one integer in :[N] */
+ last = first;
+ } else if (p[0] == '.' && p[1] == '.' && p[2] != '\0') {
+ /* Expecting another integer after ".." */
+ p += 2;
+ if (!TryParseIntBase0(&p, &last) || *p != '\0')
+ goto bad_modifier; /* Found junk after ".." */
+ } else
+ goto bad_modifier; /* Found junk instead of ".." */
- Buf_InitSize(&buf, 4); /* 3 digits + '\0' is usually enough */
- Buf_AddInt(&buf, (int)ac);
- st->newVal = Buf_Destroy(&buf, FALSE);
+ /*
+ * Now first and last are properly filled in, but we still have to
+ * check for 0 as a special case.
+ */
+ if (first == 0 && last == 0) {
+ /* ":[0]" or perhaps ":[0..0]" */
+ st->oneBigWord = TRUE;
+ st->newVal = FStr_InitRefer(val);
+ goto ok;
}
- goto ok;
- }
-
- if (estr[0] == '*' && estr[1] == '\0') {
- /* Found ":[*]" */
- st->oneBigWord = TRUE;
- st->newVal = st->val;
- goto ok;
- }
-
- if (estr[0] == '@' && estr[1] == '\0') {
- /* Found ":[@]" */
- st->oneBigWord = FALSE;
- st->newVal = st->val;
- goto ok;
- }
-
- /*
- * We expect estr to contain a single integer for :[N], or two integers
- * separated by ".." for :[start..end].
- */
- p = estr;
- if (!TryParseIntBase0(&p, &first))
- goto bad_modifier; /* Found junk instead of a number */
-
- if (p[0] == '\0') { /* Found only one integer in :[N] */
- last = first;
- } else if (p[0] == '.' && p[1] == '.' && p[2] != '\0') {
- /* Expecting another integer after ".." */
- p += 2;
- if (!TryParseIntBase0(&p, &last) || *p != '\0')
- goto bad_modifier; /* Found junk after ".." */
- } else
- goto bad_modifier; /* Found junk instead of ".." */
-
- /*
- * Now first and last are properly filled in, but we still have to check
- * for 0 as a special case.
- */
- if (first == 0 && last == 0) {
- /* ":[0]" or perhaps ":[0..0]" */
- st->oneBigWord = TRUE;
- st->newVal = st->val;
- goto ok;
- }
-
- /* ":[0..N]" or ":[N..0]" */
- if (first == 0 || last == 0)
- goto bad_modifier;
-
- /* Normal case: select the words described by first and last. */
- st->newVal = VarSelectWords(st->sep, st->oneBigWord, st->val, first, last);
+
+ /* ":[0..N]" or ":[N..0]" */
+ if (first == 0 || last == 0)
+ goto bad_modifier;
+
+ /* Normal case: select the words described by first and last. */
+ st->newVal = FStr_InitOwn(
+ VarSelectWords(st->sep, st->oneBigWord, val, first, last));
ok:
- free(estr);
- return AMR_OK;
+ free(estr);
+ return AMR_OK;
bad_modifier:
- free(estr);
- return AMR_BAD;
+ free(estr);
+ return AMR_BAD;
}
static int
str_cmp_asc(const void *a, const void *b)
{
- return strcmp(*(const char * const *)a, *(const char * const *)b);
+ return strcmp(*(const char *const *)a, *(const char *const *)b);
}
static int
str_cmp_desc(const void *a, const void *b)
{
- return strcmp(*(const char * const *)b, *(const char * const *)a);
+ return strcmp(*(const char *const *)b, *(const char *const *)a);
+}
+
+static void
+ShuffleStrings(char **strs, size_t n)
+{
+ size_t i;
+
+ for (i = n - 1; i > 0; i--) {
+ size_t rndidx = (size_t)random() % (i + 1);
+ char *t = strs[i];
+ strs[i] = strs[rndidx];
+ strs[rndidx] = t;
+ }
}
/* :O (order ascending) or :Or (order descending) or :Ox (shuffle) */
static ApplyModifierResult
-ApplyModifier_Order(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Order(const char **pp, const char *val, ApplyModifiersState *st)
{
- const char *mod = (*pp)++; /* skip past the 'O' in any case */
+ const char *mod = (*pp)++; /* skip past the 'O' in any case */
- Words words = Str_Words(st->val, FALSE);
-
- if (mod[1] == st->endc || mod[1] == ':') {
- /* :O sorts ascending */
- qsort(words.words, words.len, sizeof words.words[0], str_cmp_asc);
+ Words words = Str_Words(val, FALSE);
- } else if ((mod[1] == 'r' || mod[1] == 'x') &&
- (mod[2] == st->endc || mod[2] == ':')) {
- (*pp)++;
+ if (mod[1] == st->endc || mod[1] == ':') {
+ /* :O sorts ascending */
+ qsort(words.words, words.len, sizeof words.words[0],
+ str_cmp_asc);
- if (mod[1] == 'r') {
- /* :Or sorts descending */
- qsort(words.words, words.len, sizeof words.words[0], str_cmp_desc);
+ } else if ((mod[1] == 'r' || mod[1] == 'x') &&
+ (mod[2] == st->endc || mod[2] == ':')) {
+ (*pp)++;
+ if (mod[1] == 'r') { /* :Or sorts descending */
+ qsort(words.words, words.len, sizeof words.words[0],
+ str_cmp_desc);
+ } else
+ ShuffleStrings(words.words, words.len);
} else {
- /* :Ox shuffles
- *
- * We will use [ac..2] range for mod factors. This will produce
- * random numbers in [(ac-1)..0] interval, and minimal
- * reasonable value for mod factor is 2 (the mod 1 will produce
- * 0 with probability 1).
- */
- size_t i;
- for (i = words.len - 1; i > 0; i--) {
- size_t rndidx = (size_t)random() % (i + 1);
- char *t = words.words[i];
- words.words[i] = words.words[rndidx];
- words.words[rndidx] = t;
- }
+ Words_Free(words);
+ return AMR_BAD;
}
- } else {
- Words_Free(words);
- return AMR_BAD;
- }
- st->newVal = Words_JoinFree(words);
- return AMR_OK;
+ st->newVal = FStr_InitOwn(Words_JoinFree(words));
+ return AMR_OK;
}
/* :? then : else */
static ApplyModifierResult
ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st)
{
- char *then_expr, *else_expr;
- VarParseResult res;
-
- Boolean value = FALSE;
- VarEvalFlags then_eflags = VARE_NONE;
- VarEvalFlags else_eflags = VARE_NONE;
-
- int cond_rc = COND_PARSE; /* anything other than COND_INVALID */
- if (st->eflags & VARE_WANTRES) {
- cond_rc = Cond_EvalCondition(st->var->name, &value);
- if (cond_rc != COND_INVALID && value)
- then_eflags = st->eflags;
- if (cond_rc != COND_INVALID && !value)
- else_eflags = st->eflags;
- }
-
- (*pp)++; /* skip past the '?' */
- res = ParseModifierPart(pp, ':', then_eflags, st,
- &then_expr, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
-
- res = ParseModifierPart(pp, st->endc, else_eflags, st,
- &else_expr, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
-
- (*pp)--;
- if (cond_rc == COND_INVALID) {
- Error("Bad conditional expression `%s' in %s?%s:%s",
- st->var->name, st->var->name, then_expr, else_expr);
- return AMR_CLEANUP;
- }
-
- if (value) {
- st->newVal = then_expr;
- free(else_expr);
- } else {
- st->newVal = else_expr;
- free(then_expr);
- }
- ApplyModifiersState_Define(st);
- return AMR_OK;
+ char *then_expr, *else_expr;
+ VarParseResult res;
+
+ Boolean value = FALSE;
+ VarEvalFlags then_eflags = VARE_NONE;
+ VarEvalFlags else_eflags = VARE_NONE;
+
+ int cond_rc = COND_PARSE; /* anything other than COND_INVALID */
+ if (st->eflags & VARE_WANTRES) {
+ cond_rc = Cond_EvalCondition(st->var->name.str, &value);
+ if (cond_rc != COND_INVALID && value)
+ then_eflags = st->eflags;
+ if (cond_rc != COND_INVALID && !value)
+ else_eflags = st->eflags;
+ }
+
+ (*pp)++; /* skip past the '?' */
+ res = ParseModifierPart(pp, ':', then_eflags, st, &then_expr);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+
+ res = ParseModifierPart(pp, st->endc, else_eflags, st, &else_expr);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+
+ (*pp)--;
+ if (cond_rc == COND_INVALID) {
+ Error("Bad conditional expression `%s' in %s?%s:%s",
+ st->var->name.str, st->var->name.str, then_expr, else_expr);
+ return AMR_CLEANUP;
+ }
+
+ if (value) {
+ st->newVal = FStr_InitOwn(then_expr);
+ free(else_expr);
+ } else {
+ st->newVal = FStr_InitOwn(else_expr);
+ free(then_expr);
+ }
+ ApplyModifiersState_Define(st);
+ return AMR_OK;
}
/*
@@ -2961,678 +3188,728 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st)
static ApplyModifierResult
ApplyModifier_Assign(const char **pp, ApplyModifiersState *st)
{
- GNode *v_ctxt;
- char delim;
- char *val;
- VarParseResult res;
-
- const char *mod = *pp;
- const char *op = mod + 1;
-
- if (op[0] == '=')
- goto ok;
- if ((op[0] == '!' || op[0] == '+' || op[0] == '?') && op[1] == '=')
- goto ok;
- return AMR_UNKNOWN; /* "::<unrecognised>" */
+ GNode *ctxt;
+ char delim;
+ char *val;
+ VarParseResult res;
+
+ const char *mod = *pp;
+ const char *op = mod + 1;
+
+ if (op[0] == '=')
+ goto ok;
+ if ((op[0] == '!' || op[0] == '+' || op[0] == '?') && op[1] == '=')
+ goto ok;
+ return AMR_UNKNOWN; /* "::<unrecognised>" */
ok:
- if (st->var->name[0] == '\0') {
- *pp = mod + 1;
- return AMR_BAD;
- }
+ if (st->var->name.str[0] == '\0') {
+ *pp = mod + 1;
+ return AMR_BAD;
+ }
+
+ ctxt = st->ctxt; /* context where v belongs */
+ if (!(st->exprFlags & VEF_UNDEF) && st->ctxt != VAR_GLOBAL) {
+ Var *gv = VarFind(st->var->name.str, st->ctxt, FALSE);
+ if (gv == NULL)
+ ctxt = VAR_GLOBAL;
+ else
+ VarFreeEnv(gv, TRUE);
+ }
- v_ctxt = st->ctxt; /* context where v belongs */
- if (!(st->exprFlags & VEF_UNDEF) && st->ctxt != VAR_GLOBAL) {
- Var *gv = VarFind(st->var->name, st->ctxt, FALSE);
- if (gv == NULL)
- v_ctxt = VAR_GLOBAL;
- else
- VarFreeEnv(gv, TRUE);
- }
-
- switch (op[0]) {
- case '+':
- case '?':
- case '!':
- *pp = mod + 3;
- break;
- default:
- *pp = mod + 2;
- break;
- }
-
- delim = st->startc == '(' ? ')' : '}';
- res = ParseModifierPart(pp, delim, st->eflags, st, &val, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
-
- (*pp)--;
-
- if (st->eflags & VARE_WANTRES) {
switch (op[0]) {
case '+':
- Var_Append(st->var->name, val, v_ctxt);
- break;
- case '!': {
- const char *errfmt;
- char *cmd_output = Cmd_Exec(val, &errfmt);
- if (errfmt)
- Error(errfmt, val);
- else
- Var_Set(st->var->name, cmd_output, v_ctxt);
- free(cmd_output);
- break;
- }
case '?':
- if (!(st->exprFlags & VEF_UNDEF))
+ case '!':
+ *pp = mod + 3;
break;
- /* FALLTHROUGH */
default:
- Var_Set(st->var->name, val, v_ctxt);
- break;
+ *pp = mod + 2;
+ break;
+ }
+
+ delim = st->startc == '(' ? ')' : '}';
+ res = ParseModifierPart(pp, delim, st->eflags, st, &val);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+
+ (*pp)--;
+
+ if (st->eflags & VARE_WANTRES) {
+ switch (op[0]) {
+ case '+':
+ Var_Append(st->var->name.str, val, ctxt);
+ break;
+ case '!': {
+ const char *errfmt;
+ char *cmd_output = Cmd_Exec(val, &errfmt);
+ if (errfmt != NULL)
+ Error(errfmt, val);
+ else
+ Var_Set(st->var->name.str, cmd_output, ctxt);
+ free(cmd_output);
+ break;
+ }
+ case '?':
+ if (!(st->exprFlags & VEF_UNDEF))
+ break;
+ /* FALLTHROUGH */
+ default:
+ Var_Set(st->var->name.str, val, ctxt);
+ break;
+ }
}
- }
- free(val);
- st->newVal = bmake_strdup("");
- return AMR_OK;
+ free(val);
+ st->newVal = FStr_InitRefer("");
+ return AMR_OK;
}
-/* :_=...
- * remember current value */
+/*
+ * :_=...
+ * remember current value
+ */
static ApplyModifierResult
-ApplyModifier_Remember(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Remember(const char **pp, const char *val,
+ ApplyModifiersState *st)
{
- const char *mod = *pp;
- if (!ModMatchEq(mod, "_", st->endc))
- return AMR_UNKNOWN;
-
- if (mod[1] == '=') {
- size_t n = strcspn(mod + 2, ":)}");
- char *name = bmake_strldup(mod + 2, n);
- Var_Set(name, st->val, st->ctxt);
- free(name);
- *pp = mod + 2 + n;
- } else {
- Var_Set("_", st->val, st->ctxt);
- *pp = mod + 1;
- }
- st->newVal = st->val;
- return AMR_OK;
+ const char *mod = *pp;
+ if (!ModMatchEq(mod, "_", st->endc))
+ return AMR_UNKNOWN;
+
+ if (mod[1] == '=') {
+ size_t n = strcspn(mod + 2, ":)}");
+ char *name = bmake_strldup(mod + 2, n);
+ Var_Set(name, val, st->ctxt);
+ free(name);
+ *pp = mod + 2 + n;
+ } else {
+ Var_Set("_", val, st->ctxt);
+ *pp = mod + 1;
+ }
+ st->newVal = FStr_InitRefer(val);
+ return AMR_OK;
}
-/* Apply the given function to each word of the variable value,
- * for a single-letter modifier such as :H, :T. */
+/*
+ * Apply the given function to each word of the variable value,
+ * for a single-letter modifier such as :H, :T.
+ */
static ApplyModifierResult
-ApplyModifier_WordFunc(const char **pp, ApplyModifiersState *st,
- ModifyWordsCallback modifyWord)
+ApplyModifier_WordFunc(const char **pp, const char *val,
+ ApplyModifiersState *st, ModifyWordsCallback modifyWord)
{
- char delim = (*pp)[1];
- if (delim != st->endc && delim != ':')
- return AMR_UNKNOWN;
-
- st->newVal = ModifyWords(st->val, modifyWord, NULL,
- st->oneBigWord, st->sep);
- (*pp)++;
- return AMR_OK;
+ char delim = (*pp)[1];
+ if (delim != st->endc && delim != ':')
+ return AMR_UNKNOWN;
+
+ st->newVal = FStr_InitOwn(ModifyWords(val, modifyWord, NULL,
+ st->oneBigWord, st->sep));
+ (*pp)++;
+ return AMR_OK;
}
static ApplyModifierResult
-ApplyModifier_Unique(const char **pp, ApplyModifiersState *st)
+ApplyModifier_Unique(const char **pp, const char *val, ApplyModifiersState *st)
{
- if ((*pp)[1] == st->endc || (*pp)[1] == ':') {
- st->newVal = VarUniq(st->val);
- (*pp)++;
- return AMR_OK;
- } else
- return AMR_UNKNOWN;
+ if ((*pp)[1] == st->endc || (*pp)[1] == ':') {
+ st->newVal = FStr_InitOwn(VarUniq(val));
+ (*pp)++;
+ return AMR_OK;
+ } else
+ return AMR_UNKNOWN;
}
#ifdef SYSVVARSUB
/* :from=to */
static ApplyModifierResult
-ApplyModifier_SysV(const char **pp, ApplyModifiersState *st)
+ApplyModifier_SysV(const char **pp, const char *val, ApplyModifiersState *st)
{
- char *lhs, *rhs;
- VarParseResult res;
-
- const char *mod = *pp;
- Boolean eqFound = FALSE;
-
- /*
- * First we make a pass through the string trying to verify it is a
- * SysV-make-style translation. It must be: <lhs>=<rhs>
- */
- int depth = 1;
- const char *p = mod;
- while (*p != '\0' && depth > 0) {
- if (*p == '=') { /* XXX: should also test depth == 1 */
- eqFound = TRUE;
- /* continue looking for st->endc */
- } else if (*p == st->endc)
- depth--;
- else if (*p == st->startc)
- depth++;
- if (depth > 0)
- p++;
- }
- if (*p != st->endc || !eqFound)
- return AMR_UNKNOWN;
-
- *pp = mod;
- res = ParseModifierPart(pp, '=', st->eflags, st,
- &lhs, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
-
- /* The SysV modifier lasts until the end of the variable expression. */
- res = ParseModifierPart(pp, st->endc, st->eflags, st,
- &rhs, NULL, NULL, NULL);
- if (res != VPR_OK)
- return AMR_CLEANUP;
-
- (*pp)--;
- if (lhs[0] == '\0' && st->val[0] == '\0') {
- st->newVal = st->val; /* special case */
- } else {
- struct ModifyWord_SYSVSubstArgs args = {st->ctxt, lhs, rhs};
- st->newVal = ModifyWords(st->val, ModifyWord_SYSVSubst, &args,
- st->oneBigWord, st->sep);
- }
- free(lhs);
- free(rhs);
- return AMR_OK;
+ char *lhs, *rhs;
+ VarParseResult res;
+
+ const char *mod = *pp;
+ Boolean eqFound = FALSE;
+
+ /*
+ * First we make a pass through the string trying to verify it is a
+ * SysV-make-style translation. It must be: <lhs>=<rhs>
+ */
+ int depth = 1;
+ const char *p = mod;
+ while (*p != '\0' && depth > 0) {
+ if (*p == '=') { /* XXX: should also test depth == 1 */
+ eqFound = TRUE;
+ /* continue looking for st->endc */
+ } else if (*p == st->endc)
+ depth--;
+ else if (*p == st->startc)
+ depth++;
+ if (depth > 0)
+ p++;
+ }
+ if (*p != st->endc || !eqFound)
+ return AMR_UNKNOWN;
+
+ res = ParseModifierPart(pp, '=', st->eflags, st, &lhs);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+
+ /* The SysV modifier lasts until the end of the variable expression. */
+ res = ParseModifierPart(pp, st->endc, st->eflags, st, &rhs);
+ if (res != VPR_OK)
+ return AMR_CLEANUP;
+
+ (*pp)--;
+ if (lhs[0] == '\0' && val[0] == '\0') {
+ st->newVal = FStr_InitRefer(val); /* special case */
+ } else {
+ struct ModifyWord_SYSVSubstArgs args = { st->ctxt, lhs, rhs };
+ st->newVal = FStr_InitOwn(
+ ModifyWords(val, ModifyWord_SYSVSubst, &args,
+ st->oneBigWord, st->sep));
+ }
+ free(lhs);
+ free(rhs);
+ return AMR_OK;
}
#endif
#ifdef SUNSHCMD
/* :sh */
static ApplyModifierResult
-ApplyModifier_SunShell(const char **pp, ApplyModifiersState *st)
+ApplyModifier_SunShell(const char **pp, const char *val,
+ ApplyModifiersState *st)
{
- const char *p = *pp;
- if (p[1] == 'h' && (p[2] == st->endc || p[2] == ':')) {
- if (st->eflags & VARE_WANTRES) {
- const char *errfmt;
- st->newVal = Cmd_Exec(st->val, &errfmt);
- if (errfmt)
- Error(errfmt, st->val);
+ const char *p = *pp;
+ if (p[1] == 'h' && (p[2] == st->endc || p[2] == ':')) {
+ if (st->eflags & VARE_WANTRES) {
+ const char *errfmt;
+ st->newVal = FStr_InitOwn(Cmd_Exec(val, &errfmt));
+ if (errfmt != NULL)
+ Error(errfmt, val);
+ } else
+ st->newVal = FStr_InitRefer("");
+ *pp = p + 2;
+ return AMR_OK;
} else
- st->newVal = bmake_strdup("");
- *pp = p + 2;
- return AMR_OK;
- } else
- return AMR_UNKNOWN;
+ return AMR_UNKNOWN;
}
#endif
static void
-LogBeforeApply(const ApplyModifiersState *st, const char *mod, const char endc)
+LogBeforeApply(const ApplyModifiersState *st, const char *mod, char endc,
+ const char *val)
{
- char eflags_str[VarEvalFlags_ToStringSize];
- char vflags_str[VarFlags_ToStringSize];
- char exprflags_str[VarExprFlags_ToStringSize];
- Boolean is_single_char = mod[0] != '\0' &&
- (mod[1] == endc || mod[1] == ':');
-
- /* At this point, only the first character of the modifier can
- * be used since the end of the modifier is not yet known. */
- debug_printf("Applying ${%s:%c%s} to \"%s\" (%s, %s, %s)\n",
- st->var->name, mod[0], is_single_char ? "" : "...", st->val,
- Enum_FlagsToString(eflags_str, sizeof eflags_str,
- st->eflags, VarEvalFlags_ToStringSpecs),
- Enum_FlagsToString(vflags_str, sizeof vflags_str,
- st->var->flags, VarFlags_ToStringSpecs),
- Enum_FlagsToString(exprflags_str, sizeof exprflags_str,
- st->exprFlags,
- VarExprFlags_ToStringSpecs));
+ char eflags_str[VarEvalFlags_ToStringSize];
+ char vflags_str[VarFlags_ToStringSize];
+ char exprflags_str[VarExprFlags_ToStringSize];
+ Boolean is_single_char = mod[0] != '\0' &&
+ (mod[1] == endc || mod[1] == ':');
+
+ /* At this point, only the first character of the modifier can
+ * be used since the end of the modifier is not yet known. */
+ debug_printf("Applying ${%s:%c%s} to \"%s\" (%s, %s, %s)\n",
+ st->var->name.str, mod[0], is_single_char ? "" : "...", val,
+ Enum_FlagsToString(eflags_str, sizeof eflags_str,
+ st->eflags, VarEvalFlags_ToStringSpecs),
+ Enum_FlagsToString(vflags_str, sizeof vflags_str,
+ st->var->flags, VarFlags_ToStringSpecs),
+ Enum_FlagsToString(exprflags_str, sizeof exprflags_str,
+ st->exprFlags,
+ VarExprFlags_ToStringSpecs));
}
static void
LogAfterApply(ApplyModifiersState *st, const char *p, const char *mod)
{
- char eflags_str[VarEvalFlags_ToStringSize];
- char vflags_str[VarFlags_ToStringSize];
- char exprflags_str[VarExprFlags_ToStringSize];
- const char *quot = st->newVal == var_Error ? "" : "\"";
- const char *newVal = st->newVal == var_Error ? "error" : st->newVal;
-
- debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s, %s)\n",
- st->var->name, (int)(p - mod), mod, quot, newVal, quot,
- Enum_FlagsToString(eflags_str, sizeof eflags_str,
- st->eflags, VarEvalFlags_ToStringSpecs),
- Enum_FlagsToString(vflags_str, sizeof vflags_str,
- st->var->flags, VarFlags_ToStringSpecs),
- Enum_FlagsToString(exprflags_str, sizeof exprflags_str,
- st->exprFlags,
- VarExprFlags_ToStringSpecs));
+ char eflags_str[VarEvalFlags_ToStringSize];
+ char vflags_str[VarFlags_ToStringSize];
+ char exprflags_str[VarExprFlags_ToStringSize];
+ const char *quot = st->newVal.str == var_Error ? "" : "\"";
+ const char *newVal =
+ st->newVal.str == var_Error ? "error" : st->newVal.str;
+
+ debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s, %s)\n",
+ st->var->name.str, (int)(p - mod), mod, quot, newVal, quot,
+ Enum_FlagsToString(eflags_str, sizeof eflags_str,
+ st->eflags, VarEvalFlags_ToStringSpecs),
+ Enum_FlagsToString(vflags_str, sizeof vflags_str,
+ st->var->flags, VarFlags_ToStringSpecs),
+ Enum_FlagsToString(exprflags_str, sizeof exprflags_str,
+ st->exprFlags,
+ VarExprFlags_ToStringSpecs));
}
static ApplyModifierResult
-ApplyModifier(const char **pp, ApplyModifiersState *st)
+ApplyModifier(const char **pp, const char *val, ApplyModifiersState *st)
{
- switch (**pp) {
- case ':':
- return ApplyModifier_Assign(pp, st);
- case '@':
- return ApplyModifier_Loop(pp, st);
- case '_':
- return ApplyModifier_Remember(pp, st);
- case 'D':
- case 'U':
- return ApplyModifier_Defined(pp, st);
- case 'L':
- return ApplyModifier_Literal(pp, st);
- case 'P':
- return ApplyModifier_Path(pp, st);
- case '!':
- return ApplyModifier_ShellCommand(pp, st);
- case '[':
- return ApplyModifier_Words(pp, st);
- case 'g':
- return ApplyModifier_Gmtime(pp, st);
- case 'h':
- return ApplyModifier_Hash(pp, st);
- case 'l':
- return ApplyModifier_Localtime(pp, st);
- case 't':
- return ApplyModifier_To(pp, st);
- case 'N':
- case 'M':
- return ApplyModifier_Match(pp, st);
- case 'S':
- return ApplyModifier_Subst(pp, st);
- case '?':
- return ApplyModifier_IfElse(pp, st);
+ switch (**pp) {
+ case ':':
+ return ApplyModifier_Assign(pp, st);
+ case '@':
+ return ApplyModifier_Loop(pp, val, st);
+ case '_':
+ return ApplyModifier_Remember(pp, val, st);
+ case 'D':
+ case 'U':
+ return ApplyModifier_Defined(pp, val, st);
+ case 'L':
+ return ApplyModifier_Literal(pp, st);
+ case 'P':
+ return ApplyModifier_Path(pp, st);
+ case '!':
+ return ApplyModifier_ShellCommand(pp, st);
+ case '[':
+ return ApplyModifier_Words(pp, val, st);
+ case 'g':
+ return ApplyModifier_Gmtime(pp, val, st);
+ case 'h':
+ return ApplyModifier_Hash(pp, val, st);
+ case 'l':
+ return ApplyModifier_Localtime(pp, val, st);
+ case 't':
+ return ApplyModifier_To(pp, val, st);
+ case 'N':
+ case 'M':
+ return ApplyModifier_Match(pp, val, st);
+ case 'S':
+ return ApplyModifier_Subst(pp, val, st);
+ case '?':
+ return ApplyModifier_IfElse(pp, st);
#ifndef NO_REGEX
- case 'C':
- return ApplyModifier_Regex(pp, st);
+ case 'C':
+ return ApplyModifier_Regex(pp, val, st);
#endif
- case 'q':
- case 'Q':
- return ApplyModifier_Quote(pp, st);
- case 'T':
- return ApplyModifier_WordFunc(pp, st, ModifyWord_Tail);
- case 'H':
- return ApplyModifier_WordFunc(pp, st, ModifyWord_Head);
- case 'E':
- return ApplyModifier_WordFunc(pp, st, ModifyWord_Suffix);
- case 'R':
- return ApplyModifier_WordFunc(pp, st, ModifyWord_Root);
- case 'r':
- return ApplyModifier_Range(pp, st);
- case 'O':
- return ApplyModifier_Order(pp, st);
- case 'u':
- return ApplyModifier_Unique(pp, st);
+ case 'q':
+ case 'Q':
+ return ApplyModifier_Quote(pp, val, st);
+ case 'T':
+ return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Tail);
+ case 'H':
+ return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Head);
+ case 'E':
+ return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Suffix);
+ case 'R':
+ return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Root);
+ case 'r':
+ return ApplyModifier_Range(pp, val, st);
+ case 'O':
+ return ApplyModifier_Order(pp, val, st);
+ case 'u':
+ return ApplyModifier_Unique(pp, val, st);
#ifdef SUNSHCMD
- case 's':
- return ApplyModifier_SunShell(pp, st);
+ case 's':
+ return ApplyModifier_SunShell(pp, val, st);
#endif
- default:
- return AMR_UNKNOWN;
- }
+ default:
+ return AMR_UNKNOWN;
+ }
}
-static char *ApplyModifiers(const char **, char *, char, char, Var *,
- VarExprFlags *, GNode *, VarEvalFlags, void **);
+static FStr ApplyModifiers(const char **, FStr, char, char, Var *,
+ VarExprFlags *, GNode *, VarEvalFlags);
typedef enum ApplyModifiersIndirectResult {
- AMIR_CONTINUE,
- AMIR_APPLY_MODS,
- AMIR_OUT
+ /* The indirect modifiers have been applied successfully. */
+ AMIR_CONTINUE,
+ /* Fall back to the SysV modifier. */
+ AMIR_APPLY_MODS,
+ /* Error out. */
+ AMIR_OUT
} ApplyModifiersIndirectResult;
-/* While expanding a variable expression, expand and apply indirect
- * modifiers such as in ${VAR:${M_indirect}}. */
+/*
+ * While expanding a variable expression, expand and apply indirect modifiers,
+ * such as in ${VAR:${M_indirect}}.
+ *
+ * All indirect modifiers of a group must come from a single variable
+ * expression. ${VAR:${M1}} is valid but ${VAR:${M1}${M2}} is not.
+ *
+ * Multiple groups of indirect modifiers can be chained by separating them
+ * with colons. ${VAR:${M1}:${M2}} contains 2 indirect modifiers.
+ *
+ * If the variable expression is not followed by st->endc or ':', fall
+ * back to trying the SysV modifier, such as in ${VAR:${FROM}=${TO}}.
+ *
+ * The expression ${VAR:${M1}${M2}} is not treated as an indirect
+ * modifier, and it is neither a SysV modifier but a parse error.
+ */
static ApplyModifiersIndirectResult
-ApplyModifiersIndirect(
- ApplyModifiersState *const st,
- const char **const inout_p,
- void **const inout_freeIt
-) {
- const char *p = *inout_p;
- const char *mods;
- void *mods_freeIt;
-
- (void)Var_Parse(&p, st->ctxt, st->eflags, &mods, &mods_freeIt);
- /* TODO: handle errors */
-
- /*
- * If we have not parsed up to st->endc or ':', we are not
- * interested. This means the expression ${VAR:${M_1}${M_2}}
- * is not accepted, but ${VAR:${M_1}:${M_2}} is.
- */
- if (mods[0] != '\0' && *p != '\0' && *p != ':' && *p != st->endc) {
- if (opts.lint)
- Parse_Error(PARSE_FATAL,
- "Missing delimiter ':' after indirect modifier \"%.*s\"",
- (int)(p - *inout_p), *inout_p);
-
- free(mods_freeIt);
- /* XXX: apply_mods doesn't sound like "not interested". */
- /* XXX: Why is the indirect modifier parsed once more by
- * apply_mods? If any, p should be advanced to nested_p. */
- return AMIR_APPLY_MODS;
- }
-
- VAR_DEBUG3("Indirect modifier \"%s\" from \"%.*s\"\n",
- mods, (int)(p - *inout_p), *inout_p);
-
- if (mods[0] != '\0') {
- const char *rval_pp = mods;
- st->val = ApplyModifiers(&rval_pp, st->val, '\0', '\0', st->var,
- &st->exprFlags, st->ctxt, st->eflags,
- inout_freeIt);
- if (st->val == var_Error || st->val == varUndefined ||
- *rval_pp != '\0') {
- free(mods_freeIt);
- *inout_p = p;
- return AMIR_OUT; /* error already reported */
+ApplyModifiersIndirect(ApplyModifiersState *st, const char **pp,
+ FStr *inout_value)
+{
+ const char *p = *pp;
+ FStr mods;
+
+ (void)Var_Parse(&p, st->ctxt, st->eflags, &mods);
+ /* TODO: handle errors */
+
+ if (mods.str[0] != '\0' && *p != '\0' && *p != ':' && *p != st->endc) {
+ FStr_Done(&mods);
+ return AMIR_APPLY_MODS;
}
- }
- free(mods_freeIt);
-
- if (*p == ':')
- p++;
- else if (*p == '\0' && st->endc != '\0') {
- Error("Unclosed variable specification after complex "
- "modifier (expecting '%c') for %s", st->endc, st->var->name);
- *inout_p = p;
- return AMIR_OUT;
- }
-
- *inout_p = p;
- return AMIR_CONTINUE;
-}
-/* Apply any modifiers (such as :Mpattern or :@var@loop@ or :Q or ::=value). */
-static char *
-ApplyModifiers(
- const char **const pp, /* the parsing position, updated upon return */
- char *const val, /* the current value of the expression */
- char const startc, /* '(' or '{', or '\0' for indirect modifiers */
- char const endc, /* ')' or '}', or '\0' for indirect modifiers */
- Var *const v,
- VarExprFlags *const exprFlags,
- GNode *const ctxt, /* for looking up and modifying variables */
- VarEvalFlags const eflags,
- void **const inout_freeIt /* free this after using the return value */
-) {
- ApplyModifiersState st = {
- startc, endc, v, ctxt, eflags,
- val, /* .val */
- var_Error, /* .newVal */
- ' ', /* .sep */
- FALSE, /* .oneBigWord */
- *exprFlags /* .exprFlags */
- };
- const char *p;
- const char *mod;
- ApplyModifierResult res;
-
- assert(startc == '(' || startc == '{' || startc == '\0');
- assert(endc == ')' || endc == '}' || endc == '\0');
- assert(val != NULL);
-
- p = *pp;
-
- if (*p == '\0' && endc != '\0') {
- Error("Unclosed variable expression (expecting '%c') for \"%s\"",
- st.endc, st.var->name);
- goto cleanup;
- }
-
- while (*p != '\0' && *p != endc) {
-
- if (*p == '$') {
- ApplyModifiersIndirectResult amir;
- amir = ApplyModifiersIndirect(&st, &p, inout_freeIt);
- if (amir == AMIR_CONTINUE)
- continue;
- if (amir == AMIR_OUT)
- goto out;
+ DEBUG3(VAR, "Indirect modifier \"%s\" from \"%.*s\"\n",
+ mods.str, (int)(p - *pp), *pp);
+
+ if (mods.str[0] != '\0') {
+ const char *modsp = mods.str;
+ FStr newVal = ApplyModifiers(&modsp, *inout_value, '\0', '\0',
+ st->var, &st->exprFlags, st->ctxt, st->eflags);
+ *inout_value = newVal;
+ if (newVal.str == var_Error || *modsp != '\0') {
+ FStr_Done(&mods);
+ *pp = p;
+ return AMIR_OUT; /* error already reported */
+ }
}
- st.newVal = var_Error; /* default value, in case of errors */
- mod = p;
+ FStr_Done(&mods);
+
+ if (*p == ':')
+ p++;
+ else if (*p == '\0' && st->endc != '\0') {
+ Error("Unclosed variable specification after complex "
+ "modifier (expecting '%c') for %s",
+ st->endc, st->var->name.str);
+ *pp = p;
+ return AMIR_OUT;
+ }
+
+ *pp = p;
+ return AMIR_CONTINUE;
+}
+
+static ApplyModifierResult
+ApplySingleModifier(ApplyModifiersState *st, const char *mod, char endc,
+ const char **pp, FStr *inout_value)
+{
+ ApplyModifierResult res;
+ const char *p = *pp;
+ const char *const val = inout_value->str;
if (DEBUG(VAR))
- LogBeforeApply(&st, mod, endc);
+ LogBeforeApply(st, mod, endc, val);
- res = ApplyModifier(&p, &st);
+ res = ApplyModifier(&p, val, st);
#ifdef SYSVVARSUB
if (res == AMR_UNKNOWN) {
- assert(p == mod);
- res = ApplyModifier_SysV(&p, &st);
+ assert(p == mod);
+ res = ApplyModifier_SysV(&p, val, st);
}
#endif
if (res == AMR_UNKNOWN) {
- Error("Unknown modifier '%c'", *mod);
- /* Guess the end of the current modifier.
- * XXX: Skipping the rest of the modifier hides errors and leads
- * to wrong results. Parsing should rather stop here. */
- for (p++; *p != ':' && *p != st.endc && *p != '\0'; p++)
- continue;
- st.newVal = var_Error;
+ Parse_Error(PARSE_FATAL, "Unknown modifier '%c'", *mod);
+ /*
+ * Guess the end of the current modifier.
+ * XXX: Skipping the rest of the modifier hides
+ * errors and leads to wrong results.
+ * Parsing should rather stop here.
+ */
+ for (p++; *p != ':' && *p != st->endc && *p != '\0'; p++)
+ continue;
+ st->newVal = FStr_InitRefer(var_Error);
+ }
+ if (res == AMR_CLEANUP || res == AMR_BAD) {
+ *pp = p;
+ return res;
}
- if (res == AMR_CLEANUP)
- goto cleanup;
- if (res == AMR_BAD)
- goto bad_modifier;
if (DEBUG(VAR))
- LogAfterApply(&st, p, mod);
-
- if (st.newVal != st.val) {
- if (*inout_freeIt != NULL) {
- free(st.val);
- *inout_freeIt = NULL;
- }
- st.val = st.newVal;
- if (st.val != var_Error && st.val != varUndefined)
- *inout_freeIt = st.val;
+ LogAfterApply(st, p, mod);
+
+ if (st->newVal.str != val) {
+ FStr_Done(inout_value);
+ *inout_value = st->newVal;
}
- if (*p == '\0' && st.endc != '\0') {
- Error("Unclosed variable specification (expecting '%c') "
- "for \"%s\" (value \"%s\") modifier %c",
- st.endc, st.var->name, st.val, *mod);
+ if (*p == '\0' && st->endc != '\0') {
+ Error(
+ "Unclosed variable specification (expecting '%c') "
+ "for \"%s\" (value \"%s\") modifier %c",
+ st->endc, st->var->name.str, inout_value->str, *mod);
} else if (*p == ':') {
- p++;
- } else if (opts.lint && *p != '\0' && *p != endc) {
- Parse_Error(PARSE_FATAL,
- "Missing delimiter ':' after modifier \"%.*s\"",
- (int)(p - mod), mod);
- /* TODO: propagate parse error to the enclosing expression */
+ p++;
+ } else if (opts.strict && *p != '\0' && *p != endc) {
+ Parse_Error(PARSE_FATAL,
+ "Missing delimiter ':' after modifier \"%.*s\"",
+ (int)(p - mod), mod);
+ /*
+ * TODO: propagate parse error to the enclosing
+ * expression
+ */
+ }
+ *pp = p;
+ return AMR_OK;
+}
+
+/* Apply any modifiers (such as :Mpattern or :@var@loop@ or :Q or ::=value). */
+static FStr
+ApplyModifiers(
+ const char **pp, /* the parsing position, updated upon return */
+ FStr value, /* the current value of the expression */
+ char startc, /* '(' or '{', or '\0' for indirect modifiers */
+ char endc, /* ')' or '}', or '\0' for indirect modifiers */
+ Var *v,
+ VarExprFlags *exprFlags,
+ GNode *ctxt, /* for looking up and modifying variables */
+ VarEvalFlags eflags
+)
+{
+ ApplyModifiersState st = {
+ startc, endc, v, ctxt, eflags,
+ FStr_InitRefer(var_Error), /* .newVal */
+ ' ', /* .sep */
+ FALSE, /* .oneBigWord */
+ *exprFlags /* .exprFlags */
+ };
+ const char *p;
+ const char *mod;
+
+ assert(startc == '(' || startc == '{' || startc == '\0');
+ assert(endc == ')' || endc == '}' || endc == '\0');
+ assert(value.str != NULL);
+
+ p = *pp;
+
+ if (*p == '\0' && endc != '\0') {
+ Error(
+ "Unclosed variable expression (expecting '%c') for \"%s\"",
+ st.endc, st.var->name.str);
+ goto cleanup;
}
- }
-out:
- *pp = p;
- assert(st.val != NULL); /* Use var_Error or varUndefined instead. */
- *exprFlags = st.exprFlags;
- return st.val;
+
+ while (*p != '\0' && *p != endc) {
+ ApplyModifierResult res;
+
+ if (*p == '$') {
+ ApplyModifiersIndirectResult amir;
+ amir = ApplyModifiersIndirect(&st, &p, &value);
+ if (amir == AMIR_CONTINUE)
+ continue;
+ if (amir == AMIR_OUT)
+ break;
+ }
+
+ /* default value, in case of errors */
+ st.newVal = FStr_InitRefer(var_Error);
+ mod = p;
+
+ res = ApplySingleModifier(&st, mod, endc, &p, &value);
+ if (res == AMR_CLEANUP)
+ goto cleanup;
+ if (res == AMR_BAD)
+ goto bad_modifier;
+ }
+
+ *pp = p;
+ assert(value.str != NULL); /* Use var_Error or varUndefined instead. */
+ *exprFlags = st.exprFlags;
+ return value;
bad_modifier:
- /* XXX: The modifier end is only guessed. */
- Error("Bad modifier `:%.*s' for %s",
- (int)strcspn(mod, ":)}"), mod, st.var->name);
+ /* XXX: The modifier end is only guessed. */
+ Error("Bad modifier `:%.*s' for %s",
+ (int)strcspn(mod, ":)}"), mod, st.var->name.str);
cleanup:
- *pp = p;
- free(*inout_freeIt);
- *inout_freeIt = NULL;
- *exprFlags = st.exprFlags;
- return var_Error;
+ *pp = p;
+ FStr_Done(&value);
+ *exprFlags = st.exprFlags;
+ return FStr_InitRefer(var_Error);
}
-/* Only four of the local variables are treated specially as they are the
- * only four that will be set when dynamic sources are expanded. */
+/*
+ * Only four of the local variables are treated specially as they are the
+ * only four that will be set when dynamic sources are expanded.
+ */
static Boolean
VarnameIsDynamic(const char *name, size_t len)
{
- if (len == 1 || (len == 2 && (name[1] == 'F' || name[1] == 'D'))) {
- switch (name[0]) {
- case '@':
- case '%':
- case '*':
- case '!':
- return TRUE;
+ if (len == 1 || (len == 2 && (name[1] == 'F' || name[1] == 'D'))) {
+ switch (name[0]) {
+ case '@':
+ case '%':
+ case '*':
+ case '!':
+ return TRUE;
+ }
+ return FALSE;
}
- return FALSE;
- }
- if ((len == 7 || len == 8) && name[0] == '.' && ch_isupper(name[1])) {
- return strcmp(name, ".TARGET") == 0 ||
- strcmp(name, ".ARCHIVE") == 0 ||
- strcmp(name, ".PREFIX") == 0 ||
- strcmp(name, ".MEMBER") == 0;
- }
+ if ((len == 7 || len == 8) && name[0] == '.' && ch_isupper(name[1])) {
+ return strcmp(name, ".TARGET") == 0 ||
+ strcmp(name, ".ARCHIVE") == 0 ||
+ strcmp(name, ".PREFIX") == 0 ||
+ strcmp(name, ".MEMBER") == 0;
+ }
- return FALSE;
+ return FALSE;
}
static const char *
-UndefinedShortVarValue(char varname, const GNode *ctxt, VarEvalFlags eflags)
+UndefinedShortVarValue(char varname, const GNode *ctxt)
{
- if (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL) {
- /*
- * If substituting a local variable in a non-local context,
- * assume it's for dynamic source stuff. We have to handle
- * this specially and return the longhand for the variable
- * with the dollar sign escaped so it makes it back to the
- * caller. Only four of the local variables are treated
- * specially as they are the only four that will be set
- * when dynamic sources are expanded.
- */
- switch (varname) {
- case '@':
- return "$(.TARGET)";
- case '%':
- return "$(.MEMBER)";
- case '*':
- return "$(.PREFIX)";
- case '!':
- return "$(.ARCHIVE)";
+ if (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL) {
+ /*
+ * If substituting a local variable in a non-local context,
+ * assume it's for dynamic source stuff. We have to handle
+ * this specially and return the longhand for the variable
+ * with the dollar sign escaped so it makes it back to the
+ * caller. Only four of the local variables are treated
+ * specially as they are the only four that will be set
+ * when dynamic sources are expanded.
+ */
+ switch (varname) {
+ case '@':
+ return "$(.TARGET)";
+ case '%':
+ return "$(.MEMBER)";
+ case '*':
+ return "$(.PREFIX)";
+ case '!':
+ return "$(.ARCHIVE)";
+ }
}
- }
- return eflags & VARE_UNDEFERR ? var_Error : varUndefined;
+ return NULL;
}
-/* Parse a variable name, until the end character or a colon, whichever
- * comes first. */
+/*
+ * Parse a variable name, until the end character or a colon, whichever
+ * comes first.
+ */
static char *
ParseVarname(const char **pp, char startc, char endc,
GNode *ctxt, VarEvalFlags eflags,
size_t *out_varname_len)
{
- Buffer buf;
- const char *p = *pp;
- int depth = 1;
-
- Buf_Init(&buf);
-
- while (*p != '\0') {
- /* Track depth so we can spot parse errors. */
- if (*p == startc)
- depth++;
- if (*p == endc) {
- if (--depth == 0)
- break;
- }
- if (*p == ':' && depth == 1)
- break;
-
- /* A variable inside a variable, expand. */
- if (*p == '$') {
- const char *nested_val;
- void *nested_val_freeIt;
- (void)Var_Parse(&p, ctxt, eflags, &nested_val, &nested_val_freeIt);
- /* TODO: handle errors */
- Buf_AddStr(&buf, nested_val);
- free(nested_val_freeIt);
- } else {
- Buf_AddByte(&buf, *p);
- p++;
+ Buffer buf;
+ const char *p = *pp;
+ int depth = 1;
+
+ Buf_Init(&buf);
+
+ while (*p != '\0') {
+ /* Track depth so we can spot parse errors. */
+ if (*p == startc)
+ depth++;
+ if (*p == endc) {
+ if (--depth == 0)
+ break;
+ }
+ if (*p == ':' && depth == 1)
+ break;
+
+ /* A variable inside a variable, expand. */
+ if (*p == '$') {
+ FStr nested_val;
+ (void)Var_Parse(&p, ctxt, eflags, &nested_val);
+ /* TODO: handle errors */
+ Buf_AddStr(&buf, nested_val.str);
+ FStr_Done(&nested_val);
+ } else {
+ Buf_AddByte(&buf, *p);
+ p++;
+ }
}
- }
- *pp = p;
- *out_varname_len = Buf_Len(&buf);
- return Buf_Destroy(&buf, FALSE);
+ *pp = p;
+ *out_varname_len = Buf_Len(&buf);
+ return Buf_Destroy(&buf, FALSE);
}
static VarParseResult
ValidShortVarname(char varname, const char *start)
{
- switch (varname) {
- case '\0':
- case ')':
- case '}':
- case ':':
- case '$':
- break; /* and continue below */
- default:
- return VPR_OK;
- }
+ switch (varname) {
+ case '\0':
+ case ')':
+ case '}':
+ case ':':
+ case '$':
+ break; /* and continue below */
+ default:
+ return VPR_OK;
+ }
- if (!opts.lint)
- return VPR_PARSE_SILENT;
+ if (!opts.strict)
+ return VPR_ERR; /* XXX: Missing error message */
- if (varname == '$')
- Parse_Error(PARSE_FATAL,
+ if (varname == '$')
+ Parse_Error(PARSE_FATAL,
"To escape a dollar, use \\$, not $$, at \"%s\"", start);
- else if (varname == '\0')
- Parse_Error(PARSE_FATAL, "Dollar followed by nothing");
- else
- Parse_Error(PARSE_FATAL,
+ else if (varname == '\0')
+ Parse_Error(PARSE_FATAL, "Dollar followed by nothing");
+ else
+ Parse_Error(PARSE_FATAL,
"Invalid variable name '%c', at \"%s\"", varname, start);
- return VPR_PARSE_MSG;
+ return VPR_ERR;
}
-/* Parse a single-character variable name such as $V or $@.
- * Return whether to continue parsing. */
+/*
+ * Parse a single-character variable name such as $V or $@.
+ * Return whether to continue parsing.
+ */
static Boolean
ParseVarnameShort(char startc, const char **pp, GNode *ctxt,
VarEvalFlags eflags,
VarParseResult *out_FALSE_res, const char **out_FALSE_val,
Var **out_TRUE_var)
{
- char name[2];
- Var *v;
- VarParseResult vpr;
-
- /*
- * If it's not bounded by braces of some sort, life is much simpler.
- * We just need to check for the first character and return the
- * value if it exists.
- */
-
- vpr = ValidShortVarname(startc, *pp);
- if (vpr != VPR_OK) {
- (*pp)++;
- *out_FALSE_val = var_Error;
- *out_FALSE_res = vpr;
- return FALSE;
- }
+ char name[2];
+ Var *v;
+ VarParseResult vpr;
- name[0] = startc;
- name[1] = '\0';
- v = VarFind(name, ctxt, TRUE);
- if (v == NULL) {
- *pp += 2;
+ /*
+ * If it's not bounded by braces of some sort, life is much simpler.
+ * We just need to check for the first character and return the
+ * value if it exists.
+ */
- *out_FALSE_val = UndefinedShortVarValue(startc, ctxt, eflags);
- if (opts.lint && *out_FALSE_val == var_Error) {
- Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", name);
- *out_FALSE_res = VPR_UNDEF_MSG;
- return FALSE;
+ vpr = ValidShortVarname(startc, *pp);
+ if (vpr != VPR_OK) {
+ (*pp)++;
+ *out_FALSE_val = var_Error;
+ *out_FALSE_res = vpr;
+ return FALSE;
+ }
+
+ name[0] = startc;
+ name[1] = '\0';
+ v = VarFind(name, ctxt, TRUE);
+ if (v == NULL) {
+ const char *val;
+ *pp += 2;
+
+ val = UndefinedShortVarValue(startc, ctxt);
+ if (val == NULL)
+ val = eflags & VARE_UNDEFERR ? var_Error : varUndefined;
+
+ if (opts.strict && val == var_Error) {
+ Parse_Error(PARSE_FATAL,
+ "Variable \"%s\" is undefined", name);
+ *out_FALSE_res = VPR_ERR;
+ *out_FALSE_val = val;
+ return FALSE;
+ }
+
+ /*
+ * XXX: This looks completely wrong.
+ *
+ * If undefined expressions are not allowed, this should
+ * rather be VPR_ERR instead of VPR_UNDEF, together with an
+ * error message.
+ *
+ * If undefined expressions are allowed, this should rather
+ * be VPR_UNDEF instead of VPR_OK.
+ */
+ *out_FALSE_res = eflags & VARE_UNDEFERR ? VPR_UNDEF : VPR_OK;
+ *out_FALSE_val = val;
+ return FALSE;
}
- *out_FALSE_res = eflags & VARE_UNDEFERR ? VPR_UNDEF_SILENT : VPR_OK;
- return FALSE;
- }
- *out_TRUE_var = v;
- return TRUE;
+ *out_TRUE_var = v;
+ return TRUE;
}
/* Find variables like @F or <D. */
@@ -3640,67 +3917,68 @@ static Var *
FindLocalLegacyVar(const char *varname, size_t namelen, GNode *ctxt,
const char **out_extraModifiers)
{
- /* Only resolve these variables if ctxt is a "real" target. */
- if (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL)
- return NULL;
-
- if (namelen != 2)
- return NULL;
- if (varname[1] != 'F' && varname[1] != 'D')
- return NULL;
- if (strchr("@%?*!<>", varname[0]) == NULL)
- return NULL;
-
- {
- char name[] = { varname[0], '\0' };
- Var *v = VarFind(name, ctxt, FALSE);
-
- if (v != NULL) {
- if (varname[1] == 'D') {
- *out_extraModifiers = "H:";
- } else { /* F */
- *out_extraModifiers = "T:";
- }
+ /* Only resolve these variables if ctxt is a "real" target. */
+ if (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL)
+ return NULL;
+
+ if (namelen != 2)
+ return NULL;
+ if (varname[1] != 'F' && varname[1] != 'D')
+ return NULL;
+ if (strchr("@%?*!<>", varname[0]) == NULL)
+ return NULL;
+
+ {
+ char name[] = { varname[0], '\0' };
+ Var *v = VarFind(name, ctxt, FALSE);
+
+ if (v != NULL) {
+ if (varname[1] == 'D') {
+ *out_extraModifiers = "H:";
+ } else { /* F */
+ *out_extraModifiers = "T:";
+ }
+ }
+ return v;
}
- return v;
- }
}
static VarParseResult
EvalUndefined(Boolean dynamic, const char *start, const char *p, char *varname,
VarEvalFlags eflags,
- void **out_freeIt, const char **out_val)
+ FStr *out_val)
{
- if (dynamic) {
- char *pstr = bmake_strsedup(start, p);
- free(varname);
- *out_freeIt = pstr;
- *out_val = pstr;
- return VPR_OK;
- }
+ if (dynamic) {
+ *out_val = FStr_InitOwn(bmake_strsedup(start, p));
+ free(varname);
+ return VPR_OK;
+ }
- if ((eflags & VARE_UNDEFERR) && opts.lint) {
- Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", varname);
- free(varname);
- *out_val = var_Error;
- return VPR_UNDEF_MSG;
- }
+ if ((eflags & VARE_UNDEFERR) && opts.strict) {
+ Parse_Error(PARSE_FATAL,
+ "Variable \"%s\" is undefined", varname);
+ free(varname);
+ *out_val = FStr_InitRefer(var_Error);
+ return VPR_ERR;
+ }
- if (eflags & VARE_UNDEFERR) {
- free(varname);
- *out_val = var_Error;
- return VPR_UNDEF_SILENT;
- }
+ if (eflags & VARE_UNDEFERR) {
+ free(varname);
+ *out_val = FStr_InitRefer(var_Error);
+ return VPR_UNDEF; /* XXX: Should be VPR_ERR instead. */
+ }
- free(varname);
- *out_val = varUndefined;
- return VPR_OK;
+ free(varname);
+ *out_val = FStr_InitRefer(varUndefined);
+ return VPR_OK;
}
-/* Parse a long variable name enclosed in braces or parentheses such as $(VAR)
+/*
+ * Parse a long variable name enclosed in braces or parentheses such as $(VAR)
* or ${VAR}, up to the closing brace or parenthesis, or in the case of
* ${VAR:Modifiers}, up to the ':' that starts the modifiers.
- * Return whether to continue parsing. */
+ * Return whether to continue parsing.
+ */
static Boolean
ParseVarnameLong(
const char *p,
@@ -3710,8 +3988,7 @@ ParseVarnameLong(
const char **out_FALSE_pp,
VarParseResult *out_FALSE_res,
- const char **out_FALSE_val,
- void **out_FALSE_freeIt,
+ FStr *out_FALSE_val,
char *out_TRUE_endc,
const char **out_TRUE_p,
@@ -3720,76 +3997,98 @@ ParseVarnameLong(
const char **out_TRUE_extraModifiers,
Boolean *out_TRUE_dynamic,
VarExprFlags *out_TRUE_exprFlags
-) {
- size_t namelen;
- char *varname;
- Var *v;
- Boolean haveModifier;
- Boolean dynamic = FALSE;
-
- const char *const start = p;
- char endc = startc == '(' ? ')' : '}';
-
- p += 2; /* skip "${" or "$(" or "y(" */
- varname = ParseVarname(&p, startc, endc, ctxt, eflags, &namelen);
-
- if (*p == ':') {
- haveModifier = TRUE;
- } else if (*p == endc) {
- haveModifier = FALSE;
- } else {
- Parse_Error(PARSE_FATAL, "Unclosed variable \"%s\"", varname);
- free(varname);
- *out_FALSE_pp = p;
- *out_FALSE_val = var_Error;
- *out_FALSE_res = VPR_PARSE_MSG;
- return FALSE;
- }
+)
+{
+ size_t namelen;
+ char *varname;
+ Var *v;
+ Boolean haveModifier;
+ Boolean dynamic = FALSE;
- v = VarFind(varname, ctxt, TRUE);
+ const char *const start = p;
+ char endc = startc == '(' ? ')' : '}';
- /* At this point, p points just after the variable name,
- * either at ':' or at endc. */
+ p += 2; /* skip "${" or "$(" or "y(" */
+ varname = ParseVarname(&p, startc, endc, ctxt, eflags, &namelen);
- if (v == NULL)
- v = FindLocalLegacyVar(varname, namelen, ctxt, out_TRUE_extraModifiers);
+ if (*p == ':') {
+ haveModifier = TRUE;
+ } else if (*p == endc) {
+ haveModifier = FALSE;
+ } else {
+ Parse_Error(PARSE_FATAL, "Unclosed variable \"%s\"", varname);
+ free(varname);
+ *out_FALSE_pp = p;
+ *out_FALSE_val = FStr_InitRefer(var_Error);
+ *out_FALSE_res = VPR_ERR;
+ return FALSE;
+ }
- if (v == NULL) {
- /* Defer expansion of dynamic variables if they appear in non-local
- * context since they are not defined there. */
- dynamic = VarnameIsDynamic(varname, namelen) &&
- (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL);
+ v = VarFind(varname, ctxt, TRUE);
- if (!haveModifier) {
- p++; /* skip endc */
- *out_FALSE_pp = p;
- *out_FALSE_res = EvalUndefined(dynamic, start, p, varname, eflags,
- out_FALSE_freeIt, out_FALSE_val);
- return FALSE;
+ /* At this point, p points just after the variable name,
+ * either at ':' or at endc. */
+
+ if (v == NULL) {
+ v = FindLocalLegacyVar(varname, namelen, ctxt,
+ out_TRUE_extraModifiers);
}
- /* The variable expression is based on an undefined variable.
- * Nevertheless it needs a Var, for modifiers that access the
- * variable name, such as :L or :?.
- *
- * Most modifiers leave this expression in the "undefined" state
- * (VEF_UNDEF), only a few modifiers like :D, :U, :L, :P turn this
- * undefined expression into a defined expression (VEF_DEF).
- *
- * At the end, after applying all modifiers, if the expression
- * is still undefined, Var_Parse will return an empty string
- * instead of the actually computed value. */
- v = VarNew(varname, varname, "", 0);
- *out_TRUE_exprFlags = VEF_UNDEF;
- } else
- free(varname);
+ if (v == NULL) {
+ /*
+ * Defer expansion of dynamic variables if they appear in
+ * non-local context since they are not defined there.
+ */
+ dynamic = VarnameIsDynamic(varname, namelen) &&
+ (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL);
+
+ if (!haveModifier) {
+ p++; /* skip endc */
+ *out_FALSE_pp = p;
+ *out_FALSE_res = EvalUndefined(dynamic, start, p,
+ varname, eflags, out_FALSE_val);
+ return FALSE;
+ }
+
+ /*
+ * The variable expression is based on an undefined variable.
+ * Nevertheless it needs a Var, for modifiers that access the
+ * variable name, such as :L or :?.
+ *
+ * Most modifiers leave this expression in the "undefined"
+ * state (VEF_UNDEF), only a few modifiers like :D, :U, :L,
+ * :P turn this undefined expression into a defined
+ * expression (VEF_DEF).
+ *
+ * At the end, after applying all modifiers, if the expression
+ * is still undefined, Var_Parse will return an empty string
+ * instead of the actually computed value.
+ */
+ v = VarNew(FStr_InitOwn(varname), "", VAR_NONE);
+ *out_TRUE_exprFlags = VEF_UNDEF;
+ } else
+ free(varname);
- *out_TRUE_endc = endc;
- *out_TRUE_p = p;
- *out_TRUE_v = v;
- *out_TRUE_haveModifier = haveModifier;
- *out_TRUE_dynamic = dynamic;
- return TRUE;
+ *out_TRUE_endc = endc;
+ *out_TRUE_p = p;
+ *out_TRUE_v = v;
+ *out_TRUE_haveModifier = haveModifier;
+ *out_TRUE_dynamic = dynamic;
+ return TRUE;
+}
+
+/* Free the environment variable now since we own it. */
+static void
+FreeEnvVar(void **out_val_freeIt, Var *v, const char *value)
+{
+ char *varValue = Buf_Destroy(&v->val, FALSE);
+ if (value == varValue)
+ *out_val_freeIt = varValue;
+ else
+ free(varValue);
+
+ FStr_Done(&v->name);
+ free(v);
}
/*
@@ -3832,191 +4131,220 @@ ParseVarnameLong(
*/
/* coverity[+alloc : arg-*4] */
VarParseResult
-Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags,
- const char **out_val, void **out_val_freeIt)
+Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags, FStr *out_val)
{
- const char *p = *pp;
- const char *const start = p;
- Boolean haveModifier; /* TRUE if have modifiers for the variable */
- char startc; /* Starting character if variable in parens
- * or braces */
- char endc; /* Ending character if variable in parens
- * or braces */
- Boolean dynamic; /* TRUE if the variable is local and we're
- * expanding it in a non-local context. This
- * is done to support dynamic sources. The
- * result is just the expression, unaltered */
- const char *extramodifiers;
- Var *v;
- char *value;
- char eflags_str[VarEvalFlags_ToStringSize];
- VarExprFlags exprFlags = 0;
-
- VAR_DEBUG2("Var_Parse: %s with %s\n", start,
- Enum_FlagsToString(eflags_str, sizeof eflags_str, eflags,
- VarEvalFlags_ToStringSpecs));
-
- *out_val_freeIt = NULL;
- extramodifiers = NULL; /* extra modifiers to apply first */
- dynamic = FALSE;
-
- /* Appease GCC, which thinks that the variable might not be
- * initialized. */
- endc = '\0';
-
- startc = p[1];
- if (startc != '(' && startc != '{') {
- VarParseResult res;
- if (!ParseVarnameShort(startc, pp, ctxt, eflags, &res, out_val, &v))
- return res;
- haveModifier = FALSE;
- p++;
- } else {
- VarParseResult res;
- if (!ParseVarnameLong(p, startc, ctxt, eflags,
- pp, &res, out_val, out_val_freeIt,
- &endc, &p, &v, &haveModifier, &extramodifiers,
- &dynamic, &exprFlags))
- return res;
- }
-
- if (v->flags & VAR_IN_USE)
- Fatal("Variable %s is recursive.", v->name);
-
- /* XXX: This assignment creates an alias to the current value of the
- * variable. This means that as long as the value of the expression stays
- * the same, the value of the variable must not change.
- * Using the '::=' modifier, it could be possible to do exactly this.
- * At the bottom of this function, the resulting value is compared to the
- * then-current value of the variable. This might also invoke undefined
- * behavior. */
- value = Buf_GetAll(&v->val, NULL);
-
- /* Before applying any modifiers, expand any nested expressions from the
- * variable value. */
- if (strchr(value, '$') != NULL && (eflags & VARE_WANTRES)) {
- VarEvalFlags nested_eflags = eflags;
- if (opts.lint)
- nested_eflags &= ~(unsigned)VARE_UNDEFERR;
- v->flags |= VAR_IN_USE;
- (void)Var_Subst(value, ctxt, nested_eflags, &value);
- v->flags &= ~(unsigned)VAR_IN_USE;
- /* TODO: handle errors */
- *out_val_freeIt = value;
- }
+ const char *p = *pp;
+ const char *const start = p;
+ /* TRUE if have modifiers for the variable. */
+ Boolean haveModifier;
+ /* Starting character if variable in parens or braces. */
+ char startc;
+ /* Ending character if variable in parens or braces. */
+ char endc;
+ /*
+ * TRUE if the variable is local and we're expanding it in a
+ * non-local context. This is done to support dynamic sources.
+ * The result is just the expression, unaltered.
+ */
+ Boolean dynamic;
+ const char *extramodifiers;
+ Var *v;
+ FStr value;
+ char eflags_str[VarEvalFlags_ToStringSize];
+ VarExprFlags exprFlags = VEF_NONE;
- if (haveModifier || extramodifiers != NULL) {
- void *extraFree;
+ DEBUG2(VAR, "Var_Parse: %s with %s\n", start,
+ Enum_FlagsToString(eflags_str, sizeof eflags_str, eflags,
+ VarEvalFlags_ToStringSpecs));
- extraFree = NULL;
- if (extramodifiers != NULL) {
- const char *em = extramodifiers;
- value = ApplyModifiers(&em, value, '\0', '\0',
- v, &exprFlags, ctxt, eflags, &extraFree);
+ *out_val = FStr_InitRefer(NULL);
+ extramodifiers = NULL; /* extra modifiers to apply first */
+ dynamic = FALSE;
+
+ /*
+ * Appease GCC, which thinks that the variable might not be
+ * initialized.
+ */
+ endc = '\0';
+
+ startc = p[1];
+ if (startc != '(' && startc != '{') {
+ VarParseResult res;
+ if (!ParseVarnameShort(startc, pp, ctxt, eflags, &res,
+ &out_val->str, &v))
+ return res;
+ haveModifier = FALSE;
+ p++;
+ } else {
+ VarParseResult res;
+ if (!ParseVarnameLong(p, startc, ctxt, eflags,
+ pp, &res, out_val,
+ &endc, &p, &v, &haveModifier, &extramodifiers,
+ &dynamic, &exprFlags))
+ return res;
}
- if (haveModifier) {
- /* Skip initial colon. */
- p++;
+ if (v->flags & VAR_IN_USE)
+ Fatal("Variable %s is recursive.", v->name.str);
- value = ApplyModifiers(&p, value, startc, endc,
- v, &exprFlags, ctxt, eflags, out_val_freeIt);
- free(extraFree);
- } else {
- *out_val_freeIt = extraFree;
+ /*
+ * XXX: This assignment creates an alias to the current value of the
+ * variable. This means that as long as the value of the expression
+ * stays the same, the value of the variable must not change.
+ * Using the '::=' modifier, it could be possible to do exactly this.
+ * At the bottom of this function, the resulting value is compared to
+ * the then-current value of the variable. This might also invoke
+ * undefined behavior.
+ */
+ value = FStr_InitRefer(Buf_GetAll(&v->val, NULL));
+
+ /*
+ * Before applying any modifiers, expand any nested expressions from
+ * the variable value.
+ */
+ if (strchr(value.str, '$') != NULL && (eflags & VARE_WANTRES)) {
+ char *expanded;
+ VarEvalFlags nested_eflags = eflags;
+ if (opts.strict)
+ nested_eflags &= ~(unsigned)VARE_UNDEFERR;
+ v->flags |= VAR_IN_USE;
+ (void)Var_Subst(value.str, ctxt, nested_eflags, &expanded);
+ v->flags &= ~(unsigned)VAR_IN_USE;
+ /* TODO: handle errors */
+ value = FStr_InitOwn(expanded);
+ }
+
+ if (haveModifier || extramodifiers != NULL) {
+ if (extramodifiers != NULL) {
+ const char *em = extramodifiers;
+ value = ApplyModifiers(&em, value, '\0', '\0',
+ v, &exprFlags, ctxt, eflags);
+ }
+
+ if (haveModifier) {
+ p++; /* Skip initial colon. */
+
+ value = ApplyModifiers(&p, value, startc, endc,
+ v, &exprFlags, ctxt, eflags);
+ }
}
- }
-
- if (*p != '\0') /* Skip past endc if possible. */
- p++;
-
- *pp = p;
-
- if (v->flags & VAR_FROM_ENV) {
- /* Free the environment variable now since we own it,
- * but don't free the variable value if it will be returned. */
- Boolean keepValue = value == Buf_GetAll(&v->val, NULL);
- if (keepValue)
- *out_val_freeIt = value;
- (void)VarFreeEnv(v, !keepValue);
-
- } else if (exprFlags & VEF_UNDEF) {
- if (!(exprFlags & VEF_DEF)) {
- /* TODO: Use a local variable instead of out_val_freeIt.
- * Variables named out_* must only be written to. */
- if (*out_val_freeIt != NULL) {
- free(*out_val_freeIt);
- *out_val_freeIt = NULL;
- }
- if (dynamic) {
- value = bmake_strsedup(start, p);
- *out_val_freeIt = value;
- } else {
- /* The expression is still undefined, therefore discard the
- * actual value and return an error marker instead. */
- value = eflags & VARE_UNDEFERR ? var_Error : varUndefined;
- }
+
+ if (*p != '\0') /* Skip past endc if possible. */
+ p++;
+
+ *pp = p;
+
+ if (v->flags & VAR_FROM_ENV) {
+ FreeEnvVar(&value.freeIt, v, value.str);
+
+ } else if (exprFlags & VEF_UNDEF) {
+ if (!(exprFlags & VEF_DEF)) {
+ FStr_Done(&value);
+ if (dynamic) {
+ value = FStr_InitOwn(bmake_strsedup(start, p));
+ } else {
+ /*
+ * The expression is still undefined,
+ * therefore discard the actual value and
+ * return an error marker instead.
+ */
+ value = FStr_InitRefer(eflags & VARE_UNDEFERR
+ ? var_Error : varUndefined);
+ }
+ }
+ if (value.str != Buf_GetAll(&v->val, NULL))
+ Buf_Destroy(&v->val, TRUE);
+ FStr_Done(&v->name);
+ free(v);
}
- if (value != Buf_GetAll(&v->val, NULL))
- Buf_Destroy(&v->val, TRUE);
- free(v->name_freeIt);
- free(v);
- }
- *out_val = value;
- return VPR_UNKNOWN;
+ *out_val = (FStr){ value.str, value.freeIt };
+ return VPR_OK; /* XXX: Is not correct in all cases */
}
static void
-VarSubstNested(const char **const pp, Buffer *const buf, GNode *const ctxt,
- VarEvalFlags const eflags, Boolean *inout_errorReported)
+VarSubstDollarDollar(const char **pp, Buffer *res, VarEvalFlags eflags)
{
- const char *p = *pp;
- const char *nested_p = p;
- const char *val;
- void *val_freeIt;
-
- (void)Var_Parse(&nested_p, ctxt, eflags, &val, &val_freeIt);
- /* TODO: handle errors */
-
- if (val == var_Error || val == varUndefined) {
- if (!preserveUndefined) {
- p = nested_p;
- } else if ((eflags & VARE_UNDEFERR) || val == var_Error) {
- /* XXX: This condition is wrong. If val == var_Error,
- * this doesn't necessarily mean there was an undefined
- * variable. It could equally well be a parse error; see
- * unit-tests/varmod-order.exp. */
-
- /*
- * If variable is undefined, complain and skip the
- * variable. The complaint will stop us from doing anything
- * when the file is parsed.
- */
- if (!*inout_errorReported) {
- Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"",
- (int)(size_t)(nested_p - p), p);
- }
- p = nested_p;
- *inout_errorReported = TRUE;
+ /*
+ * A dollar sign may be escaped with another dollar
+ * sign.
+ */
+ if (save_dollars && (eflags & VARE_KEEP_DOLLAR))
+ Buf_AddByte(res, '$');
+ Buf_AddByte(res, '$');
+ *pp += 2;
+}
+
+static void
+VarSubstExpr(const char **pp, Buffer *buf, GNode *ctxt,
+ VarEvalFlags eflags, Boolean *inout_errorReported)
+{
+ const char *p = *pp;
+ const char *nested_p = p;
+ FStr val;
+
+ (void)Var_Parse(&nested_p, ctxt, eflags, &val);
+ /* TODO: handle errors */
+
+ if (val.str == var_Error || val.str == varUndefined) {
+ if (!(eflags & VARE_KEEP_UNDEF)) {
+ p = nested_p;
+ } else if ((eflags & VARE_UNDEFERR) || val.str == var_Error) {
+
+ /*
+ * XXX: This condition is wrong. If val == var_Error,
+ * this doesn't necessarily mean there was an undefined
+ * variable. It could equally well be a parse error;
+ * see unit-tests/varmod-order.exp.
+ */
+
+ /*
+ * If variable is undefined, complain and skip the
+ * variable. The complaint will stop us from doing
+ * anything when the file is parsed.
+ */
+ if (!*inout_errorReported) {
+ Parse_Error(PARSE_FATAL,
+ "Undefined variable \"%.*s\"",
+ (int)(size_t)(nested_p - p), p);
+ }
+ p = nested_p;
+ *inout_errorReported = TRUE;
+ } else {
+ /* Copy the initial '$' of the undefined expression,
+ * thereby deferring expansion of the expression, but
+ * expand nested expressions if already possible.
+ * See unit-tests/varparse-undef-partial.mk. */
+ Buf_AddByte(buf, *p);
+ p++;
+ }
} else {
- /* Copy the initial '$' of the undefined expression,
- * thereby deferring expansion of the expression, but
- * expand nested expressions if already possible.
- * See unit-tests/varparse-undef-partial.mk. */
- Buf_AddByte(buf, *p);
- p++;
+ p = nested_p;
+ Buf_AddStr(buf, val.str);
}
- } else {
- p = nested_p;
- Buf_AddStr(buf, val);
- }
- free(val_freeIt);
+ FStr_Done(&val);
- *pp = p;
+ *pp = p;
}
-/* Expand all variable expressions like $V, ${VAR}, $(VAR:Modifiers) in the
+/*
+ * Skip as many characters as possible -- either to the end of the string
+ * or to the next dollar sign (variable expression).
+ */
+static void
+VarSubstPlain(const char **pp, Buffer *res)
+{
+ const char *p = *pp;
+ const char *start = p;
+
+ for (p++; *p != '$' && *p != '\0'; p++)
+ continue;
+ Buf_AddBytesBetween(res, start, p);
+ *pp = p;
+}
+
+/*
+ * Expand all variable expressions like $V, ${VAR}, $(VAR:Modifiers) in the
* given string.
*
* Input:
@@ -4029,90 +4357,76 @@ VarSubstNested(const char **const pp, Buffer *const buf, GNode *const ctxt,
VarParseResult
Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res)
{
- const char *p = str;
- Buffer buf; /* Buffer for forming things */
-
- /* Set true if an error has already been reported,
- * to prevent a plethora of messages when recursing */
- /* XXX: Why is the 'static' necessary here? */
- static Boolean errorReported;
-
- Buf_Init(&buf);
- errorReported = FALSE;
-
- while (*p != '\0') {
- if (p[0] == '$' && p[1] == '$') {
- /* A dollar sign may be escaped with another dollar sign. */
- if (save_dollars && (eflags & VARE_KEEP_DOLLAR))
- Buf_AddByte(&buf, '$');
- Buf_AddByte(&buf, '$');
- p += 2;
-
- } else if (p[0] == '$') {
- VarSubstNested(&p, &buf, ctxt, eflags, &errorReported);
-
- } else {
- /*
- * Skip as many characters as possible -- either to the end of
- * the string or to the next dollar sign (variable expression).
- */
- const char *plainStart = p;
-
- for (p++; *p != '$' && *p != '\0'; p++)
- continue;
- Buf_AddBytesBetween(&buf, plainStart, p);
+ const char *p = str;
+ Buffer res;
+
+ /* Set true if an error has already been reported,
+ * to prevent a plethora of messages when recursing */
+ /* XXX: Why is the 'static' necessary here? */
+ static Boolean errorReported;
+
+ Buf_Init(&res);
+ errorReported = FALSE;
+
+ while (*p != '\0') {
+ if (p[0] == '$' && p[1] == '$')
+ VarSubstDollarDollar(&p, &res, eflags);
+ else if (p[0] == '$')
+ VarSubstExpr(&p, &res, ctxt, eflags, &errorReported);
+ else
+ VarSubstPlain(&p, &res);
}
- }
- *out_res = Buf_DestroyCompact(&buf);
- return VPR_OK;
+ *out_res = Buf_DestroyCompact(&res);
+ return VPR_OK;
}
/* Initialize the variables module. */
void
Var_Init(void)
{
- VAR_INTERNAL = GNode_New("Internal");
- VAR_GLOBAL = GNode_New("Global");
- VAR_CMDLINE = GNode_New("Command");
+ VAR_INTERNAL = GNode_New("Internal");
+ VAR_GLOBAL = GNode_New("Global");
+ VAR_CMDLINE = GNode_New("Command");
}
/* Clean up the variables module. */
void
Var_End(void)
{
- Var_Stats();
+ Var_Stats();
}
void
Var_Stats(void)
{
- HashTable_DebugStats(&VAR_GLOBAL->context, "VAR_GLOBAL");
+ HashTable_DebugStats(&VAR_GLOBAL->vars, "VAR_GLOBAL");
}
/* Print all variables in a context, sorted by name. */
void
Var_Dump(GNode *ctxt)
{
- Vector /* of const char * */ vec;
- HashIter hi;
- size_t i;
- const char **varnames;
+ Vector /* of const char * */ vec;
+ HashIter hi;
+ size_t i;
+ const char **varnames;
- Vector_Init(&vec, sizeof(const char *));
+ Vector_Init(&vec, sizeof(const char *));
- HashIter_Init(&hi, &ctxt->context);
- while (HashIter_Next(&hi) != NULL)
- *(const char **)Vector_Push(&vec) = hi.entry->key;
- varnames = vec.items;
+ HashIter_Init(&hi, &ctxt->vars);
+ while (HashIter_Next(&hi) != NULL)
+ *(const char **)Vector_Push(&vec) = hi.entry->key;
+ varnames = vec.items;
- qsort(varnames, vec.len, sizeof varnames[0], str_cmp_asc);
+ qsort(varnames, vec.len, sizeof varnames[0], str_cmp_asc);
- for (i = 0; i < vec.len; i++) {
- const char *varname = varnames[i];
- Var *var = HashTable_FindValue(&ctxt->context, varname);
- debug_printf("%-16s = %s\n", varname, Buf_GetAll(&var->val, NULL));
- }
+ for (i = 0; i < vec.len; i++) {
+ const char *varname = varnames[i];
+ Var *var = HashTable_FindValue(&ctxt->vars, varname);
+ debug_printf("%-16s = %s\n",
+ varname, Buf_GetAll(&var->val, NULL));
+ }
- Vector_Done(&vec);
+ Vector_Done(&vec);
}