Import 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
This commit is contained in:
Simon J. Gerraty 2021-01-13 17:24:34 -08:00
parent 1b65f0bd2b
commit 8e11a9b425
241 changed files with 24179 additions and 14437 deletions

241
ChangeLog
View File

@ -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

64
FILES
View File

@ -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

View File

@ -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

View File

@ -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 .

View File

@ -1,2 +1,2 @@
# keep this compatible with sh and make
_MAKE_VERSION=20201117
_MAKE_VERSION=20210110

1307
arch.c

File diff suppressed because it is too large Load Diff

18
bmake.1
View File

@ -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 .

View File

@ -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

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

133
buf.c
View File

@ -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;
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);
}
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';
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];
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);
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);
}

28
buf.h
View File

@ -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);

958
compat.c

File diff suppressed because it is too large Load Diff

1571
cond.c

File diff suppressed because it is too large Load Diff

2069
dir.c

File diff suppressed because it is too large Load Diff

36
dir.h
View File

@ -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 *);

11
enum.c
View File

@ -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*/
}

69
enum.h
View File

@ -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, \

View File

@ -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 */

View File

@ -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;
}

View File

@ -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 @@ filemon_process(struct filemon *F)
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 @@ filemon_process(struct filemon *F)
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 @@ filemon_process(struct filemon *F)
}
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);
}

675
for.c
View File

