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

333
arch.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $ */
/* $NetBSD: arch.c,v 1.193 2021/01/09 16:06:09 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -68,7 +68,8 @@
* SUCH DAMAGE.
*/
/* Manipulate libraries, archives and their members.
/*
* Manipulate libraries, archives and their members.
*
* The first time an archive is referenced, all of its members' headers are
* read and cached and the archive closed again. All cached archives are kept
@ -146,12 +147,12 @@ struct ar_hdr {
#include "dir.h"
/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $");
MAKE_RCSID("$NetBSD: arch.c,v 1.193 2021/01/09 16:06:09 rillig Exp $");
typedef struct List ArchList;
typedef struct ListNode ArchListNode;
static ArchList *archives; /* The archives we've already examined */
static ArchList archives; /* The archives we've already examined */
typedef struct Arch {
char *name; /* Name of archive */
@ -218,52 +219,47 @@ ArchFree(void *ap)
/*
* Parse an archive specification such as "archive.a(member1 member2.${EXT})",
* adding nodes for the expanded members to nodeLst. Nodes are created as
* adding nodes for the expanded members to gns. Nodes are created as
* necessary.
*
* Input:
* pp The start of the specification.
* nodeLst The list on which to place the nodes.
* gns The list on which to place the nodes.
* ctxt The context in which to expand variables.
*
* Output:
* return TRUE if it was a valid specification.
* *pp Points to the first non-space after the archive spec.
* *nodeLst Nodes for the members have been added.
*/
Boolean
Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
Arch_ParseArchive(char **pp, GNodeList *gns, GNode *ctxt)
{
char *cp; /* Pointer into line */
GNode *gn; /* New node */
char *libName; /* Library-part of specification */
char *libName_freeIt = NULL;
MFStr libName; /* Library-part of specification */
char *memName; /* Member-part of specification */
char saveChar; /* Ending delimiter of member-name */
Boolean expandLibName; /* Whether the parsed libName contains
* variable expressions that need to be
* expanded */
libName = *pp;
libName = MFStr_InitRefer(*pp);
expandLibName = FALSE;
for (cp = libName; *cp != '(' && *cp != '\0';) {
for (cp = libName.str; *cp != '(' && *cp != '\0';) {
if (*cp == '$') {
/*
* Variable spec, so call the Var module to parse the puppy
* so we can safely advance beyond it...
*/
/* Expand nested variable expressions. */
/* XXX: This code can probably be shortened. */
const char *nested_p = cp;
void *result_freeIt;
const char *result;
FStr result;
Boolean isError;
/* XXX: is expanded twice: once here and once below */
(void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR,
&result, &result_freeIt);
(void)Var_Parse(&nested_p, ctxt,
VARE_WANTRES | VARE_UNDEFERR, &result);
/* TODO: handle errors */
isError = result == var_Error;
free(result_freeIt);
isError = result.str == var_Error;
FStr_Done(&result);
if (isError)
return FALSE;
@ -275,9 +271,11 @@ Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
*cp++ = '\0';
if (expandLibName) {
(void)Var_Subst(libName, ctxt, VARE_WANTRES | VARE_UNDEFERR, &libName);
char *expanded;
(void)Var_Subst(libName.str, ctxt,
VARE_WANTRES | VARE_UNDEFERR, &expanded);
/* TODO: handle errors */
libName_freeIt = libName;
libName = MFStr_InitOwn(expanded);
}
@ -287,27 +285,25 @@ Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
* place and skip to the end of it (either white-space or
* a close paren).
*/
Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */
Boolean doSubst = FALSE;
pp_skip_whitespace(&cp);
memName = cp;
while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) {
if (*cp == '$') {
/*
* Variable spec, so call the Var module to parse the puppy
* so we can safely advance beyond it...
*/
void *freeIt;
const char *result;
/* Expand nested variable expressions. */
/* XXX: This code can probably be shortened. */
FStr result;
Boolean isError;
const char *nested_p = cp;
(void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR,
&result, &freeIt);
(void)Var_Parse(&nested_p, ctxt,
VARE_WANTRES | VARE_UNDEFERR,
&result);
/* TODO: handle errors */
isError = result == var_Error;
free(freeIt);
isError = result.str == var_Error;
FStr_Done(&result);
if (isError)
return FALSE;
@ -321,110 +317,118 @@ Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
/*
* If the specification ends without a closing parenthesis,
* chances are there's something wrong (like a missing backslash),
* so it's better to return failure than allow such things to happen
* chances are there's something wrong (like a missing
* backslash), so it's better to return failure than allow
* such things to happen
*/
if (*cp == '\0') {
Parse_Error(PARSE_FATAL, "No closing parenthesis in archive specification");
Parse_Error(PARSE_FATAL,
"No closing parenthesis "
"in archive specification");
return FALSE;
}
/*
* If we didn't move anywhere, we must be done
*/
if (cp == memName) {
if (cp == memName)
break;
}
saveChar = *cp;
*cp = '\0';
/*
* XXX: This should be taken care of intelligently by
* SuffExpandChildren, both for the archive and the member portions.
* SuffExpandChildren, both for the archive and the member
* portions.
*/
/*
* If member contains variables, try and substitute for them.
* This will slow down archive specs with dynamic sources, of course,
* since we'll be (non-)substituting them three times, but them's
* the breaks -- we need to do this since SuffExpandChildren calls
* us, otherwise we could assume the thing would be taken care of
* later.
* This will slow down archive specs with dynamic sources, of
* course, since we'll be (non-)substituting them three
* times, but them's the breaks -- we need to do this since
* SuffExpandChildren calls us, otherwise we could assume the
* thing would be taken care of later.
*/
if (doSubst) {
char *buf;
char *sacrifice;
char *oldMemName = memName;
char *fullName;
char *p;
char *unexpandedMemName = memName;
(void)Var_Subst(memName, ctxt, VARE_WANTRES | VARE_UNDEFERR,
(void)Var_Subst(memName, ctxt,
VARE_WANTRES | VARE_UNDEFERR,
&memName);
/* TODO: handle errors */
/*
* Now form an archive spec and recurse to deal with nested
* variables and multi-word variable values.... The results
* are just placed at the end of the nodeLst we're returning.
* Now form an archive spec and recurse to deal with
* nested variables and multi-word variable values.
*/
buf = sacrifice = str_concat4(libName, "(", memName, ")");
fullName = str_concat4(libName.str, "(", memName, ")");
p = fullName;
if (strchr(memName, '$') != NULL &&
strcmp(memName, oldMemName) == 0) {
strcmp(memName, unexpandedMemName) == 0) {
/*
* Must contain dynamic sources, so we can't deal with it now.
* Just create an ARCHV node for the thing and let
* SuffExpandChildren handle it...
* Must contain dynamic sources, so we can't
* deal with it now. Just create an ARCHV node
* for the thing and let SuffExpandChildren
* handle it.
*/
gn = Targ_GetNode(buf);
gn = Targ_GetNode(fullName);
gn->type |= OP_ARCHV;
Lst_Append(nodeLst, gn);
Lst_Append(gns, gn);
} else if (!Arch_ParseArchive(&sacrifice, nodeLst, ctxt)) {
} else if (!Arch_ParseArchive(&p, gns, ctxt)) {
/* Error in nested call. */
free(buf);
free(fullName);
/* XXX: does unexpandedMemName leak? */
return FALSE;
}
free(buf);
free(fullName);
/* XXX: does unexpandedMemName leak? */
} else if (Dir_HasWildcards(memName)) {
StringList *members = Lst_New();
Dir_Expand(memName, dirSearchPath, members);
StringList members = LST_INIT;
Dir_Expand(memName, &dirSearchPath, &members);
while (!Lst_IsEmpty(members)) {
char *member = Lst_Dequeue(members);
char *fullname = str_concat4(libName, "(", member, ")");
while (!Lst_IsEmpty(&members)) {
char *member = Lst_Dequeue(&members);
char *fullname = str_concat4(libName.str, "(",
member, ")");
free(member);
gn = Targ_GetNode(fullname);
free(fullname);
gn->type |= OP_ARCHV;
Lst_Append(nodeLst, gn);
Lst_Append(gns, gn);
}
Lst_Free(members);
Lst_Done(&members);
} else {
char *fullname = str_concat4(libName, "(", memName, ")");
char *fullname = str_concat4(libName.str, "(", memName,
")");
gn = Targ_GetNode(fullname);
free(fullname);
/*
* We've found the node, but have to make sure the rest of the
* world knows it's an archive member, without having to
* constantly check for parentheses, so we type the thing with
* the OP_ARCHV bit before we place it on the end of the
* provided list.
* We've found the node, but have to make sure the
* rest of the world knows it's an archive member,
* without having to constantly check for parentheses,
* so we type the thing with the OP_ARCHV bit before
* we place it on the end of the provided list.
*/
gn->type |= OP_ARCHV;
Lst_Append(nodeLst, gn);
Lst_Append(gns, gn);
}
if (doSubst) {
if (doSubst)
free(memName);
}
*cp = saveChar;
}
free(libName_freeIt);
MFStr_Done(&libName);
cp++; /* skip the ')' */
/* We promised that pp would be set up at the next non-space. */
@ -433,7 +437,8 @@ Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
return TRUE;
}
/* Locate a member of an archive, given the path of the archive and the path
/*
* Locate a member of an archive, given the path of the archive and the path
* of the desired member.
*
* Input:
@ -449,8 +454,8 @@ Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
static struct ar_hdr *
ArchStatMember(const char *archive, const char *member, Boolean addToCache)
{
#define AR_MAX_NAME_LEN (sizeof arh.AR_NAME - 1)
FILE *arch; /* Stream to archive */
#define AR_MAX_NAME_LEN (sizeof arh.ar_name - 1)
FILE *arch;
size_t size; /* Size of archive member */
char magic[SARMAG];
ArchListNode *ln;
@ -463,11 +468,9 @@ ArchStatMember(const char *archive, const char *member, Boolean addToCache)
* Because of space constraints and similar things, files are archived
* using their basename, not the entire path.
*/
const char *lastSlash = strrchr(member, '/');
if (lastSlash != NULL)
member = lastSlash + 1;
member = str_basename(member);
for (ln = archives->first; ln != NULL; ln = ln->next) {
for (ln = archives.first; ln != NULL; ln = ln->next) {
const Arch *a = ln->datum;
if (strcmp(a->name, archive) == 0)
break;
@ -487,7 +490,6 @@ ArchStatMember(const char *archive, const char *member, Boolean addToCache)
size_t len = strlen(member);
if (len > AR_MAX_NAME_LEN) {
len = AR_MAX_NAME_LEN;
snprintf(copy, sizeof copy, "%s", member);
hdr = HashTable_FindValue(&ar->members, copy);
}
@ -497,11 +499,11 @@ ArchStatMember(const char *archive, const char *member, Boolean addToCache)
if (!addToCache) {
/*
* Caller doesn't want the thing cached, just use ArchFindMember
* to read the header for the member out and close down the stream
* again. Since the archive is not to be cached, we assume there's
* no need to allocate extra room for the header we're returning,
* so just declare it static.
* Caller doesn't want the thing cached, just use
* ArchFindMember to read the header for the member out and
* close down the stream again. Since the archive is not to be
* cached, we assume there's no need to allocate extra room
* for the header we're returning, so just declare it static.
*/
static struct ar_hdr sarh;
@ -548,8 +550,9 @@ ArchStatMember(const char *archive, const char *member, Boolean addToCache)
/*
* We need to advance the stream's pointer to the start of the
* next header. Files are padded with newlines to an even-byte
* boundary, so we need to extract the size of the file from the
* 'size' field of the header and round it up during the seek.
* boundary, so we need to extract the size of the file from
* the 'size' field of the header and round it up during the
* seek.
*/
arh.AR_SIZE[sizeof arh.AR_SIZE - 1] = '\0';
size = (size_t)strtol(arh.AR_SIZE, NULL, 10);
@ -562,12 +565,11 @@ ArchStatMember(const char *archive, const char *member, Boolean addToCache)
#ifdef SVR4ARCHIVES
/*
* svr4 names are slash terminated. Also svr4 extended AR format.
* svr4 names are slash-terminated.
* Also svr4 extended the AR format.
*/
if (memName[0] == '/') {
/*
* svr4 magic mode; handle it
*/
/* svr4 magic mode; handle it */
switch (ArchSVR4Entry(ar, memName, size, arch)) {
case -1: /* Invalid data */
goto badarch;
@ -600,13 +602,16 @@ ArchStatMember(const char *archive, const char *member, Boolean addToCache)
if (fseek(arch, -elen, SEEK_CUR) != 0)
goto badarch;
if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("ArchStatMember: Extended format entry for %s\n",
debug_printf(
"ArchStatMember: "
"Extended format entry for %s\n",
memName);
}
#endif
{
struct ar_hdr *cached_hdr = bmake_malloc(sizeof *cached_hdr);
struct ar_hdr *cached_hdr = bmake_malloc(
sizeof *cached_hdr);
memcpy(cached_hdr, &arh, sizeof arh);
HashTable_Set(&ar->members, memName, cached_hdr);
}
@ -617,7 +622,7 @@ ArchStatMember(const char *archive, const char *member, Boolean addToCache)
fclose(arch);
Lst_Append(archives, ar);
Lst_Append(&archives, ar);
/*
* Now that the archive has been read and cached, we can look into
@ -634,22 +639,18 @@ ArchStatMember(const char *archive, const char *member, Boolean addToCache)
}
#ifdef SVR4ARCHIVES
/*-
*-----------------------------------------------------------------------
* ArchSVR4Entry --
/*
* Parse an SVR4 style entry that begins with a slash.
* If it is "//", then load the table of filenames
* If it is "//", then load the table of filenames.
* If it is "/<offset>", then try to substitute the long file name
* from offset of a table previously read.
* If a table is read, the file pointer is moved to the next archive
* member.
* If a table is read, the file pointer is moved to the next archive member.
*
* Results:
* -1: Bad data in archive
* 0: A table was loaded from the file
* 1: Name was successfully substituted from table
* 2: Name was not successfully substituted from table
*-----------------------------------------------------------------------
*/
static int
ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
@ -663,7 +664,8 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) {
if (ar->fnametab != NULL) {
DEBUG0(ARCH, "Attempted to redefine an SVR4 name table\n");
DEBUG0(ARCH,
"Attempted to redefine an SVR4 name table\n");
return -1;
}
@ -737,7 +739,8 @@ ArchiveMember_HasName(const struct ar_hdr *hdr,
return FALSE;
}
/* Locate a member of an archive, given the path of the archive and the path
/*
* Locate a member of an archive, given the path of the archive and the path
* of the desired member.
*
* Input:
@ -762,8 +765,7 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
FILE *arch; /* Stream to archive */
int size; /* Size of archive member */
char magic[SARMAG];
size_t len, tlen;
const char *lastSlash;
size_t len;
arch = fopen(archive, mode);
if (arch == NULL)
@ -783,18 +785,14 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
* Because of space constraints and similar things, files are archived
* using their basename, not the entire path.
*/
lastSlash = strrchr(member, '/');
if (lastSlash != NULL)
member = lastSlash + 1;
member = str_basename(member);
len = tlen = strlen(member);
if (len > sizeof out_arh->AR_NAME) {
tlen = sizeof out_arh->AR_NAME;
}
len = strlen(member);
while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) {
if (strncmp(out_arh->AR_FMAG, ARFMAG, sizeof out_arh->AR_FMAG) != 0) {
if (strncmp(out_arh->AR_FMAG, ARFMAG,
sizeof out_arh->AR_FMAG) != 0) {
/*
* The header is bogus, so the archive is bad
* and there's no way we can recover...
@ -810,14 +808,15 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
if (ArchiveMember_HasName(out_arh, member, len)) {
/*
* To make life easier for callers that want to update the
* archive, we reposition the file at the start
* of the header we just read before we return the stream.
* In a more general situation, it might be better to leave
* the file at the actual member, rather than its header, but
* not here.
* To make life easier for callers that want to update
* the archive, we reposition the file at the start of
* the header we just read before we return the
* stream. In a more general situation, it might be
* better to leave the file at the actual member,
* rather than its header, but not here.
*/
if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) != 0) {
if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) !=
0) {
fclose(arch);
return NULL;
}
@ -829,9 +828,9 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
* BSD 4.4 extended AR format: #1/<namelen>, with name as the
* first <namelen> bytes of the file
*/
if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
ch_isdigit(out_arh->AR_NAME[sizeof AR_EFMT1 - 1]))
{
if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) ==
0 &&
(ch_isdigit(out_arh->AR_NAME[sizeof AR_EFMT1 - 1]))) {
int elen = atoi(&out_arh->AR_NAME[sizeof AR_EFMT1 - 1]);
char ename[MAXPATHLEN + 1];
@ -845,11 +844,14 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
}
ename[elen] = '\0';
if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("ArchFindMember: Extended format entry for %s\n",
debug_printf(
"ArchFindMember: "
"Extended format entry for %s\n",
ename);
if (strncmp(ename, member, len) == 0) {
/* Found as extended name */
if (fseek(arch, -(long)sizeof(struct ar_hdr) - elen,
if (fseek(arch,
-(long)sizeof(struct ar_hdr) - elen,
SEEK_CUR) != 0) {
fclose(arch);
return NULL;
@ -882,7 +884,8 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
return NULL;
}
/* Touch a member of an archive, on disk.
/*
* Touch a member of an archive, on disk.
* The GNode's modification time is left as-is.
*
* The st_mtime of the entire archive is also changed.
@ -900,7 +903,8 @@ Arch_Touch(GNode *gn)
FILE *f;
struct ar_hdr arh;
f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+");
f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh,
"r+");
if (f == NULL)
return;
@ -909,11 +913,14 @@ Arch_Touch(GNode *gn)
fclose(f); /* TODO: handle errors */
}
/* Given a node which represents a library, touch the thing, making sure that
/*
* Given a node which represents a library, touch the thing, making sure that
* the table of contents is also touched.
*
* Both the modification time of the library and of the RANLIBMAG member are
* set to 'now'. */
* set to 'now'.
*/
/*ARGSUSED*/
void
Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED)
{
@ -935,8 +942,10 @@ Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED)
#endif
}
/* Update the mtime of the GNode with the mtime from the archive member on
* disk (or in the cache). */
/*
* Update the mtime of the GNode with the mtime from the archive member on
* disk (or in the cache).
*/
void
Arch_UpdateMTime(GNode *gn)
{
@ -949,23 +958,26 @@ Arch_UpdateMTime(GNode *gn)
gn->mtime = 0;
}
/* Given a non-existent archive member's node, update gn->mtime from its
* archived form, if it exists. */
/*
* Given a nonexistent archive member's node, update gn->mtime from its
* archived form, if it exists.
*/
void
Arch_UpdateMemberMTime(GNode *gn)
{
GNodeListNode *ln;
for (ln = gn->parents->first; ln != NULL; ln = ln->next) {
for (ln = gn->parents.first; ln != NULL; ln = ln->next) {
GNode *pgn = ln->datum;
if (pgn->type & OP_ARCHV) {
/*
* If the parent is an archive specification and is being made
* and its member's name matches the name of the node we were
* given, record the modification time of the parent in the
* child. We keep searching its parents in case some other
* parent requires this child to exist...
* If the parent is an archive specification and is
* being made and its member's name matches the name
* of the node we were given, record the modification
* time of the parent in the child. We keep searching
* its parents in case some other parent requires this
* child to exist.
*/
const char *nameStart = strchr(pgn->name, '(') + 1;
const char *nameEnd = strchr(nameStart, ')');
@ -978,8 +990,8 @@ Arch_UpdateMemberMTime(GNode *gn)
}
} else if (pgn->flags & REMAKE) {
/*
* Something which isn't a library depends on the existence of
* this target, so it needs to exist.
* Something which isn't a library depends on the
* existence of this target, so it needs to exist.
*/
gn->mtime = 0;
break;
@ -987,7 +999,8 @@ Arch_UpdateMemberMTime(GNode *gn)
}
}
/* Search for a library along the given search path.
/*
* Search for a library along the given search path.
*
* The node's 'path' field is set to the found path (including the
* actual file name, not -l...). If the system can handle the -L
@ -1015,7 +1028,8 @@ Arch_FindLib(GNode *gn, SearchPath *path)
#endif
}
/* Decide if a node with the OP_LIB attribute is out-of-date. Called from
/*
* Decide if a node with the OP_LIB attribute is out-of-date. Called from
* GNode_IsOODate to make its life easier.
* The library is cached if it hasn't been already.
*
@ -1051,9 +1065,9 @@ Arch_LibOODate(GNode *gn)
if (gn->type & OP_PHONY) {
oodate = TRUE;
} else if (!GNode_IsTarget(gn) && Lst_IsEmpty(gn->children)) {
} else if (!GNode_IsTarget(gn) && Lst_IsEmpty(&gn->children)) {
oodate = FALSE;
} else if ((!Lst_IsEmpty(gn->children) && gn->youngestChild == NULL) ||
} else if ((!Lst_IsEmpty(&gn->children) && gn->youngestChild == NULL) ||
(gn->mtime > now) ||
(gn->youngestChild != NULL &&
gn->mtime < gn->youngestChild->mtime)) {
@ -1061,7 +1075,7 @@ Arch_LibOODate(GNode *gn)
} else {
#ifdef RANLIBMAG
struct ar_hdr *arh; /* Header for __.SYMDEF */
int modTimeTOC; /* The table-of-contents's mod time */
int modTimeTOC; /* The table-of-contents' mod time */
arh = ArchStatMember(gn->path, RANLIBMAG, FALSE);
@ -1070,11 +1084,14 @@ Arch_LibOODate(GNode *gn)
if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("%s modified %s...",
RANLIBMAG, Targ_FmtTime(modTimeTOC));
RANLIBMAG,
Targ_FmtTime(modTimeTOC));
oodate = gn->youngestChild == NULL ||
gn->youngestChild->mtime > modTimeTOC;
} else {
/* A library without a table of contents is out-of-date. */
/*
* A library without a table of contents is out-of-date.
*/
if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("no toc...");
oodate = TRUE;
@ -1090,7 +1107,7 @@ Arch_LibOODate(GNode *gn)
void
Arch_Init(void)
{
archives = Lst_New();
Lst_Init(&archives);
}
/* Clean up the archives module. */
@ -1098,7 +1115,7 @@ void
Arch_End(void)
{
#ifdef CLEANUP
Lst_Destroy(archives, ArchFree);
Lst_DoneCall(&archives, ArchFree);
#endif
}

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

31
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,17 +69,17 @@
* 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);
@ -93,7 +93,8 @@ Buf_AddBytes(Buffer *buf, const char *bytes, size_t bytes_len)
char *end;
if (__predict_false(old_len + bytes_len >= buf->cap)) {
buf->cap += buf->cap > bytes_len + 16 ? buf->cap : bytes_len + 16;
size_t minIncr = bytes_len + 16;
buf->cap += buf->cap > minIncr ? buf->cap : minIncr;
buf->data = bmake_realloc(buf->data, buf->cap);
}
@ -134,11 +135,13 @@ Buf_AddInt(Buffer *buf, int 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)
{
@ -171,9 +174,11 @@ Buf_Init(Buffer *buf)
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)
{
@ -194,10 +199,12 @@ Buf_Destroy(Buffer *buf, Boolean freeData)
# 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)
{

6
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.
@ -91,7 +91,7 @@ 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
@ -100,7 +100,7 @@ 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);
Buf_Expand(buf);
end = buf->data + old_len;
end[0] = byte;
end[1] = '\0';

356
compat.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $ */
/* $NetBSD: compat.c,v 1.219 2021/01/10 21:20:46 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -99,7 +99,7 @@
#include "pathnames.h"
/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $");
MAKE_RCSID("$NetBSD: compat.c,v 1.219 2021/01/10 21:20:46 rillig Exp $");
static GNode *curTarg = NULL;
static pid_t compatChild;
@ -121,7 +121,8 @@ CompatDeleteTarget(GNode *gn)
}
}
/* Interrupt the creation of the current target and remove it if it ain't
/*
* Interrupt the creation of the current target and remove it if it ain't
* precious. Then exit.
*
* If .INTERRUPT exists, its commands are run first WITH INTERRUPTS IGNORED.
@ -162,18 +163,67 @@ CompatInterrupt(int signo)
}
}
/* Execute the next command for a target. If the command returns an error,
static void
DebugFailedTarget(const char *cmd, GNode *gn)
{
const char *p = cmd;
debug_printf("\n*** Failed target: %s\n*** Failed command: ",
gn->name);
/* Replace runs of whitespace with a single space, to reduce
* the amount of whitespace for multi-line command lines. */
while (*p != '\0') {
if (ch_isspace(*p)) {
debug_printf(" ");
cpp_skip_whitespace(&p);
} else {
debug_printf("%c", *p);
p++;
}
}
debug_printf("\n");
}
static Boolean
UseShell(const char *cmd MAKE_ATTR_UNUSED)
{
#if !defined(MAKE_NATIVE)
/*
* In a non-native build, the host environment might be weird enough
* that it's necessary to go through a shell to get the correct
* behaviour. Or perhaps the shell has been replaced with something
* that does extra logging, and that should not be bypassed.
*/
return TRUE;
#else
/*
* Search for meta characters in the command. If there are no meta
* characters, there's no need to execute a shell to execute the
* command.
*
* Additionally variable assignments and empty commands
* go to the shell. Therefore treat '=' and ':' like shell
* meta characters as documented in make(1).
*/
return needshell(cmd);
#endif
}
/*
* Execute the next command for a target. If the command returns an error,
* the node's made field is set to ERROR and creation stops.
*
* Input:
* cmdp Command to execute
* gnp Node from which the command came
* gn Node from which the command came
* ln List node that contains the command
*
* Results:
* 0 if the command succeeded, 1 if an error occurred.
*/
int
Compat_RunCommand(const char *cmdp, GNode *gn)
Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
{
char *cmdStart; /* Start of expanded command */
char *bp;
@ -181,10 +231,9 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
Boolean doIt; /* Execute even if -n */
volatile Boolean errCheck; /* Check errors */
WAIT_T reason; /* Reason for child's death */
int status; /* Description of child's death */
WAIT_T status; /* Description of child's death */
pid_t cpid; /* Child actually found */
pid_t retstat; /* Result of wait */
StringListNode *cmdNode; /* Node where current command is located */
const char **volatile av; /* Argument vector for thing to exec */
char **volatile mav; /* Copy of the argument vector for freeing */
Boolean useShell; /* TRUE if command should be executed
@ -195,12 +244,6 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
errCheck = !(gn->type & OP_IGNORE);
doIt = FALSE;
/* Luckily the commands don't end up in a string pool, otherwise
* this comparison could match too early, in a dependency using "..."
* for delayed commands, run in parallel mode, using the same shell
* command line more than once; see JobPrintCommand.
* TODO: write a unit-test to protect against this potential bug. */
cmdNode = Lst_FindDatum(gn->commands, cmd);
(void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart);
/* TODO: handle errors */
@ -209,12 +252,23 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
return 0;
}
cmd = cmdStart;
LstNode_Set(cmdNode, cmdStart);
LstNode_Set(ln, cmdStart);
if (gn->type & OP_SAVE_CMDS) {
GNode *endNode = Targ_GetEndNode();
if (gn != endNode) {
Lst_Append(endNode->commands, cmdStart);
/*
* Append the expanded command, to prevent the
* local variables from being interpreted in the
* context of the .END node.
*
* A probably unintended side effect of this is that
* the expanded command will be expanded again in the
* .END node. Therefore, a literal '$' in these
* commands must be written as '$$$$' instead of the
* usual '$$'.
*/
Lst_Append(&endNode->commands, cmdStart);
return 0;
}
}
@ -230,7 +284,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
errCheck = FALSE;
else if (*cmd == '+') {
doIt = TRUE;
if (!shellName) /* we came here from jobs */
if (shellName == NULL) /* we came here from jobs */
Shell_Init();
} else
break;
@ -246,31 +300,10 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
if (cmd[0] == '\0')
return 0;
#if !defined(MAKE_NATIVE)
useShell = UseShell(cmd);
/*
* In a non-native build, the host environment might be weird enough
* that it's necessary to go through a shell to get the correct
* behaviour. Or perhaps the shell has been replaced with something
* that does extra logging, and that should not be bypassed.
*/
useShell = TRUE;
#else
/*
* Search for meta characters in the command. If there are no meta
* characters, there's no need to execute a shell to execute the
* command.
*
* Additionally variable assignments and empty commands
* go to the shell. Therefore treat '=' and ':' like shell
* meta characters as documented in make(1).
*/
useShell = needshell(cmd);
#endif
/*
* Print the command before echoing if we're not supposed to be quiet for
* this one. We also print the command if -n given.
* Print the command before echoing if we're not supposed to be quiet
* for this one. We also print the command if -n given.
*/
if (!silent || !GNode_ShouldExecute(gn)) {
printf("%s\n", cmd);
@ -296,7 +329,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
/* The following work for any of the builtin shell specs. */
int shargc = 0;
shargv[shargc++] = shellPath;
if (errCheck && shellErrFlag)
if (errCheck && shellErrFlag != NULL)
shargv[shargc++] = shellErrFlag;
shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c";
shargv[shargc++] = cmd;
@ -306,8 +339,9 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
mav = NULL;
} else {
/*
* No meta-characters, so no need to exec a shell. Break the command
* into words to form an argument vector we can execute.
* No meta-characters, so no need to exec a shell. Break the
* command into words to form an argument vector we can
* execute.
*/
Words words = Str_Words(cmd, FALSE);
mav = words.words;
@ -321,6 +355,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
}
#endif
Var_ReexportVars();
/*
* Fork and execute the single command. If the fork fails, we abort.
*/
@ -329,7 +365,6 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
Fatal("Could not fork");
}
if (cpid == 0) {
Var_ExportVars();
#ifdef USE_META
if (useMeta) {
meta_compat_child();
@ -344,7 +379,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
/* XXX: Memory management looks suspicious here. */
/* XXX: Setting a list item to NULL is unexpected. */
LstNode_SetNull(cmdNode);
LstNode_SetNull(ln);
#ifdef USE_META
if (useMeta) {
@ -376,24 +411,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
}
#endif
if (status != 0) {
if (DEBUG(ERROR)) {
const char *p = cmd;
debug_printf("\n*** Failed target: %s\n*** Failed command: ",
gn->name);
/* Replace runs of whitespace with a single space, to reduce
* the amount of whitespace for multi-line command lines. */
while (*p != '\0') {
if (ch_isspace(*p)) {
debug_printf(" ");
cpp_skip_whitespace(&p);
} else {
debug_printf("%c", *p);
p++;
}
}
debug_printf("\n");
}
if (DEBUG(ERROR))
DebugFailedTarget(cmd, gn);
printf("*** Error code %d", status);
}
} else {
@ -406,12 +425,15 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
if (errCheck) {
#ifdef USE_META
if (useMeta) {
meta_job_error(NULL, gn, 0, status);
meta_job_error(NULL, gn, FALSE, status);
}
#endif
gn->made = ERROR;
if (opts.keepgoing) {
/* Abort the current target, but let others continue. */
/*
* Abort the current target,
* but let others continue.
*/
printf(" (continuing)\n");
} else {
printf("\n");
@ -430,7 +452,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
free(cmdStart);
compatChild = 0;
if (compatSigno) {
if (compatSigno != 0) {
bmake_signal(compatSigno, SIG_DFL);
kill(myPid, compatSigno);
}
@ -442,9 +464,10 @@ static void
RunCommands(GNode *gn)
{
StringListNode *ln;
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 (Compat_RunCommand(cmd, gn) != 0)
if (Compat_RunCommand(cmd, gn, ln) != 0)
break;
}
}
@ -453,27 +476,19 @@ static void
MakeNodes(GNodeList *gnodes, GNode *pgn)
{
GNodeListNode *ln;
for (ln = gnodes->first; ln != NULL; ln = ln->next) {
GNode *cohort = ln->datum;
Compat_Make(cohort, pgn);
}
}
/* Make a target.
*
* If an error is detected and not being ignored, the process exits.
*
* Input:
* gn The node to make
* pgn Parent to abort if necessary
*/
void
Compat_Make(GNode *gn, GNode *pgn)
static Boolean
MakeUnmade(GNode *gn, GNode *pgn)
{
if (shellName == NULL) /* we came here from jobs */
Shell_Init();
if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
assert(gn->made == UNMADE);
/*
* First mark ourselves to be made, then apply whatever transformations
* the suffix module thinks are necessary. Once that's done, we can
@ -484,16 +499,19 @@ Compat_Make(GNode *gn, GNode *pgn)
*/
gn->flags |= REMAKE;
gn->made = BEINGMADE;
if (!(gn->type & OP_MADE))
Suff_FindDeps(gn);
MakeNodes(gn->children, gn);
MakeNodes(&gn->children, gn);
if (!(gn->flags & REMAKE)) {
gn->made = ABORTED;
pgn->flags &= ~(unsigned)REMAKE;
goto cohorts;
return FALSE;
}
if (Lst_FindDatum(gn->implicitParents, pgn) != NULL)
if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL)
Var_Set(IMPSRC, GNode_VarTarget(gn), pgn);
/*
@ -506,37 +524,36 @@ Compat_Make(GNode *gn, GNode *pgn)
if (!GNode_IsOODate(gn)) {
gn->made = UPTODATE;
DEBUG0(MAKE, "up-to-date.\n");
goto cohorts;
} else
DEBUG0(MAKE, "out-of-date.\n");
return FALSE;
}
/*
* If the user is just seeing if something is out-of-date, exit now
* to tell him/her "yes".
*/
DEBUG0(MAKE, "out-of-date.\n");
if (opts.queryFlag)
exit(1);
/*
* We need to be re-made. We also have to make sure we've got a $?
* variable. To be nice, we also define the $> variable using
* Make_DoAllVar().
* We need to be re-made.
* Ensure that $? (.OODATE) and $> (.ALLSRC) are both set.
*/
Make_DoAllVar(gn);
/*
* Alter our type to tell if errors should be ignored or things
* should not be printed so CompatRunCommand knows what to do.
* should not be printed so Compat_RunCommand knows what to do.
*/
if (Targ_Ignore(gn))
if (opts.ignoreErrors)
gn->type |= OP_IGNORE;
if (Targ_Silent(gn))
if (opts.beSilent)
gn->type |= OP_SILENT;
if (Job_CheckCommands(gn, Fatal)) {
/*
* Our commands are ok, but we still have to worry about the -t
* flag...
* Our commands are ok, but we still have to worry about
* the -t flag.
*/
if (!opts.touchFlag || (gn->type & OP_MAKE)) {
curTarg = gn;
@ -579,14 +596,18 @@ Compat_Make(GNode *gn, GNode *pgn)
PrintOnError(gn, "\nStop.");
exit(1);
}
} else if (gn->made == ERROR) {
/* Already had an error when making this. Tell the parent to abort. */
pgn->flags &= ~(unsigned)REMAKE;
} else {
if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) {
return TRUE;
}
static void
MakeOther(GNode *gn, GNode *pgn)
{
if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) {
const char *target = GNode_VarTarget(gn);
Var_Set(IMPSRC, target != NULL ? target : "", pgn);
}
switch (gn->made) {
case BEINGMADE:
Error("Graph cycles through %s", gn->name);
@ -608,24 +629,66 @@ Compat_Make(GNode *gn, GNode *pgn)
}
}
cohorts:
MakeNodes(gn->cohorts, pgn);
}
/* Initialize this module and start making.
/*
* Make a target.
*
* If an error is detected and not being ignored, the process exits.
*
* Input:
* targs The target nodes to re-create
* gn The node to make
* pgn Parent to abort if necessary
*
* Output:
* gn->made
* UPTODATE gn was already up-to-date.
* MADE gn was recreated successfully.
* ERROR An error occurred while gn was being created,
* either due to missing commands or in -k mode.
* ABORTED gn was not remade because one of its
* dependencies could not be made due to errors.
*/
void
Compat_Run(GNodeList *targs)
Compat_Make(GNode *gn, GNode *pgn)
{
GNode *gn = NULL; /* Current root target */
int errors; /* Number of targets not remade due to errors */
if (!shellName)
if (shellName == NULL) /* we came here from jobs */
Shell_Init();
if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
if (!MakeUnmade(gn, pgn))
goto cohorts;
/* XXX: Replace with GNode_IsError(gn) */
} else if (gn->made == ERROR) {
/*
* Already had an error when making this.
* Tell the parent to abort.
*/
pgn->flags &= ~(unsigned)REMAKE;
} else {
MakeOther(gn, pgn);
}
cohorts:
MakeNodes(&gn->cohorts, pgn);
}
static void
MakeBeginNode(void)
{
GNode *gn = Targ_FindNode(".BEGIN");
if (gn == NULL)
return;
Compat_Make(gn, gn);
if (GNode_IsError(gn)) {
PrintOnError(gn, "\nStop.");
exit(1);
}
}
static void
InitSignals(void)
{
if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN)
bmake_signal(SIGINT, CompatInterrupt);
if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN)
@ -634,25 +697,31 @@ Compat_Run(GNodeList *targs)
bmake_signal(SIGHUP, CompatInterrupt);
if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN)
bmake_signal(SIGQUIT, CompatInterrupt);
}
/*
* Initialize this module and start making.
*
* Input:
* targs The target nodes to re-create
*/
void
Compat_Run(GNodeList *targs)
{
GNode *errorNode = NULL;
if (shellName == NULL)
Shell_Init();
InitSignals();
/* Create the .END node now, to keep the (debug) output of the
* counter.mk test the same as before 2020-09-23. This implementation
* detail probably doesn't matter though. */
(void)Targ_GetEndNode();
/*
* If the user has defined a .BEGIN target, execute the commands attached
* to it.
*/
if (!opts.queryFlag) {
gn = Targ_FindNode(".BEGIN");
if (gn != NULL) {
Compat_Make(gn, gn);
if (gn->made == ERROR) {
PrintOnError(gn, "\nStop.");
exit(1);
}
}
}
if (!opts.queryFlag)
MakeBeginNode();
/*
* Expand .USE nodes right now, because they can modify the structure
@ -660,39 +729,30 @@ Compat_Run(GNodeList *targs)
*/
Make_ExpandUse(targs);
/*
* For each entry in the list of targets to create, call Compat_Make on
* it to create the thing. Compat_Make will leave the 'made' field of gn
* in one of several states:
* UPTODATE gn was already up-to-date
* MADE gn was recreated successfully
* ERROR An error occurred while gn was being created
* ABORTED gn was not remade because one of its inferiors
* could not be made due to errors.
*/
errors = 0;
while (!Lst_IsEmpty(targs)) {
gn = Lst_Dequeue(targs);
GNode *gn = Lst_Dequeue(targs);
Compat_Make(gn, gn);
if (gn->made == UPTODATE) {
printf("`%s' is up to date.\n", gn->name);
} else if (gn->made == ABORTED) {
printf("`%s' not remade because of errors.\n", gn->name);
errors++;
printf("`%s' not remade because of errors.\n",
gn->name);
}
if (GNode_IsError(gn) && errorNode == NULL)
errorNode = gn;
}
/*
* If the user has defined a .END target, run its commands.
*/
if (errors == 0) {
/* If the user has defined a .END target, run its commands. */
if (errorNode == NULL) {
GNode *endNode = Targ_GetEndNode();
Compat_Make(endNode, endNode);
/* XXX: Did you mean endNode->made instead of gn->made? */
if (gn->made == ERROR) {
PrintOnError(gn, "\nStop.");
if (GNode_IsError(endNode))
errorNode = endNode;
}
if (errorNode != NULL) {
PrintOnError(errorNode, "\nStop.");
exit(1);
}
}
}

369
cond.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $ */
/* $NetBSD: cond.c,v 1.235 2021/01/10 21:20:46 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -69,7 +69,8 @@
* SUCH DAMAGE.
*/
/* Handling of conditionals in a makefile.
/*
* Handling of conditionals in a makefile.
*
* Interface:
* Cond_EvalLine Evaluate the conditional directive, such as
@ -94,7 +95,7 @@
#include "dir.h"
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $");
MAKE_RCSID("$NetBSD: cond.c,v 1.235 2021/01/10 21:20:46 rillig Exp $");
/*
* The parsing of conditional expressions is based on this grammar:
@ -144,10 +145,12 @@ typedef struct CondParser {
const char *p; /* The remaining condition to parse */
Token curr; /* Single push-back token used in parsing */
/* Whether an error message has already been printed for this condition.
* The first available error message is usually the most specific one,
* therefore it makes sense to suppress the standard "Malformed
* conditional" message. */
/*
* Whether an error message has already been printed for this
* condition. The first available error message is usually the most
* specific one, therefore it makes sense to suppress the standard
* "Malformed conditional" message.
*/
Boolean printedError;
} CondParser;
@ -168,7 +171,7 @@ static unsigned int cond_min_depth = 0; /* depth at makefile open */
*/
static Boolean lhsStrict;
static int
static Boolean
is_token(const char *str, const char *tok, size_t len)
{
return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]);
@ -196,7 +199,8 @@ CondParser_SkipWhitespace(CondParser *par)
cpp_skip_whitespace(&par->p);
}
/* Parse the argument of a built-in function.
/*
* Parse the argument of a built-in function.
*
* Arguments:
* *pp initially points at the '(',
@ -207,10 +211,12 @@ CondParser_SkipWhitespace(CondParser *par)
* func says whether the argument belongs to an actual function, or
* whether the parsed argument is passed to the default function.
*
* Return the length of the argument, or 0 on error. */
* Return the length of the argument, or 0 on error.
*/
static size_t
ParseFuncArg(const char **pp, Boolean doEval, const char *func,
char **out_arg) {
char **out_arg)
{
const char *p = *pp;
Buffer argBuf;
int paren_depth;
@ -237,20 +243,21 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
break;
if (*p == '$') {
/*
* Parse the variable spec and install it as part of the argument
* if it's valid. We tell Var_Parse to complain on an undefined
* variable, so we don't need to do it. Nor do we return an error,
* though perhaps we should...
* Parse the variable expression and install it as
* part of the argument if it's valid. We tell
* Var_Parse to complain on an undefined variable,
* (XXX: but Var_Parse ignores that request)
* so we don't need to do it. Nor do we return an
* error, though perhaps we should.
*/
void *nestedVal_freeIt;
VarEvalFlags eflags = doEval ? VARE_WANTRES | VARE_UNDEFERR
VarEvalFlags eflags = doEval
? VARE_WANTRES | VARE_UNDEFERR
: VARE_NONE;
const char *nestedVal;
(void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal,
&nestedVal_freeIt);
FStr nestedVal;
(void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal);
/* TODO: handle errors */
Buf_AddStr(&argBuf, nestedVal);
free(nestedVal_freeIt);
Buf_AddStr(&argBuf, nestedVal.str);
FStr_Done(&nestedVal);
continue;
}
if (ch == '(')
@ -267,9 +274,10 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
cpp_skip_hspace(&p);
if (func != NULL && *p++ != ')') {
Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
Parse_Error(PARSE_WARNING,
"Missing closing parenthesis for %s()",
func);
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
/* The PARSE_FATAL follows in CondEvalExpression. */
return 0;
}
@ -278,35 +286,38 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
}
/* Test whether the given variable is defined. */
/*ARGSUSED*/
static Boolean
FuncDefined(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
void *freeIt;
Boolean result = Var_Value(arg, VAR_CMDLINE, &freeIt) != NULL;
bmake_free(freeIt);
FStr value = Var_Value(arg, VAR_CMDLINE);
Boolean result = value.str != NULL;
FStr_Done(&value);
return result;
}
/* See if the given target is being made. */
/*ARGSUSED*/
static Boolean
FuncMake(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
StringListNode *ln;
for (ln = opts.create->first; ln != NULL; ln = ln->next)
for (ln = opts.create.first; ln != NULL; ln = ln->next)
if (Str_Match(ln->datum, arg))
return TRUE;
return FALSE;
}
/* See if the given file exists. */
/*ARGSUSED*/
static Boolean
FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
Boolean result;
char *path;
path = Dir_FindFile(arg, dirSearchPath);
path = Dir_FindFile(arg, &dirSearchPath);
DEBUG2(COND, "exists(%s) result is \"%s\"\n",
arg, path != NULL ? path : "");
result = path != NULL;
@ -315,6 +326,7 @@ FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
}
/* See if the given node exists and is an actual target. */
/*ARGSUSED*/
static Boolean
FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
@ -322,13 +334,16 @@ FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
return gn != NULL && GNode_IsTarget(gn);
}
/* See if the given node exists and is an actual target with commands
* associated with it. */
/*
* See if the given node exists and is an actual target with commands
* associated with it.
*/
/*ARGSUSED*/
static Boolean
FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
{
GNode *gn = Targ_FindNode(arg);
return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands);
return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands);
}
/*
@ -385,12 +400,12 @@ is_separator(char ch)
* Sets out_freeIt.
*/
/* coverity:[+alloc : arg-*4] */
static const char *
static void
CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
Boolean *out_quoted, void **out_freeIt)
FStr *out_str, Boolean *out_quoted)
{
Buffer buf;
const char *str;
FStr str;
Boolean atStart;
const char *nested_p;
Boolean quoted;
@ -399,13 +414,13 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
VarParseResult parseResult;
Buf_Init(&buf);
str = NULL;
*out_freeIt = NULL;
str = FStr_InitRefer(NULL);
*out_quoted = quoted = par->p[0] == '"';
start = par->p;
if (quoted)
par->p++;
while (par->p[0] != '\0' && str == NULL) {
while (par->p[0] != '\0' && str.str == NULL) {
switch (par->p[0]) {
case '\\':
par->p++;
@ -436,53 +451,55 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
continue;
case '$':
/* if we are in quotes, an undefined variable is ok */
eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR :
eflags =
doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR :
doEval ? VARE_WANTRES :
VARE_NONE;
nested_p = par->p;
atStart = nested_p == start;
parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str,
out_freeIt);
parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags,
&str);
/* TODO: handle errors */
if (str == var_Error) {
if (parseResult & VPR_ANY_MSG)
if (str.str == var_Error) {
if (parseResult == VPR_ERR)
par->printedError = TRUE;
if (*out_freeIt != NULL) {
/* XXX: Can there be any situation in which a returned
* var_Error requires freeIt? */
free(*out_freeIt);
*out_freeIt = NULL;
}
/*
* Even if !doEval, we still report syntax errors, which
* is what getting var_Error back with !doEval means.
* XXX: Can there be any situation in which
* a returned var_Error requires freeIt?
*/
str = NULL;
FStr_Done(&str);
/*
* Even if !doEval, we still report syntax
* errors, which is what getting var_Error
* back with !doEval means.
*/
str = FStr_InitRefer(NULL);
goto cleanup;
}
par->p = nested_p;
/*
* If the '$' started the string literal (which means no quotes),
* and the variable expression is followed by a space, looks like
* a comparison operator or is the end of the expression, we are
* done.
* If the '$' started the string literal (which means
* no quotes), and the variable expression is followed
* by a space, looks like a comparison operator or is
* the end of the expression, we are done.
*/
if (atStart && is_separator(par->p[0]))
goto cleanup;
Buf_AddStr(&buf, str);
if (*out_freeIt) {
free(*out_freeIt);
*out_freeIt = NULL;
}
str = NULL; /* not finished yet */
Buf_AddStr(&buf, str.str);
FStr_Done(&str);
str = FStr_InitRefer(NULL); /* not finished yet */
continue;
default:
if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) {
/* lhs must be quoted, a variable reference or number */
str = NULL;
if (strictLHS && !quoted && *start != '$' &&
!ch_isdigit(*start)) {
/*
* The left-hand side must be quoted,
* a variable reference or a number.
*/
str = FStr_InitRefer(NULL);
goto cleanup;
}
Buf_AddByte(&buf, par->p[0]);
@ -491,18 +508,18 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
}
}
got_str:
*out_freeIt = Buf_GetAll(&buf, NULL);
str = *out_freeIt;
str = FStr_InitOwn(Buf_GetAll(&buf, NULL));
cleanup:
Buf_Destroy(&buf, FALSE);
return str;
*out_str = str;
}
struct If {
const char *form; /* Form of if */
size_t formlen; /* Length of form */
Boolean doNot; /* TRUE if default function should be negated */
Boolean (*defProc)(size_t, const char *); /* Default function to apply */
/* The default function to apply on unquoted bare words. */
Boolean (*defProc)(size_t, const char *);
};
/* The different forms of .if directives. */
@ -514,7 +531,9 @@ static const struct If ifs[] = {
{ "", 0, FALSE, FuncDefined },
{ NULL, 0, FALSE, NULL }
};
enum { PLAIN_IF_INDEX = 4 };
enum {
PLAIN_IF_INDEX = 4
};
static Boolean
If_Eval(const struct If *if_info, const char *arg, size_t arglen)
@ -523,8 +542,10 @@ If_Eval(const struct If *if_info, const char *arg, size_t arglen)
return if_info->doNot ? !res : res;
}
/* Evaluate a "comparison without operator", such as in ".if ${VAR}" or
* ".if 0". */
/*
* Evaluate a "comparison without operator", such as in ".if ${VAR}" or
* ".if 0".
*/
static Boolean
EvalNotEmpty(CondParser *par, const char *value, Boolean quoted)
{
@ -559,14 +580,14 @@ EvalCompareNum(double lhs, const char *op, double rhs)
case '!':
if (op[1] != '=') {
Parse_Error(PARSE_WARNING, "Unknown operator");
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
/* The PARSE_FATAL follows in CondEvalExpression. */
return TOK_ERROR;
}
return ToToken(lhs != rhs);
case '=':
if (op[1] != '=') {
Parse_Error(PARSE_WARNING, "Unknown operator");
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
/* The PARSE_FATAL follows in CondEvalExpression. */
return TOK_ERROR;
}
return ToToken(lhs == rhs);
@ -583,8 +604,9 @@ EvalCompareStr(const char *lhs, const char *op, const char *rhs)
{
if (!((op[0] == '!' || op[0] == '=') && op[1] == '=')) {
Parse_Error(PARSE_WARNING,
"String comparison operator must be either == or !=");
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
"String comparison operator "
"must be either == or !=");
/* The PARSE_FATAL follows in CondEvalExpression. */
return TOK_ERROR;
}
@ -606,7 +628,8 @@ EvalCompare(const char *lhs, Boolean lhsQuoted, const char *op,
return EvalCompareStr(lhs, op, rhs);
}
/* Parse a comparison condition such as:
/*
* Parse a comparison condition such as:
*
* 0
* ${VAR:Mpattern}
@ -617,16 +640,16 @@ static Token
CondParser_Comparison(CondParser *par, Boolean doEval)
{
Token t = TOK_ERROR;
const char *lhs, *op, *rhs;
void *lhs_freeIt, *rhs_freeIt;
FStr lhs, rhs;
const char *op;
Boolean lhsQuoted, rhsQuoted;
/*
* Parse the variable spec and skip over it, saving its
* value in lhs.
*/
lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhs_freeIt);
if (lhs == NULL)
CondParser_String(par, doEval, lhsStrict, &lhs, &lhsQuoted);
if (lhs.str == NULL)
goto done_lhs;
CondParser_SkipWhitespace(par);
@ -644,20 +667,21 @@ CondParser_Comparison(CondParser *par, Boolean doEval)
break;
default:
/* Unknown operator, compare against an empty string or 0. */
t = ToToken(doEval && EvalNotEmpty(par, lhs, lhsQuoted));
t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted));
goto done_lhs;
}
CondParser_SkipWhitespace(par);
if (par->p[0] == '\0') {
Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator");
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
Parse_Error(PARSE_WARNING,
"Missing right-hand-side of operator");
/* The PARSE_FATAL follows in CondEvalExpression. */
goto done_lhs;
}
rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhs_freeIt);
if (rhs == NULL)
CondParser_String(par, doEval, FALSE, &rhs, &rhsQuoted);
if (rhs.str == NULL)
goto done_rhs;
if (!doEval) {
@ -665,23 +689,25 @@ CondParser_Comparison(CondParser *par, Boolean doEval)
goto done_rhs;
}
t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted);
t = EvalCompare(lhs.str, lhsQuoted, op, rhs.str, rhsQuoted);
done_rhs:
free(rhs_freeIt);
FStr_Done(&rhs);
done_lhs:
free(lhs_freeIt);
FStr_Done(&lhs);
return t;
}
/* The argument to empty() is a variable name, optionally followed by
* variable modifiers. */
/*
* The argument to empty() is a variable name, optionally followed by
* variable modifiers.
*/
/*ARGSUSED*/
static size_t
ParseEmptyArg(const char **pp, Boolean doEval,
const char *func MAKE_ATTR_UNUSED, char **out_arg)
{
void *val_freeIt;
const char *val;
FStr val;
size_t magic_res;
/* We do all the work here and return the result as the length */
@ -689,27 +715,31 @@ ParseEmptyArg(const char **pp, Boolean doEval,
(*pp)--; /* Make (*pp)[1] point to the '('. */
(void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE,
&val, &val_freeIt);
&val);
/* TODO: handle errors */
/* If successful, *pp points beyond the closing ')' now. */
if (val == var_Error) {
free(val_freeIt);
if (val.str == var_Error) {
FStr_Done(&val);
return (size_t)-1;
}
/* A variable is empty when it just contains spaces... 4/15/92, christos */
cpp_skip_whitespace(&val);
/*
* A variable is empty when it just contains spaces...
* 4/15/92, christos
*/
cpp_skip_whitespace(&val.str);
/*
* For consistency with the other functions we can't generate the
* true/false here.
*/
magic_res = *val != '\0' ? 2 : 1;
free(val_freeIt);
magic_res = val.str[0] != '\0' ? 2 : 1;
FStr_Done(&val);
return magic_res;
}
/*ARGSUSED*/
static Boolean
FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED)
{
@ -723,7 +753,8 @@ CondParser_Func(CondParser *par, Boolean doEval, Token *out_token)
static const struct fn_def {
const char *fn_name;
size_t fn_name_len;
size_t (*fn_parse)(const char **, Boolean, const char *, char **);
size_t (*fn_parse)(const char **, Boolean, const char *,
char **);
Boolean (*fn_eval)(size_t, const char *);
} fns[] = {
{ "defined", 7, ParseFuncArg, FuncDefined },
@ -765,15 +796,17 @@ CondParser_Func(CondParser *par, Boolean doEval, Token *out_token)
return FALSE;
}
/* Parse a function call, a number, a variable expression or a string
* literal. */
/*
* Parse a function call, a number, a variable expression or a string
* literal.
*/
static Token
CondParser_LeafToken(CondParser *par, Boolean doEval)
{
Token t;
char *arg = NULL;
size_t arglen;
const char *cp = par->p;
const char *cp;
const char *cp1;
if (CondParser_Func(par, doEval, &t))
@ -788,9 +821,9 @@ CondParser_LeafToken(CondParser *par, Boolean doEval)
* Most likely we have a naked token to apply the default function to.
* However ".if a == b" gets here when the "a" is unquoted and doesn't
* start with a '$'. This surprises people.
* If what follows the function argument is a '=' or '!' then the syntax
* would be invalid if we did "defined(a)" - so instead treat as an
* expression.
* If what follows the function argument is a '=' or '!' then the
* syntax would be invalid if we did "defined(a)" - so instead treat
* as an expression.
*/
arglen = ParseFuncArg(&cp, doEval, NULL, &arg);
cp1 = cp;
@ -838,7 +871,7 @@ CondParser_Token(CondParser *par, Boolean doEval)
par->p++;
if (par->p[0] == '|')
par->p++;
else if (opts.lint) {
else if (opts.strict) {
Parse_Error(PARSE_FATAL, "Unknown operator '|'");
par->printedError = TRUE;
return TOK_ERROR;
@ -849,7 +882,7 @@ CondParser_Token(CondParser *par, Boolean doEval)
par->p++;
if (par->p[0] == '&')
par->p++;
else if (opts.lint) {
else if (opts.strict) {
Parse_Error(PARSE_FATAL, "Unknown operator '&'");
par->printedError = TRUE;
return TOK_ERROR;
@ -875,7 +908,8 @@ CondParser_Token(CondParser *par, Boolean doEval)
}
}
/* Parse a single term in the expression. This consists of a terminal symbol
/*
* Parse a single term in the expression. This consists of a terminal symbol
* or TOK_NOT and a term (not including the binary operators):
*
* T -> defined(variable) | make(target) | exists(file) | symbol
@ -918,7 +952,8 @@ CondParser_Term(CondParser *par, Boolean doEval)
return t;
}
/* Parse a conjunctive factor (nice name, wot?)
/*
* Parse a conjunctive factor (nice name, wot?)
*
* F -> T && F | T
*
@ -938,10 +973,10 @@ CondParser_Factor(CondParser *par, Boolean doEval)
/*
* F -> T && F
*
* If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we
* have to parse the r.h.s. anyway (to throw it away).
* If T is TOK_TRUE, the result is the r.h.s., be it a TOK_ERROR
* or not.
* If T is TOK_FALSE, the whole thing will be
* TOK_FALSE, but we have to parse the r.h.s. anyway
* (to throw it away). If T is TOK_TRUE, the result
* is the r.h.s., be it a TOK_ERROR or not.
*/
if (l == TOK_TRUE) {
l = CondParser_Factor(par, doEval);
@ -958,7 +993,8 @@ CondParser_Factor(CondParser *par, Boolean doEval)
return l;
}
/* Main expression production.
/*
* Main expression production.
*
* E -> F || E | F
*
@ -978,10 +1014,12 @@ CondParser_Expr(CondParser *par, Boolean doEval)
/*
* E -> F || E
*
* A similar thing occurs for ||, except that here we make sure
* the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s.
* Once again, if l is TOK_FALSE, the result is the r.h.s. and once
* again if l is TOK_TRUE, we parse the r.h.s. to throw it away.
* A similar thing occurs for ||, except that here
* we make sure the l.h.s. is TOK_FALSE before we
* bother to evaluate the r.h.s. Once again, if l
* is TOK_FALSE, the result is the r.h.s. and once
* again if l is TOK_TRUE, we parse the r.h.s. to
* throw it away.
*/
if (l == TOK_FALSE) {
l = CondParser_Expr(par, doEval);
@ -1009,14 +1047,15 @@ CondParser_Eval(CondParser *par, Boolean *value)
if (res != TOK_FALSE && res != TOK_TRUE)
return COND_INVALID;
if (CondParser_Token(par, TRUE /* XXX: Why TRUE? */) != TOK_EOF)
if (CondParser_Token(par, FALSE) != TOK_EOF)
return COND_INVALID;
*value = res == TOK_TRUE;
return COND_PARSE;
}
/* Evaluate the condition, including any side effects from the variable
/*
* Evaluate the condition, including any side effects from the variable
* expressions in the condition. The condition consists of &&, ||, !,
* function(arg), comparisons and parenthetical groupings thereof.
*
@ -1050,15 +1089,25 @@ CondEvalExpression(const struct If *info, const char *cond, Boolean *value,
return rval;
}
/* Evaluate a condition in a :? modifier, such as
* ${"${VAR}" == value:?yes:no}. */
/*
* Evaluate a condition in a :? modifier, such as
* ${"${VAR}" == value:?yes:no}.
*/
CondEvalResult
Cond_EvalCondition(const char *cond, Boolean *out_value)
{
return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE);
}
/* Evaluate the conditional directive in the line, which is one of:
static Boolean
IsEndif(const char *p)
{
return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' &&
p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]);
}
/*
* Evaluate the conditional directive in the line, which is one of:
*
* .if <cond>
* .ifmake <cond>
@ -1088,7 +1137,7 @@ Cond_EvalCondition(const char *cond, Boolean *out_value)
* or because the condition could not be evaluated
*/
CondEvalResult
Cond_EvalLine(const char *const line)
Cond_EvalLine(const char *line)
{
typedef enum IfState {
@ -1117,25 +1166,20 @@ Cond_EvalLine(const char *const line)
const char *p = line;
if (cond_states == NULL) {
cond_states = bmake_malloc(cond_states_cap * sizeof *cond_states);
cond_states = bmake_malloc(
cond_states_cap * sizeof *cond_states);
cond_states[0] = IFS_ACTIVE;
}
p++; /* skip the leading '.' */
cpp_skip_hspace(&p);
/* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
if (p[0] == 'e') {
if (p[1] != 'l') {
if (!is_token(p + 1, "ndif", 4)) {
/* Unknown directive. It might still be a transformation
* rule like '.elisp.scm', therefore no error message here. */
return COND_INVALID;
if (IsEndif(p)) { /* It is an '.endif'. */
if (p[5] != '\0') {
Parse_Error(PARSE_FATAL,
"The .endif directive does not take arguments.");
}
/* It is an '.endif'. */
/* TODO: check for extraneous <cond> */
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less endif");
return COND_PARSE;
@ -1147,13 +1191,25 @@ Cond_EvalLine(const char *const line)
? COND_PARSE : COND_SKIP;
}
/* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
if (p[0] == 'e') {
if (p[1] != 'l') {
/*
* Unknown directive. It might still be a
* transformation rule like '.elisp.scm',
* therefore no error message here.
*/
return COND_INVALID;
}
/* Quite likely this is 'else' or 'elif' */
p += 2;
if (is_token(p, "se", 2)) { /* It is an 'else'. */
if (opts.lint && p[2] != '\0')
if (p[2] != '\0')
Parse_Error(PARSE_FATAL,
"The .else directive does not take arguments.");
"The .else directive "
"does not take arguments.");
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less else");
@ -1165,7 +1221,8 @@ Cond_EvalLine(const char *const line)
state = IFS_ACTIVE | IFS_SEEN_ELSE;
} else {
if (state & IFS_SEEN_ELSE)
Parse_Error(PARSE_WARNING, "extra else");
Parse_Error(PARSE_WARNING,
"extra else");
state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
}
cond_states[cond_depth] = state;
@ -1178,8 +1235,10 @@ Cond_EvalLine(const char *const line)
isElif = FALSE;
if (p[0] != 'i' || p[1] != 'f') {
/* Unknown directive. It might still be a transformation rule like
* '.elisp.scm', therefore no error message here. */
/*
* Unknown directive. It might still be a transformation rule
* like '.elisp.scm', therefore no error message here.
*/
return COND_INVALID; /* Not an ifxxx or elifxxx line */
}
@ -1190,10 +1249,13 @@ Cond_EvalLine(const char *const line)
p += 2;
for (ifp = ifs;; ifp++) {
if (ifp->form == NULL) {
/* TODO: Add error message about unknown directive,
* since there is no other known directive that starts with 'el'
* or 'if'.
* Example: .elifx 123 */
/*
* TODO: Add error message about unknown directive,
* since there is no other known directive that starts
* with 'el' or 'if'.
*
* Example: .elifx 123
*/
return COND_INVALID;
}
if (is_token(p, ifp->form, ifp->formlen)) {
@ -1212,7 +1274,8 @@ Cond_EvalLine(const char *const line)
state = cond_states[cond_depth];
if (state & IFS_SEEN_ELSE) {
Parse_Error(PARSE_WARNING, "extra elif");
cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
cond_states[cond_depth] =
IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
return COND_SKIP;
}
if (state != IFS_INITIAL) {
@ -1229,12 +1292,16 @@ Cond_EvalLine(const char *const line)
*/
cond_states_cap += 32;
cond_states = bmake_realloc(cond_states,
cond_states_cap * sizeof *cond_states);
cond_states_cap *
sizeof *cond_states);
}
state = cond_states[cond_depth];
cond_depth++;
if (!(state & IFS_ACTIVE)) {
/* If we aren't parsing the data, treat as always false */
/*
* If we aren't parsing the data,
* treat as always false.
*/
cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP;
}
@ -1263,8 +1330,8 @@ Cond_restore_depth(unsigned int saved_depth)
unsigned int open_conds = cond_depth - cond_min_depth;
if (open_conds != 0 || saved_depth > cond_depth) {
Parse_Error(PARSE_FATAL, "%u open conditional%s", open_conds,
open_conds == 1 ? "" : "s");
Parse_Error(PARSE_FATAL, "%u open conditional%s",
open_conds, open_conds == 1 ? "" : "s");
cond_depth = cond_min_depth;
}

995
dir.c

File diff suppressed because it is too large Load Diff

28
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.
@ -75,24 +75,9 @@
#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,12 +88,11 @@ 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. */

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.

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

267
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...>
* ...
* # 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,14 +58,14 @@
#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;
size_t nameLen;
} ForVar;
/*
@ -92,7 +90,7 @@ ForAddVar(For *f, const char *name, size_t len)
{
ForVar *var = Vector_Push(&f->vars);
var->name = bmake_strldup(name, len);
var->len = len;
var->nameLen = len;
}
static void
@ -125,7 +123,8 @@ IsEndfor(const char *p)
(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:
@ -178,7 +177,10 @@ For_Eval(const char *line)
return -1;
}
/* XXX: This allows arbitrary variable names; see directive-for.mk. */
/*
* XXX: This allows arbitrary variable names;
* see directive-for.mk.
*/
for (len = 1; p[len] != '\0' && !ch_isspace(p[len]); len++)
continue;
@ -203,8 +205,12 @@ For_Eval(const char *line)
{
char *items;
(void)Var_Subst(p, VAR_GLOBAL, VARE_WANTRES, &items);
/* TODO: handle errors */
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);
@ -215,19 +221,22 @@ For_Eval(const char *line)
{
size_t nitems, nvars;
if ((nitems = f->items.len) > 0 && nitems % (nvars = f->vars.len)) {
if ((nitems = f->items.len) > 0 &&
nitems % (nvars = f->vars.len) != 0) {
Parse_Error(PARSE_FATAL,
"Wrong number of words (%zu) in .for substitution list"
" with %zu variables", nitems, nvars);
"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.
* 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;
}
}
done:
accumFor = f;
forLevel = 1;
return 1;
@ -279,8 +288,7 @@ for_var_len(const char *var)
else if (var_start == '{')
var_end = '}';
else
/* Single char variable */
return 1;
return 1; /* Single char variable */
depth = 1;
for (len = 1; (ch = var[len++]) != '\0';) {
@ -294,8 +302,10 @@ for_var_len(const char *var)
return 0;
}
/* The .for loop substitutes the items as ${:U<value>...}, which means
* that characters that break this syntax must be backslash-escaped. */
/*
* The .for loop substitutes the items as ${:U<value>...}, which means
* that characters that break this syntax must be backslash-escaped.
*/
static Boolean
NeedsEscapes(const char *word, char endc)
{
@ -308,21 +318,23 @@ NeedsEscapes(const char *word, char endc)
return FALSE;
}
/* While expanding the body of a .for loop, write the item in the ${:U...}
/*
* While expanding the body of a .for loop, write the item in the ${:U...}
* expression, escaping characters as needed.
*
* The result is later unescaped by ApplyModifier_Defined. */
* The result is later unescaped by ApplyModifier_Defined.
*/
static void
Buf_AddEscaped(Buffer *cmds, const char *item, char ech)
Buf_AddEscaped(Buffer *cmds, const char *item, char endc)
{
char ch;
if (!NeedsEscapes(item, ech)) {
if (!NeedsEscapes(item, endc)) {
Buf_AddStr(cmds, item);
return;
}
/* Escape ':', '$', '\\' and 'ech' - these will be removed later by
/* Escape ':', '$', '\\' and 'endc' - these will be removed later by
* :U processing, see ApplyModifier_Defined. */
while ((ch = *item++) != '\0') {
if (ch == '$') {
@ -333,136 +345,146 @@ Buf_AddEscaped(Buffer *cmds, const char *item, char ech)
continue;
}
Buf_AddByte(cmds, '\\');
} else if (ch == ':' || ch == '\\' || ch == ech)
} else if (ch == ':' || ch == '\\' || ch == endc)
Buf_AddByte(cmds, '\\');
Buf_AddByte(cmds, ch);
}
}
/* While expanding the body of a .for loop, replace expressions like
* ${i}, ${i:...}, $(i) or $(i:...) with their ${:U...} expansion. */
/*
* While expanding the body of a .for loop, replace the variable name of an
* expression like ${i} or ${i:...} or $(i) or $(i:...) with ":Uvalue".
*/
static void
SubstVarLong(For *f, const char **pp, const char **inout_mark, char ech)
SubstVarLong(For *f, const char **pp, const char *bodyEnd, char endc,
const char **inout_mark)
{
size_t i;
const char *p = *pp;
for (i = 0; i < f->vars.len; i++) {
ForVar *forVar = Vector_Get(&f->vars, i);
char *var = forVar->name;
size_t vlen = forVar->len;
char *varname = forVar->name;
size_t varnameLen = forVar->nameLen;
/* XXX: undefined behavior for p if vlen is longer than p? */
if (memcmp(p, var, vlen) != 0)
if (varnameLen >= (size_t)(bodyEnd - p))
continue;
if (memcmp(p, varname, varnameLen) != 0)
continue;
/* XXX: why test for backslash here? */
if (p[vlen] != ':' && p[vlen] != ech && p[vlen] != '\\')
if (p[varnameLen] != ':' && p[varnameLen] != endc &&
p[varnameLen] != '\\')
continue;
/* Found a variable match. Replace with :U<value> */
/*
* 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], ech);
Buf_AddEscaped(&f->curBody,
f->items.words[f->sub_next + i], endc);
p += vlen;
p += varnameLen;
*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.
* 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 expansions ensures that the .for loop can't generate
* Using variable expressions ensures that the .for loop can't generate
* syntax, and that the later parsing will still see a variable.
* We assume that the null variable will never be defined.
* This code assumes that the variable with the empty name will never be
* defined, see unit-tests/varname-empty.mk for more details.
*
* The detection of substitutions of the loop control variable is naive.
* Many of the modifiers use \ to escape $ (not $) so it is possible
* to contrive a makefile where an unwanted substitution happens.
*/
static void
ForSubstBody(For *f)
{
const char *p, *bodyEnd;
const char *mark; /* where the last replacement left off */
Buf_Empty(&f->curBody);
mark = f->body.data;
bodyEnd = f->body.data + f->body.len;
for (p = mark; (p = strchr(p, '$')) != NULL;) {
if (p[1] == '{' || p[1] == '(') {
p += 2;
SubstVarLong(f, &p, bodyEnd, p[-1] == '{' ? '}' : ')',
&mark);
} else if (p[1] != '\0') {
SubstVarShort(f, p + 1, &mark);
p += 2;
} else
break;
}
Buf_AddBytesBetween(&f->curBody, mark, bodyEnd);
}
/*
* Compute the body for the current iteration by copying the unexpanded body,
* replacing the expressions for the iteration variables on the way.
*/
static char *
ForIterate(void *v_arg, size_t *out_len)
ForReadMore(void *v_arg, size_t *out_len)
{
For *f = v_arg;
const char *p;
const char *mark; /* where the last replacement left off */
const char *body_end;
char *cmds_str;
if (f->sub_next + f->vars.len > f->items.len) {
if (f->sub_next == f->items.len) {
/* No more iterations */
For_Free(f);
return NULL;
}
Buf_Empty(&f->curBody);
ForSubstBody(f);
DEBUG1(FOR, "For: loop body:\n%s", f->curBody.data);
f->sub_next += (unsigned int)f->vars.len;
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;
}
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;
*out_len = f->curBody.len;
return f->curBody.data;
}
/* Run the for loop, imitating the actions of an include file. */
/* Run the .for loop, imitating the actions of an include file. */
void
For_Run(int lineno)
{
@ -470,10 +492,13 @@ For_Run(int lineno)
accumFor = NULL;
if (f->items.len == 0) {
/* Nothing to expand - possibly due to an earlier syntax error. */
/*
* 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);
}

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

40
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.
@ -103,6 +103,11 @@ typedef struct HashIter {
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)
{
@ -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

1676
job.c

File diff suppressed because it is too large Load Diff

32
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.
@ -125,20 +125,8 @@ typedef enum JobStatus {
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,7 +136,8 @@ 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;
@ -161,8 +150,7 @@ typedef struct Job {
* 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. */
/* This is where the shell commands go. */
FILE *cmdFILE;
int exit_status; /* from wait4() in signal handler */
@ -171,7 +159,12 @@ typedef struct Job {
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 */
@ -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 */

134
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,21 +34,17 @@
#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;
}
@ -57,16 +53,12 @@ List *
Lst_New(void)
{
List *list = bmake_malloc(sizeof *list);
list->first = NULL;
list->last = NULL;
Lst_Init(list);
return list;
}
/* Free a list and all its nodes. The node data are not freed though. */
void
Lst_Free(List *list)
Lst_Done(List *list)
{
ListNode *ln, *next;
@ -74,14 +66,10 @@ Lst_Free(List *list)
next = ln->next;
free(ln);
}
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. */
void
Lst_Destroy(List *list, LstFreeProc freeProc)
Lst_DoneCall(List *list, LstFreeProc freeProc)
{
ListNode *ln, *next;
@ -90,7 +78,25 @@ Lst_Destroy(List *list, LstFreeProc freeProc)
freeProc(ln->datum);
free(ln);
}
}
/* Free a list and all its nodes. The node data are not freed though. */
void
Lst_Free(List *list)
{
Lst_Done(list);
free(list);
}
/*
* 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)
{
Lst_DoneCall(list, freeProc);
free(list);
}
@ -150,8 +156,10 @@ Lst_Append(List *list, void *datum)
}
}
/* Remove the given node from the given list.
* The datum stored in the node must be freed by the caller, if necessary. */
/*
* 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)
{
@ -177,17 +185,21 @@ LstNode_Set(ListNode *ln, void *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;
}
/* 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)
{
@ -202,22 +214,10 @@ Lst_FindDatum(List *list, const void *datum)
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)
{
@ -230,36 +230,26 @@ Lst_MoveAll(List *dst, List *src)
dst->last = src->last;
}
free(src);
}
/* 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. */
@ -276,26 +266,30 @@ void
Vector_Init(Vector *v, size_t itemSize)
{
v->len = 0;
v->priv_cap = 10;
v->cap = 10;
v->itemSize = itemSize;
v->items = bmake_malloc(v->priv_cap * v->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);
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)
{
@ -303,9 +297,3 @@ Vector_Pop(Vector *v)
v->len--;
return Vector_Get(v, v->len);
}
void
Vector_Done(Vector *v)
{
free(v->items);
}

72
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,9 +78,14 @@
#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;
@ -90,36 +95,45 @@ typedef struct ListNode ListNode;
struct ListNode {
ListNode *prev; /* previous node in list, or NULL */
ListNode *next; /* next node in list, or NULL */
union {
void *datum; /* datum associated with this element */
const struct GNode *priv_gnode; /* alias, just for debugging */
const char *priv_str; /* alias, just for debugging */
};
};
struct List {
ListNode *first; /* first node in list */
ListNode *last; /* last node in list */
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,34 +159,34 @@ 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 itemSize; /* size of a single item */
size_t len; /* number of actually usable elements */
size_t priv_cap; /* capacity */
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)
{
@ -182,6 +196,10 @@ Vector_Get(Vector *v, size_t i)
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 */

362
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,10 +1038,10 @@ 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));
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);
}
@ -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
@ -126,7 +113,6 @@
* 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

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

487
make.c

File diff suppressed because it is too large Load Diff

374
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
@ -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)
@ -189,80 +197,115 @@ typedef int Boolean;
# define POSIX_SIGNALS
#endif
/*
* The typical flow of states is:
*
* The direct successful path:
* UNMADE -> BEINGMADE -> MADE.
*
* The direct error path:
* UNMADE -> BEINGMADE -> ERROR.
*
* The successful path when dependencies need to be made first:
* UNMADE -> DEFERRED -> REQUESTED -> BEINGMADE -> MADE.
*
* A node that has dependencies, and one of the dependencies cannot be made:
* UNMADE -> DEFERRED -> ABORTED.
*
* A node that turns out to be up-to-date:
* UNMADE -> BEINGMADE -> UPTODATE.
*/
typedef enum GNodeMade {
UNMADE, /* Not examined yet */
DEFERRED, /* Examined once (building child) */
REQUESTED, /* on toBeMade list */
BEINGMADE, /* Target is already being made.
* Indicates a cycle in the graph. */
MADE, /* Was out-of-date and has been made */
UPTODATE, /* Was already up-to-date */
ERROR, /* An error occurred while it was being
* made (used only in compat mode) */
ABORTED /* The target was aborted due to an error
* making an inferior (compat). */
/* Not examined yet. */
UNMADE,
/* The node has been examined but is not yet ready since its
* dependencies have to be made first. */
DEFERRED,
/* The node is on the toBeMade list. */
REQUESTED,
/* The node is already being made. Trying to build a node in this
* state indicates a cycle in the graph. */
BEINGMADE,
/* Was out-of-date and has been made. */
MADE,
/* Was already up-to-date, does not need to be made. */
UPTODATE,
/* An error occurred while it was being made.
* Used only in compat mode. */
ERROR,
/* The target was aborted due to an error making a dependency.
* Used only in compat mode. */
ABORTED
} GNodeMade;
/* The OP_ constants are used when parsing a dependency line as a way of
/*
* The OP_ constants are used when parsing a dependency line as a way of
* communicating to other parts of the program the way in which a target
* should be made.
*
* Some of the OP_ constants can be combined, others cannot. */
* Some of the OP_ constants can be combined, others cannot.
*/
typedef enum GNodeType {
OP_NONE = 0,
/* The dependency operator ':' is the most common one. The commands of
* this node are executed if any child is out-of-date. */
/* 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. */
/* The dependency operator '::' behaves like ':', except that it
* allows multiple dependency groups to be defined. Each of these
* groups is executed on its own, independently from the others.
* Each individual dependency group is called a cohort. */
OP_DOUBLEDEP = 1 << 2,
/* Matches the dependency operators ':', '!' and '::'. */
OP_OPMASK = OP_DEPENDS | OP_FORCE | OP_DOUBLEDEP,
/* Don't care if the target doesn't exist and can't be created */
/* Don't care if the target doesn't exist and can't be created. */
OP_OPTIONAL = 1 << 3,
/* Use associated commands for parents */
/* 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 */
* Its time doesn't matter, so it has none...sort of. */
OP_EXEC = 1 << 5,
/* Ignore non-zero exit status from shell commands when creating the node */
/* Ignore non-zero exit status from shell commands when creating the
* node. */
OP_IGNORE = 1 << 6,
/* Don't remove the target when interrupted */
/* Don't remove the target when interrupted. */
OP_PRECIOUS = 1 << 7,
/* Don't echo commands when executed */
/* Don't echo commands when executed. */
OP_SILENT = 1 << 8,
/* Target is a recursive make so its commands should always be executed
* when it is out of date, regardless of the state of the -n or -t flags */
/* Target is a recursive make so its commands should always be
* executed when it is out of date, regardless of the state of the
* -n or -t flags. */
OP_MAKE = 1 << 9,
/* Target is out-of-date only if any of its children was out-of-date */
/* Target is out-of-date only if any of its children was out-of-date. */
OP_JOIN = 1 << 10,
/* Assume the children of the node have been already made */
/* Assume the children of the node have been already made. */
OP_MADE = 1 << 11,
/* Special .BEGIN, .END, .INTERRUPT */
/* Special .BEGIN, .END or .INTERRUPT. */
OP_SPECIAL = 1 << 12,
/* Like .USE, only prepend commands */
/* Like .USE, only prepend commands. */
OP_USEBEFORE = 1 << 13,
/* The node is invisible to its parents. I.e. it doesn't show up in the
* parents' local variables (.IMPSRC, .ALLSRC). */
/* The node is invisible to its parents. I.e. it doesn't show up in
* the parents' local variables (.IMPSRC, .ALLSRC). */
OP_INVISIBLE = 1 << 14,
/* The node is exempt from normal 'main target' processing in parse.c */
/* 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 */
/* Not a file target; run always. */
OP_PHONY = 1 << 16,
/* Don't search for file in the path */
/* 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. */
/* 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,
@ -276,44 +319,56 @@ typedef enum GNodeType {
/* Attributes applied by PMake */
/* The node is a transformation rule, such as ".c.o". */
OP_TRANSFORM = 1 << 31,
OP_TRANSFORM = 1 << 30,
/* Target is a member of an archive */
/* XXX: How does this differ from OP_ARCHV? */
OP_MEMBER = 1 << 30,
OP_MEMBER = 1 << 29,
/* The node is a library,
* its name has the form "-l<libname>" */
OP_LIB = 1 << 29,
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 << 28,
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 << 27,
* 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 << 26,
/* Already processed by Suff_FindDeps */
OP_DEPS_FOUND = 1 << 25,
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 << 24,
OP_MARK = 1 << 23,
OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM
} GNodeType;
typedef enum GNodeFlags {
REMAKE = 0x0001, /* this target needs to be (re)made */
CHILDMADE = 0x0002, /* children of this target were made */
FORCE = 0x0004, /* children don't exist, and we pretend made */
DONE_WAIT = 0x0008, /* Set by Make_ProcessWait() */
DONE_ORDER = 0x0010, /* Build requested by .ORDER processing */
FROM_DEPEND = 0x0020, /* Node created from .depend */
DONE_ALLSRC = 0x0040, /* We do it once only */
CYCLE = 0x1000, /* Used by MakePrintStatus */
DONECYCLE = 0x2000, /* Used by MakePrintStatus */
INTERNAL = 0x4000 /* Internal use only */
GNF_NONE = 0,
/* this target needs to be (re)made */
REMAKE = 0x0001,
/* children of this target were made */
CHILDMADE = 0x0002,
/* children don't exist, and we pretend made */
FORCE = 0x0004,
/* Set by Make_ProcessWait() */
DONE_WAIT = 0x0008,
/* Build requested by .ORDER processing */
DONE_ORDER = 0x0010,
/* Node created from .depend */
FROM_DEPEND = 0x0020,
/* We do it once only */
DONE_ALLSRC = 0x0040,
/* Used by MakePrintStatus */
CYCLE = 0x1000,
/* Used by MakePrintStatus */
DONECYCLE = 0x2000,
/* Internal use only */
INTERNAL = 0x4000
} GNodeFlags;
typedef struct List StringList;
@ -324,53 +379,58 @@ 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. */
* 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).
/* 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 number of unmade children */
int unmade;
/* The modification time; 0 means the node does not have a corresponding
* file; see GNode_IsOODate. */
/* The modification time; 0 means the node does not have a
* corresponding file; see GNode_IsOODate. */
time_t mtime;
struct GNode *youngestChild;
/* The GNodes for which this node is an implied source. May be empty.
* For example, when there is an inference rule for .c.o, the node for
* file.c has the node for file.o in this list. */
GNodeList *implicitParents;
GNodeList implicitParents;
/* The nodes that depend on this one, or in other words, the nodes for
* which this is a source. */
GNodeList *parents;
GNodeList parents;
/* The nodes on which this one depends. */
GNodeList *children;
GNodeList children;
/* .ORDER nodes we need made. The nodes that must be made (if they're
* made) before this node can be made, but that do not enter into the
* datedness of this node. */
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;
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;
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 */
@ -382,19 +442,19 @@ typedef struct GNode {
/* 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 $@, $<, $?.
/* 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;
HashTable /* of Var pointer */ vars;
/* The commands to be given to a shell to create this target. */
StringList *commands;
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? */
@ -405,7 +465,8 @@ typedef struct GNode {
/* Error levels for diagnostics during parsing. */
typedef enum ParseErrorLevel {
/* Exit when the current top-level makefile has been parsed completely. */
/* Exit when the current top-level makefile has been parsed
* completely. */
PARSE_FATAL = 1,
/* Print "warning"; may be upgraded to fatal by the -w option. */
PARSE_WARNING,
@ -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. */
@ -505,9 +559,10 @@ 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 */
/* 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 */
@ -543,33 +598,28 @@ typedef enum DebugFlags {
#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,
@ -593,7 +643,7 @@ typedef struct CmdOpts {
*
* Runs make in strict mode, with additional checks and better error
* handling. */
Boolean lint;
Boolean strict;
/* -dV: for the -V option, print unexpanded variable values */
Boolean debugVflag;
@ -602,7 +652,7 @@ typedef struct CmdOpts {
Boolean checkEnvFirst;
/* -f: the makefiles to read */
StringList *makefiles;
StringList makefiles;
/* -i: if true, ignore all errors from shell commands */
Boolean ignoreErrors;
@ -611,8 +661,8 @@ typedef struct CmdOpts {
* 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 */
/* -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 */
@ -621,8 +671,8 @@ typedef struct CmdOpts {
/* -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 */
/* -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. */
@ -638,7 +688,7 @@ typedef struct CmdOpts {
/* -[Vv]: print expanded or unexpanded selected variables */
PrintVarsMode printVars;
/* -[Vv]: the variables to print */
StringList *variables;
StringList variables;
/* -W: if true, makefile parsing warnings are treated as errors */
Boolean parseWarnFatal;
@ -652,7 +702,7 @@ typedef struct CmdOpts {
/* The target names specified on the command line.
* Used to resolve .if make(...) statements. */
StringList *create;
StringList create;
} CmdOpts;
@ -690,6 +740,30 @@ GNode_Path(const GNode *gn)
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 *
GNode_VarTarget(GNode *gn) { return Var_ValueDirect(TARGET, gn); }
MAKE_INLINE const char *
@ -776,11 +850,17 @@ pp_skip_hspace(char **pp)
(*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
#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,11 +41,13 @@ 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)
{

325
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
@ -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++)
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,9 +927,9 @@ 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 */
@ -947,21 +938,28 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
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 */
{
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,7 +1415,7 @@ 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 */
@ -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,8 +1699,8 @@ 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;
}

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
.if ${.MAKE.LEVEL:U1} == 0 && ${MK_DIRDEPS_BUILD:Uno} == "yes"
# 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}}} != ""
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.

194
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;
@ -140,27 +154,81 @@ typedef struct VarAssign {
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;
} 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) {
Words_Free(Words w)
{
free(w.words);
free(w.freeIt);
}
@ -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);
@ -237,14 +303,27 @@ typedef enum VarEvalFlags {
/* 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.
* multiple such assignments to be chained without accidentally
* expanding '$$file' to '$file' in the first assignment and
* interpreting it as '${f}' followed by 'ile' in the next assignment.
*
* See also preserveUndefined, which preserves subexpressions that are
* based on undefined variables; maybe that can be converted to a flag
* as well. */
VARE_KEEP_DOLLAR = 1 << 2
VARE_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 {
@ -258,76 +337,55 @@ typedef enum VarSetFlags {
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,
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

1556
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,6 +43,7 @@
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif
#define _PATH_OBJDIR "obj"
#define _PATH_OBJDIRPREFIX "/usr/obj"
#ifndef _PATH_DEFSHELLDIR

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

1845
suff.c

File diff suppressed because it is too large Load Diff

268
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,11 +131,7 @@ static void GNode_Free(void *);
void
Targ_Init(void)
{
allTargets = Lst_New();
HashTable_Init(&allTargetsByName);
#ifdef CLEANUP
allNodes = Lst_New();
#endif
}
void
@ -146,9 +139,9 @@ Targ_End(void)
{
Targ_Stats();
#ifdef CLEANUP
Lst_Free(allTargets);
Lst_Done(&allTargets);
HashTable_Done(&allTargetsByName);
Lst_Destroy(allNodes, GNode_Free);
Lst_DoneCall(&allNodes, GNode_Free);
#endif
}
@ -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
@ -192,30 +186,30 @@ GNode_New(const char *name)
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->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;
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();
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->context);
gn->commands = Lst_New();
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;
@ -230,22 +224,49 @@ GNode_Free(void *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. */
/* 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);
}
@ -285,7 +306,8 @@ Targ_NewInternalNode(const char *name)
{
GNode *gn = GNode_New(name);
Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
Lst_Append(allTargets, gn);
Lst_Append(&allTargets, gn);
DEBUG1(TARG, "Adding \"%s\" to all targets.\n", gn->name);
if (doing_depend)
gn->flags |= FROM_DEPEND;
return gn;
@ -295,10 +317,15 @@ Targ_NewInternalNode(const char *name)
* 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. */
/*
* 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;
@ -306,32 +333,17 @@ GNode *Targ_GetEndNode(void)
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);
Lst_Append(gns, gn);
}
return nodes;
}
/* 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;
}
/* See if the given target is precious. */
@ -358,10 +370,10 @@ Targ_SetMain(GNode *gn)
static void
PrintNodeNames(GNodeList *gnodes)
{
GNodeListNode *node;
GNodeListNode *ln;
for (node = gnodes->first; node != NULL; node = node->next) {
GNode *gn = node->datum;
for (ln = gnodes->first; ln != NULL; ln = ln->next) {
GNode *gn = ln->datum;
debug_printf(" %s%s", gn->name, gn->cohort_num);
}
}
@ -380,21 +392,24 @@ void
Targ_PrintCmds(GNode *gn)
{
StringListNode *ln;
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;
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];
parts = localtime(&tm);
struct tm *parts = localtime(&tm);
(void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts);
return buf;
}
@ -405,32 +420,32 @@ Targ_PrintType(int type)
{
int tbit;
#define PRINTBIT(attr) case CONCAT(OP_,attr): debug_printf(" ." #attr); break
#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))debug_printf(" ." #attr); break
type &= ~OP_OPMASK;
while (type) {
while (type != 0) {
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);
#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
}
}
}
@ -474,54 +489,54 @@ Targ_PrintNode(GNode *gn, int pass)
if (gn->flags == 0)
return;
if (GNode_IsTarget(gn)) {
if (!GNode_IsTarget(gn))
return;
debug_printf("#\n");
if (gn == mainTarg) {
if (gn == mainTarg)
debug_printf("# *** MAIN TARGET ***\n");
}
if (pass >= 2) {
if (gn->unmade > 0) {
if (gn->unmade > 0)
debug_printf("# %d unmade children\n", gn->unmade);
} else {
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",
debug_printf("# nonexistent (maybe): %s\n",
made_name(gn->made));
} else {
} else
debug_printf("# unmade\n");
}
}
PrintNodeNamesLine("implicit parents", gn->implicitParents);
PrintNodeNamesLine("implicit parents", &gn->implicitParents);
} else {
if (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);
}
@ -532,7 +547,7 @@ PrintOnlySources(void)
{
GNodeListNode *ln;
for (ln = allTargets->first; ln != NULL; ln = ln->next) {
for (ln = allTargets.first; ln != NULL; ln = ln->next) {
GNode *gn = ln->datum;
if (GNode_IsTarget(gn))
continue;
@ -543,7 +558,8 @@ PrintOnlySources(void)
}
}
/* Input:
/*
* Input:
* pass 1 => before processing
* 2 => after processing
* 3 => after processing, an error occurred
@ -552,7 +568,7 @@ void
Targ_PrintGraph(int pass)
{
debug_printf("#*** Input graph:\n");
Targ_PrintNodes(allTargets, pass);
Targ_PrintNodes(&allTargets, pass);
debug_printf("\n");
debug_printf("\n");
@ -573,24 +589,26 @@ Targ_PrintGraph(int pass)
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;
for (ln = allTargets->first; ln != NULL; ln = ln->next) {
for (ln = allTargets.first; ln != NULL; ln = ln->next) {
GNode *gn = ln->datum;
GNodeType type = gn->type;
if (!(type & OP_DOUBLEDEP))
continue;
for (cln = gn->cohorts->first; cln != NULL; cln = cln->next) {
for (cln = gn->cohorts.first; cln != NULL; cln = cln->next) {
GNode *cohort = cln->datum;
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
@ -425,18 +461,19 @@ ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2
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' \
@ -449,25 +486,33 @@ SED_CMDS.job-output-long-lines= \
-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.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,8 +520,7 @@ 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.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'
@ -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