@ -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
* .for <varname...> in <value...>
* # the body
* .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.
* 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.
*
* During parsing, any nested .for loops are just passed through; they get
* handled recursively in For_Eval when the enclosing .for loop is evaluated
* in For_Run.
*
* When the .for loop has been parsed completely, the variable expressions
* for the iteration variables are replaced with expressions of the form
* ${:Uvalue}, and then this modified body is "included" as a special file.
* 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;
}
return 0;
}
p += 3;
p += 3;
/*
* we found a for loop, and now we are going to parse it.
*/
/*
* 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;
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;
/* Grab the variables. Terminate on "in". */
for (;;) {
size_t len;
cpp_skip_whitespace(&p);
if (*p == '\0') {
Parse_Error(PARSE_FATAL, "missing `in' in for");
For_Free(f);
return -1;
}
/*
* XXX: This allows arbitrary variable names;
* see directive-for.mk.
*/
for (len = 1; p[len] != '\0' && !ch_isspace(p[len]); len++)
continue;
if (len == 2 && p[0] == 'i' && p[1] == 'n') {
p += 2;
break;
}
if (len == 1)
f->short_var = TRUE;
ForAddVar(f, p, len);
p += len;
}
if (f->vars.len == 0) {
Parse_Error(PARSE_FATAL, "no iteration variables in for");
For_Free(f);
return -1;
}
cpp_skip_whitespace(&p);
if (*p == '\0') {
Parse_Error(PARSE_FATAL, "missing `in' in for");
For_Free(f);
return -1;
{
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 (f->items.len == 1 && f->items.words[0][0] == '\0')
f->items.len = 0; /* .for var in ${:U} */
}
/* XXX: This allows arbitrary variable names; see directive-for.mk. */
for (len = 1; p[len] != '\0' && !ch_isspace(p[len]); len++)
continue;
{
size_t nitems, nvars;
if (len == 2 && p[0] == 'i' && p[1] == 'n') {
p += 2;
break;
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;
}
}
if (len == 1)
f->short_var = TRUE;
ForAddVar(f, p, len);
p += len;
}
if (f->vars.len == 0) {
Parse_Error(PARSE_FATAL, "no iteration variables in for");
For_Free(f);
return -1;
}
cpp_skip_whitespace(&p);
{
char *items;
(void)Var_Subst(p, VAR_GLOBAL, VARE_WANTRES, &items);
/* TODO: handle errors */
f->items = Str_Words(items, FALSE);
free(items);
if (f->items.len == 1 && f->items.words[0][0] == '\0')
f->items.len = 0; /* .for var in ${:U} */
}
{
size_t nitems, nvars;
if ((nitems = f->items.len) > 0 && nitems % (nvars = f->vars.len)) {
Parse_Error(PARSE_FATAL,
"Wrong number of words (%zu) in .for substitution list"
" with %zu variables", nitems, nvars);
/*
* Return 'success' so that the body of the .for loop is
* accumulated.
* Remove all items so that the loop doesn't iterate.
*/
f->items.len = 0;
}
}
accumFor = f;
forLevel = 1;
return 1;
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;
const char *ptr = line;
if (*ptr == '.') {
ptr++;
cpp_skip_whitespace(&ptr);
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);
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;
char ch, var_start, var_end;
int depth;
size_t len;
var_start = *var;
if (var_start == '\0')
/* just escape the $ */
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;
if (var_start == '(')
var_end = ')';
else if (var_start == '{')
var_end = '}';
else
/* Single char variable */
return 1;
depth = 1;
for (len = 1; (ch = var[len++]) != '\0';) {
if (ch == var_start)
depth++;
else if (ch == var_end && --depth == 0)
return len;
}
/* Variable end not found, escape the $ */
return 0;
}
/* The .for loop substitutes the items as ${:U<value>...}, which means
* that characters that break this syntax must be backslash-escaped. */
static Boolean
NeedsEscapes(const char *word, char endc)
{
const char *p;
for (p = word; *p != '\0'; p++) {
if (*p == ':' || *p == '$' || *p == '\\' || *p == endc)
return TRUE;
}
return FALSE;
}
/* While expanding the body of a .for loop, write the item in the ${:U...}
* expression, escaping characters as needed.
*
* The result is later unescaped by ApplyModifier_Defined. */
static void
Buf_AddEscaped(Buffer *cmds, const char *item, char ech)
{
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);
}
}
/* While expanding the body of a .for loop, replace expressions like
* ${i}, ${i:...}, $(i) or $(i:...) with their ${:U...} expansion. */
static void
SubstVarLong(For *f, const char **pp, const char **inout_mark, char ech)
{
size_t i;
const char *p = *pp;
for (i = 0; i < f->vars.len; i++) {
ForVar *forVar = Vector_Get(&f->vars, i);
char *var = forVar->name;
size_t vlen = forVar->len;
/* XXX: undefined behavior for p if vlen is longer than p? */
if (memcmp(p, var, vlen) != 0)
continue;
/* XXX: why test for backslash here? */
if (p[vlen] != ':' && p[vlen] != ech && p[vlen] != '\\')
continue;
/* Found a variable match. Replace with :U<value> */
Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
Buf_AddStr(&f->curBody, ":U");
Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], ech);
p += vlen;
*inout_mark = p;
break;
}
*pp = p;
}
/* While expanding the body of a .for loop, replace single-character
* variable expressions like $i with their ${:U...} expansion. */
static void
SubstVarShort(For *f, char const ch, const char **pp, const char **inout_mark)
{
const char *p = *pp;
size_t i;
/* Probably a single character name, ignore $$ and stupid ones. */
if (!f->short_var || strchr("}):$", ch) != NULL) {
p++;
*pp = p;
return;
}
for (i = 0; i < f->vars.len; i++) {
ForVar *var = Vector_Get(&f->vars, i);
const char *varname = var->name;
if (varname[0] != ch || varname[1] != '\0')
continue;
/* Found a variable match. Replace with ${:U<value>} */
Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
Buf_AddStr(&f->curBody, "{:U");
Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], '}');
Buf_AddByte(&f->curBody, '}');
*inout_mark = ++p;
break;
}
*pp = p;
}
/*
* Scan the for loop body and replace references to the loop variables
* with variable references that expand to the required text.
* 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;
for (p = word; *p != '\0'; p++) {
if (*p == ':' || *p == '$' || *p == '\\' || *p == endc)
return TRUE;
}
return FALSE;
}
/*
* While expanding the body of a .for loop, write the item in the ${:U...}
* expression, escaping characters as needed.
*
* Using variable expansions ensures that the .for loop can't generate
* The result is later unescaped by ApplyModifier_Defined.
*/
static void
Buf_AddEscaped(Buffer *cmds, const char *item, char endc)
{
char ch;
if (!NeedsEscapes(item, endc)) {
Buf_AddStr(cmds, item);
return;
}
/* Escape ':', '$', '\\' and 'endc' - these will be removed later by
* :U processing, see ApplyModifier_Defined. */
while ((ch = *item++) != '\0') {
if (ch == '$') {
size_t len = for_var_len(item);
if (len != 0) {
Buf_AddBytes(cmds, item - 1, len + 1);
item += len;
continue;
}
Buf_AddByte(cmds, '\\');
} else if (ch == ':' || ch == '\\' || ch == endc)
Buf_AddByte(cmds, '\\');
Buf_AddByte(cmds, ch);
}
}
/*
* While expanding the body of a .for loop, replace the variable name of an
* expression like ${i} or ${i:...} or $(i) or $(i:...) with ":Uvalue".
*/
static void
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 *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.
*/
static void
SubstVarShort(For *f, const char *p, const char **inout_mark)
{
const char ch = *p;
ForVar *vars;
size_t i;
/* Skip $$ and stupid ones. */
if (!f->short_var || strchr("}):$", ch) != NULL)
return;
vars = Vector_Get(&f->vars, 0);
for (i = 0; i < f->vars.len; i++) {
const char *varname = vars[i].name;
if (varname[0] == ch && varname[1] == '\0')
goto found;
}
return;
found:
/* Replace $<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, '}');
}
/*
* Compute the body for the current iteration by copying the unexpanded body,
* replacing the expressions for the iteration variables on the way.
*
* Using variable expressions ensures that the .for loop can't generate
* syntax, and that the later parsing will still see a variable.
* 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;
const char *p, *bodyEnd;
const char *mark; /* where the last replacement left off */
if (f->sub_next + f->vars.len > f->items.len) {
/* No more iterations */
For_Free(f);
return NULL;
}
Buf_Empty(&f->curBody);
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;
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);
*out_len = Buf_Len(&f->curBody);
cmds_str = Buf_GetAll(&f->curBody, NULL);
DEBUG1(FOR, "For: loop body:\n%s", cmds_str);
f->sub_next += f->vars.len;
return cmds_str;
Buf_AddBytesBetween(&f->curBody, mark, bodyEnd);
}
/* Run the for loop, imitating the actions of an include file. */
/*
* 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;
}
ForSubstBody(f);
DEBUG1(FOR, "For: loop body:\n%s", f->curBody.data);
f->sub_next += (unsigned int)f->vars.len;
*out_len = f->curBody.len;
return f->curBody.data;
}
/* Run the .for loop, imitating the actions of an include file. */
void
For_Run(int lineno)
{
For *f = accumFor;
accumFor = NULL;
For *f = accumFor;
accumFor = NULL;
if (f->items.len == 0) {
/* Nothing to expand - possibly due to an earlier syntax error. */
For_Free(f);
return;
}
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);
}

41
hash.c
View File

@ -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)
{

72
hash.h
View File

@ -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 */

87
import.sh Executable file
View File

@ -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

3908
job.c

File diff suppressed because it is too large Load Diff

84
job.h
View File

@ -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 */

300
lst.c
View File

@ -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;
return list;
for (ln = list->first; ln != NULL; ln = next) {
next = ln->next;
free(ln);
}
}
void
Lst_DoneCall(List *list, LstFreeProc freeProc)
{
ListNode *ln, *next;
for (ln = list->first; ln != NULL; ln = next) {
next = ln->next;
freeProc(ln->datum);
free(ln);
}
}
/* Free a list and all its nodes. The node data are not freed though. */
void
Lst_Free(List *list)
{
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 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 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;
ListNode *ln;
assert(datum != NULL);
assert(datum != NULL);
for (ln = list->first; ln != NULL; ln = ln->next)
if (ln->datum == datum)
return ln;
for (ln = list->first; ln != NULL; ln = ln->next)
if (ln->datum == datum)
return ln;
return NULL;
return NULL;
}
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;
}
/* 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;
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);
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);
}
ListNode *ln;
/*
* for using the list as a queue
*/
/* Add the datum to the tail of the given list. */
void
Lst_Enqueue(List *list, void *datum)
{
Lst_Append(list, datum);
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);
}

88
lst.h
View File

@ -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 */

364
main.c
View File

@ -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 @@ MainParseArgs(int argc, char **argv)
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 @@ MainParseArgs(int argc, char **argv)
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 @@ Cmd_Exec(const char *cmd, const char **errfmt)
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;
}

View File

@ -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.
@ -72,19 +72,6 @@
* from: @(#)config.h 8.1 (Berkeley) 6/6/93
*/
/*
* 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
@ -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

18
make.1
View File

@ -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 .

1809
make.c

File diff suppressed because it is too large Load Diff

776
make.h
View File

@ -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,
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,
/* 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,
/* 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,
/* 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 */
/* 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,
/* 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
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 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 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 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 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 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;
/* 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;
/* .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;
/* 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;
/* 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 "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;
/* 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;
/* 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;
/* 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 DEBUG_IMPL(module, args) \
do { \
if (DEBUG(module)) \
debug_printf args; \
} while (/*CONSTCOND*/ 0)
#define DEBUG0(module, text) \
if (!DEBUG(module)) (void)0; \
else debug_printf("%s", 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

View File

@ -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);
}

View File

@ -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);
}

359
meta.c
View File

@ -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 */

4
meta.h
View File

@ -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 *);

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

4
mk/install-mk Normal file → Executable file
View File

@ -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

View File

@ -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

0
mk/mkopt.sh Executable file → Normal file
View File

View File

@ -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

View File

@ -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.

266
nonints.h
View File

@ -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,
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,
/* 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,
/* 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 '$$' 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,
/* Both parsing and evaluation succeeded. */
VPR_OK,
/* See if a message has already been printed for this error. */
VPR_ANY_MSG = 0x0001,
/* Parsing or evaluating failed, with an error message. */
VPR_ERR,
/* Parsing failed.
* No error message has been printed yet.
* Deprecated, migrate to VPR_PARSE_MSG instead. */
VPR_PARSE_SILENT = 0x0002,
/*
* 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
/* Parsing failed.
* An error message has already been printed. */
VPR_PARSE_MSG = VPR_PARSE_SILENT | VPR_ANY_MSG,
/* Parsing succeeded.
* During evaluation, VARE_UNDEFERR was set and there was an undefined
* variable.
* No error message has been printed yet.
* Deprecated, migrate to VPR_UNDEF_MSG instead. */
VPR_UNDEF_SILENT = 0x0004,
/* Parsing succeeded.
* During evaluation, VARE_UNDEFERR was set and there was an undefined
* variable.
* An error message has already been printed. */
VPR_UNDEF_MSG = VPR_UNDEF_SILENT | VPR_ANY_MSG,
/* Parsing succeeded.
* Evaluation failed.
* No error message has been printed yet.
* Deprecated, migrate to VPR_EVAL_MSG instead. */
VPR_EVAL_SILENT = 0x0006,
/* Parsing succeeded.
* Evaluation failed.
* An error message has already been printed. */
VPR_EVAL_MSG = VPR_EVAL_SILENT | VPR_ANY_MSG,
/* The exact error handling status is not known yet.
* Deprecated, migrate to VPR_OK or any VPE_*_MSG instead. */
VPR_UNKNOWN = 0x0008
} VarParseResult;
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);

0
os.sh Executable file → Normal file
View File

4058
parse.c

File diff suppressed because it is too large Load Diff

View File

@ -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

15
str.c
View File

@ -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 };

2929
suff.c

File diff suppressed because it is too large Load Diff

554
targ.c
View File

@ -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;
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;
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;
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->name);
free(gn->uname);
free(gn->path);
free(gn);
/* 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);
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;
}
{
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)
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;
/*
* 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)
/* Add the named nodes to the list, creating them as necessary. */
void
Targ_FindList(GNodeList *gns, StringList *names)
{
StringListNode *ln;
GNodeList *nodes = Lst_New();
for (ln = names->first; ln != NULL; ln = ln->next) {
const char *name = ln->datum;
GNode *gn = Targ_GetNode(name);
Lst_Append(nodes, gn);
}
return nodes;
}
StringListNode *ln;
/* Return true if should ignore errors when creating gn. */
Boolean
Targ_Ignore(const GNode *gn)
{
return opts.ignoreErrors || gn->type & OP_IGNORE;
}
/* 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;
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;
type &= ~OP_OPMASK;
while (type != 0) {
tbit = 1 << (ffs(type) - 1);
type &= ~tbit;
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);
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;
}
}
}
}

16
trace.c
View File

@ -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);

View File

@ -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

View File

@ -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 ; \

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}'>'

9
unit-tests/cond-eof.exp Normal file
View File

@ -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

20
unit-tests/cond-eof.mk Normal file
View File

@ -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

View File

@ -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:
@:;

View File

@ -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:
@:;

View File

@ -0,0 +1,3 @@
: Making dot-main-target-1a.
: Making dot-main-target-1b.
exit status 0

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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} == "")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:
@:;

View File

@ -0,0 +1,6 @@
false
*** Error code 1 (continuing)
Stop.
make: stopped in unit-tests
exit status 1

View File

@ -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.

View File

@ -0,0 +1,6 @@
false
*** Error code 1 (continuing)
Stop.
make: stopped in unit-tests
exit status 1

View File

@ -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.

View File

@ -0,0 +1,7 @@
: Making all out of nothing.
false
*** Error code 1 (continuing)
Stop.
make: stopped in unit-tests
exit status 1

View File

@ -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

View File

@ -0,0 +1,7 @@
: all
false
*** Error code 1 (continuing)
Stop.
make: stopped in unit-tests
exit status 1

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:
@:;

View File

@ -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

View File

@ -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
# 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
# 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 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
. info 1-then
.elif 1 # ok
. info 1-elif
.elsif 1 # oops: misspelled
. info 1-elsif
.elseif 1 # oops: misspelled
. info 1-elseif
# 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
.info begin .elif misspellings tests, part 2
# 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
# 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
.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 This branch is not taken.
.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.
.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 begin .elif misspellings tests, part 3
.if 0
. info 0-then
.elsif 0 # oops: misspelled
. info 0-elsif
.endif
.if 0
. info 0-then
.elseif 0 # oops: misspelled
. info 0-elseif
.endif
.info which branch is taken on misspelling after false?
.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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:
@:;

Some files were not shown because too many files have changed in this diff Show More