Module Name: src
Committed By: rillig
Date: Tue Jun 20 09:25:34 UTC 2023
Modified Files:
src/usr.bin/make: cond.c make.h parse.c
src/usr.bin/make/unit-tests: Makefile directive-include-guard.exp
directive-include-guard.mk
Log Message:
make: allow targets to be used as multiple-inclusion guards
This style is used by FreeBSD, among others.
To generate a diff of this commit:
cvs rdiff -u -r1.349 -r1.350 src/usr.bin/make/cond.c
cvs rdiff -u -r1.322 -r1.323 src/usr.bin/make/make.h
cvs rdiff -u -r1.701 -r1.702 src/usr.bin/make/parse.c
cvs rdiff -u -r1.338 -r1.339 src/usr.bin/make/unit-tests/Makefile
cvs rdiff -u -r1.5 -r1.6 \
src/usr.bin/make/unit-tests/directive-include-guard.exp
cvs rdiff -u -r1.6 -r1.7 \
src/usr.bin/make/unit-tests/directive-include-guard.mk
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/usr.bin/make/cond.c
diff -u src/usr.bin/make/cond.c:1.349 src/usr.bin/make/cond.c:1.350
--- src/usr.bin/make/cond.c:1.349 Mon Jun 19 20:07:35 2023
+++ src/usr.bin/make/cond.c Tue Jun 20 09:25:33 2023
@@ -1,4 +1,4 @@
-/* $NetBSD: cond.c,v 1.349 2023/06/19 20:07:35 rillig Exp $ */
+/* $NetBSD: cond.c,v 1.350 2023/06/20 09:25:33 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -92,7 +92,7 @@
#include "dir.h"
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: cond.c,v 1.349 2023/06/19 20:07:35 rillig Exp $");
+MAKE_RCSID("$NetBSD: cond.c,v 1.350 2023/06/20 09:25:33 rillig Exp $");
/*
* Conditional expressions conform to this grammar:
@@ -1252,15 +1252,30 @@ ParseVarnameGuard(const char **pp, const
return false;
}
-/*
- * Tests whether the line is a conditional that forms a multiple-inclusion
- * guard, and if so, extracts the guard variable name.
- */
-char *
+static bool
+ParseTargetGuard(const char **pp, const char **target)
+{
+ const char *p = *pp;
+
+ if (ch_isalpha(*p) || *p == '_') {
+ while (ch_isalnum(*p) || *p == '_' || *p == '-'
+ || *p == '<' || *p == '>' || *p == '.' || *p == '/')
+ p++;
+ *target = *pp;
+ *pp = p;
+ return true;
+ }
+ return false;
+}
+
+/* Extracts the multiple-inclusion guard from a conditional, if any. */
+Guard *
Cond_ExtractGuard(const char *line)
{
- const char *p, *varname;
+ const char *p, *name;
Substring dir;
+ enum GuardKind kind;
+ Guard *guard;
p = line + 1; /* skip the '.' */
cpp_skip_hspace(&p);
@@ -1271,14 +1286,32 @@ Cond_ExtractGuard(const char *line)
dir.end = p;
cpp_skip_hspace(&p);
- if (Substring_Equals(dir, "if"))
- return skip_string(&p, "!defined(")
- && ParseVarnameGuard(&p, &varname) && strcmp(p, ")") == 0
- ? bmake_strsedup(varname, p) : NULL;
- if (Substring_Equals(dir, "ifndef"))
- return ParseVarnameGuard(&p, &varname) && *p == '\0'
- ? bmake_strsedup(varname, p) : NULL;
+ if (Substring_Equals(dir, "if")) {
+ if (skip_string(&p, "!defined(")) {
+ if (ParseVarnameGuard(&p, &name)
+ && strcmp(p, ")") == 0)
+ goto found_variable;
+ } else if (skip_string(&p, "!target(")) {
+ if (ParseTargetGuard(&p, &name)
+ && strcmp(p, ")") == 0)
+ goto found_target;
+ }
+ } else if (Substring_Equals(dir, "ifndef")) {
+ if (ParseVarnameGuard(&p, &name) && *p == '\0')
+ goto found_variable;
+ }
return NULL;
+
+found_variable:
+ kind = GK_VARIABLE;
+ goto found;
+found_target:
+ kind = GK_TARGET;
+found:
+ guard = bmake_malloc(sizeof(*guard));
+ guard->kind = kind;
+ guard->name = bmake_strsedup(name, p);
+ return guard;
}
void
Index: src/usr.bin/make/make.h
diff -u src/usr.bin/make/make.h:1.322 src/usr.bin/make/make.h:1.323
--- src/usr.bin/make/make.h:1.322 Mon Jun 19 12:53:57 2023
+++ src/usr.bin/make/make.h Tue Jun 20 09:25:33 2023
@@ -1,4 +1,4 @@
-/* $NetBSD: make.h,v 1.322 2023/06/19 12:53:57 rillig Exp $ */
+/* $NetBSD: make.h,v 1.323 2023/06/20 09:25:33 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -539,6 +539,14 @@ typedef enum CondResult {
CR_ERROR /* Unknown directive or parse error */
} CondResult;
+typedef struct {
+ enum GuardKind {
+ GK_VARIABLE,
+ GK_TARGET
+ } kind;
+ char *name;
+} Guard;
+
/* Names of the variables that are "local" to a specific target. */
#define TARGET "@" /* Target of dependency */
#define OODATE "?" /* All out-of-date sources */
@@ -793,7 +801,7 @@ void Compat_Make(GNode *, GNode *);
extern unsigned int cond_depth;
CondResult Cond_EvalCondition(const char *) MAKE_ATTR_USE;
CondResult Cond_EvalLine(const char *) MAKE_ATTR_USE;
-char *Cond_ExtractGuard(const char *) MAKE_ATTR_USE;
+Guard *Cond_ExtractGuard(const char *) MAKE_ATTR_USE;
void Cond_EndFile(void);
/* dir.c; see also dir.h */
Index: src/usr.bin/make/parse.c
diff -u src/usr.bin/make/parse.c:1.701 src/usr.bin/make/parse.c:1.702
--- src/usr.bin/make/parse.c:1.701 Mon Jun 19 17:30:56 2023
+++ src/usr.bin/make/parse.c Tue Jun 20 09:25:33 2023
@@ -1,4 +1,4 @@
-/* $NetBSD: parse.c,v 1.701 2023/06/19 17:30:56 rillig Exp $ */
+/* $NetBSD: parse.c,v 1.702 2023/06/20 09:25:33 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -105,7 +105,7 @@
#include "pathnames.h"
/* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: parse.c,v 1.701 2023/06/19 17:30:56 rillig Exp $");
+MAKE_RCSID("$NetBSD: parse.c,v 1.702 2023/06/20 09:25:33 rillig Exp $");
/* Detects a multiple-inclusion guard in a makefile. */
typedef enum {
@@ -136,8 +136,8 @@ typedef struct IncludedFile {
char *buf_ptr; /* next char to be read */
char *buf_end; /* buf_end[-1] == '\n' */
- GuardState guard;
- char *guardVarname;
+ GuardState guardState;
+ Guard *guard;
struct ForLoop *forLoop;
} IncludedFile;
@@ -310,7 +310,7 @@ static const struct {
enum PosixState posix_state = PS_NOT_YET;
-static HashTable guards;
+static HashTable /* full file name -> Guard */ guards;
static IncludedFile *
GetInclude(size_t i)
@@ -1213,13 +1213,19 @@ FindInQuotPath(const char *file)
static bool
SkipGuarded(const char *fullname)
{
- char *guard = HashTable_FindValue(&guards, fullname);
- if (guard != NULL && GNode_ValueDirect(SCOPE_GLOBAL, guard) != NULL) {
- DEBUG2(PARSE, "Skipping '%s' because '%s' is already set\n",
- fullname, guard);
- return true;
- }
+ Guard *guard = HashTable_FindValue(&guards, fullname);
+ if (guard != NULL && guard->kind == GK_VARIABLE
+ && GNode_ValueDirect(SCOPE_GLOBAL, guard->name) != NULL)
+ goto skip;
+ if (guard != NULL && guard->kind == GK_TARGET
+ && Targ_FindNode(guard->name) != NULL)
+ goto skip;
return false;
+
+skip:
+ DEBUG2(PARSE, "Skipping '%s' because '%s' is defined\n",
+ fullname, guard->name);
+ return true;
}
/*
@@ -2202,8 +2208,8 @@ Parse_PushInput(const char *name, unsign
curFile->forBodyReadLines = readLines;
curFile->buf = buf;
curFile->depending = doing_depend; /* restore this on EOF */
- curFile->guard = forLoop == NULL ? GS_START : GS_NO;
- curFile->guardVarname = NULL;
+ curFile->guardState = forLoop == NULL ? GS_START : GS_NO;
+ curFile->guard = NULL;
curFile->forLoop = forLoop;
if (forLoop != NULL && !For_NextIteration(forLoop, &curFile->buf))
@@ -2346,15 +2352,15 @@ ParseEOF(void)
Cond_EndFile();
- if (curFile->guard == GS_DONE) {
- HashTable_Set(&guards,
- curFile->name.str, curFile->guardVarname);
- curFile->guardVarname = NULL;
+ if (curFile->guardState == GS_DONE)
+ HashTable_Set(&guards, curFile->name.str, curFile->guard);
+ else if (curFile->guard != NULL) {
+ free(curFile->guard->name);
+ free(curFile->guard);
}
FStr_Done(&curFile->name);
Buf_Done(&curFile->buf);
- free(curFile->guardVarname);
if (curFile->forLoop != NULL)
ForLoop_Free(curFile->forLoop);
Vector_Pop(&includes);
@@ -2665,22 +2671,22 @@ ReadHighLevelLine(void)
if (line == NULL)
return NULL;
- if (curFile->guard != GS_NO
- && ((curFile->guard == GS_START && line[0] != '.')
- || curFile->guard == GS_DONE))
- curFile->guard = GS_NO;
+ if (curFile->guardState != GS_NO
+ && ((curFile->guardState == GS_START && line[0] != '.')
+ || curFile->guardState == GS_DONE))
+ curFile->guardState = GS_NO;
if (line[0] != '.')
return line;
condResult = Cond_EvalLine(line);
- if (curFile->guard == GS_START) {
- char *varname;
+ if (curFile->guardState == GS_START) {
+ Guard *guard;
if (condResult == CR_TRUE
- && (varname = Cond_ExtractGuard(line)) != NULL) {
- curFile->guard = GS_COND;
- curFile->guardVarname = varname;
+ && (guard = Cond_ExtractGuard(line)) != NULL) {
+ curFile->guardState = GS_COND;
+ curFile->guard = guard;
} else
- curFile->guard = GS_NO;
+ curFile->guardState = GS_NO;
}
switch (condResult) {
case CR_FALSE: /* May also mean a syntax error. */
@@ -2842,15 +2848,16 @@ Parse_GuardElse(void)
{
IncludedFile *curFile = CurFile();
if (cond_depth == curFile->condMinDepth + 1)
- curFile->guard = GS_NO;
+ curFile->guardState = GS_NO;
}
void
Parse_GuardEndif(void)
{
IncludedFile *curFile = CurFile();
- if (cond_depth == curFile->condMinDepth && curFile->guard == GS_COND)
- curFile->guard = GS_DONE;
+ if (cond_depth == curFile->condMinDepth
+ && curFile->guardState == GS_COND)
+ curFile->guardState = GS_DONE;
}
static char *
@@ -3061,8 +3068,11 @@ Parse_End(void)
assert(includes.len == 0);
Vector_Done(&includes);
HashIter_Init(&hi, &guards);
- while (HashIter_Next(&hi) != NULL)
- free(hi.entry->value);
+ while (HashIter_Next(&hi) != NULL) {
+ Guard *guard = hi.entry->value;
+ free(guard->name);
+ free(guard);
+ }
HashTable_Done(&guards);
#endif
}
Index: src/usr.bin/make/unit-tests/Makefile
diff -u src/usr.bin/make/unit-tests/Makefile:1.338 src/usr.bin/make/unit-tests/Makefile:1.339
--- src/usr.bin/make/unit-tests/Makefile:1.338 Fri Jun 16 09:25:13 2023
+++ src/usr.bin/make/unit-tests/Makefile Tue Jun 20 09:25:34 2023
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.338 2023/06/16 09:25:13 rillig Exp $
+# $NetBSD: Makefile,v 1.339 2023/06/20 09:25:34 rillig Exp $
#
# Unit tests for make(1)
#
@@ -509,6 +509,7 @@ SED_CMDS.directive-include-guard= \
-e '/\.MAKEFLAGS/d' \
-e '/^Parsing line/d' \
-e '/^SetFilenameVars:/d' \
+ -e '/^ParseDependency/d' \
-e '/^ParseEOF:/d'
SED_CMDS.export= -e '/^[^=_A-Za-z0-9]*=/d'
SED_CMDS.export-all= ${SED_CMDS.export}
Index: src/usr.bin/make/unit-tests/directive-include-guard.exp
diff -u src/usr.bin/make/unit-tests/directive-include-guard.exp:1.5 src/usr.bin/make/unit-tests/directive-include-guard.exp:1.6
--- src/usr.bin/make/unit-tests/directive-include-guard.exp:1.5 Mon Jun 19 20:07:35 2023
+++ src/usr.bin/make/unit-tests/directive-include-guard.exp Tue Jun 20 09:25:34 2023
@@ -1,9 +1,9 @@
Parse_PushInput: file guarded-ifndef.tmp, line 1
-Skipping 'guarded-ifndef.tmp' because 'GUARDED_IFNDEF' is already set
+Skipping 'guarded-ifndef.tmp' because 'GUARDED_IFNDEF' is defined
Parse_PushInput: file comments.tmp, line 1
-Skipping 'comments.tmp' because 'COMMENTS' is already set
+Skipping 'comments.tmp' because 'COMMENTS' is defined
Parse_PushInput: file guarded-if.tmp, line 1
-Skipping 'guarded-if.tmp' because 'GUARDED_IF' is already set
+Skipping 'guarded-if.tmp' because 'GUARDED_IF' is defined
Parse_PushInput: file triple-negation.tmp, line 1
Parse_PushInput: file triple-negation.tmp, line 1
Parse_PushInput: file ifdef-negated.tmp, line 1
@@ -19,11 +19,11 @@ Parse_PushInput: file ifndef-indirect.tm
Parse_PushInput: file if-indirect.tmp, line 1
Parse_PushInput: file if-indirect.tmp, line 1
Parse_PushInput: file varassign-indirect.tmp, line 1
-Skipping 'varassign-indirect.tmp' because 'VARASSIGN_INDIRECT' is already set
+Skipping 'varassign-indirect.tmp' because 'VARASSIGN_INDIRECT' is defined
Parse_PushInput: file late-assignment.tmp, line 1
-Skipping 'late-assignment.tmp' because 'LATE_ASSIGNMENT' is already set
+Skipping 'late-assignment.tmp' because 'LATE_ASSIGNMENT' is defined
Parse_PushInput: file two-conditions.tmp, line 1
-Skipping 'two-conditions.tmp' because 'TWO_CONDITIONS' is already set
+Skipping 'two-conditions.tmp' because 'TWO_CONDITIONS' is defined
Parse_PushInput: file already-set.tmp, line 1
Parse_PushInput: file already-set.tmp, line 1
Parse_PushInput: file twice.tmp, line 1
@@ -41,5 +41,17 @@ Parse_PushInput: file if-elif.tmp, line
Parse_PushInput: file if-else.tmp, line 1
Parse_PushInput: file if-else.tmp, line 1
Parse_PushInput: file inner-if-elif-else.tmp, line 1
-Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is already set
+Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is defined
+Parse_PushInput: file target.tmp, line 1
+Skipping 'target.tmp' because '__target.tmp__' is defined
+Parse_PushInput: file target-sys.tmp, line 1
+Skipping 'target-sys.tmp' because '__<target-sys.tmp>__' is defined
+Parse_PushInput: file target-indirect.tmp, line 1
+Parse_PushInput: file target-indirect.tmp, line 1
+Parse_PushInput: file target-unguarded.tmp, line 1
+Parse_PushInput: file target-unguarded.tmp, line 1
+Parse_PushInput: file target-plus.tmp, line 1
+Parse_PushInput: file target-plus.tmp, line 1
+Parse_PushInput: file target-already-set.tmp, line 1
+Parse_PushInput: file target-already-set.tmp, line 1
exit status 0
Index: src/usr.bin/make/unit-tests/directive-include-guard.mk
diff -u src/usr.bin/make/unit-tests/directive-include-guard.mk:1.6 src/usr.bin/make/unit-tests/directive-include-guard.mk:1.7
--- src/usr.bin/make/unit-tests/directive-include-guard.mk:1.6 Mon Jun 19 20:07:35 2023
+++ src/usr.bin/make/unit-tests/directive-include-guard.mk Tue Jun 20 09:25:34 2023
@@ -1,17 +1,22 @@
-# $NetBSD: directive-include-guard.mk,v 1.6 2023/06/19 20:07:35 rillig Exp $
+# $NetBSD: directive-include-guard.mk,v 1.7 2023/06/20 09:25:34 rillig Exp $
#
# Tests for multiple-inclusion guards in makefiles.
#
-# A file that is guarded by a multiple-inclusion guard has the following form:
+# A file that is guarded by a multiple-inclusion guard has one of the
+# following forms:
#
-# .ifndef GUARD_NAME
-# ...
-# GUARD_NAME= # any value, may also be empty
-# ...
+# .ifndef GUARD_VARIABLE
# .endif
#
-# When such a file is included later and the guard variable is set, including
-# the file has no effect, as all its content is skipped.
+# .if !defined(GUARD_VARIABLE)
+# .endif
+#
+# .if !target(guard-target)
+# .endif
+#
+# When such a file is included for the second or later time, and the guard
+# variable is set or the guard target defined, including the file has no
+# effect, as all its content is skipped.
#
# See also:
# https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
@@ -24,7 +29,7 @@ LINES.guarded-ifndef= \
'GUARDED_IFNDEF=' \
'.endif'
# expect: Parse_PushInput: file guarded-ifndef.tmp, line 1
-# expect: Skipping 'guarded-ifndef.tmp' because 'GUARDED_IFNDEF' is already set
+# expect: Skipping 'guarded-ifndef.tmp' because 'GUARDED_IFNDEF' is defined
# Comments and empty lines have no influence on the multiple-inclusion guard.
INCS+= comments
@@ -37,7 +42,7 @@ LINES.comments= \
'.endif' \
'\# comment'
# expect: Parse_PushInput: file comments.tmp, line 1
-# expect: Skipping 'comments.tmp' because 'COMMENTS' is already set
+# expect: Skipping 'comments.tmp' because 'COMMENTS' is defined
# An alternative form uses the 'defined' function. It is more verbose than
# the canonical form. There are other possible forms as well, such as with a
@@ -48,7 +53,7 @@ LINES.guarded-if= \
'GUARDED_IF=' \
'.endif'
# expect: Parse_PushInput: file guarded-if.tmp, line 1
-# expect: Skipping 'guarded-if.tmp' because 'GUARDED_IF' is already set
+# expect: Skipping 'guarded-if.tmp' because 'GUARDED_IF' is defined
# Triple negation is so uncommon that it's not recognized.
INCS+= triple-negation
@@ -127,7 +132,7 @@ LINES.varassign-indirect= \
'$${VARASSIGN_INDIRECT:L}=' \
'.endif'
# expect: Parse_PushInput: file varassign-indirect.tmp, line 1
-# expect: Skipping 'varassign-indirect.tmp' because 'VARASSIGN_INDIRECT' is already set
+# expect: Skipping 'varassign-indirect.tmp' because 'VARASSIGN_INDIRECT' is defined
# The time at which the guard variable is set doesn't matter, as long as it is
# set when the file is included the next time.
@@ -138,7 +143,7 @@ LINES.late-assignment= \
'LATE_ASSIGNMENT=' \
'.endif'
# expect: Parse_PushInput: file late-assignment.tmp, line 1
-# expect: Skipping 'late-assignment.tmp' because 'LATE_ASSIGNMENT' is already set
+# expect: Skipping 'late-assignment.tmp' because 'LATE_ASSIGNMENT' is defined
# The time at which the guard variable is set doesn't matter, as long as it is
# set when the file is included the next time.
@@ -150,11 +155,10 @@ LINES.two-conditions= \
'. endif' \
'.endif'
# expect: Parse_PushInput: file two-conditions.tmp, line 1
-# expect: Skipping 'two-conditions.tmp' because 'TWO_CONDITIONS' is already set
+# expect: Skipping 'two-conditions.tmp' because 'TWO_CONDITIONS' is defined
-# If the guard variable is already set before the file is included for the
-# first time, the file is not considered guarded, as the makefile parser skips
-# all lines in the inactive part between the '.ifndef' and the '.endif'.
+# If the guard variable is defined before the file is included for the first
+# time, the file is not considered guarded.
INCS+= already-set
LINES.already-set= \
'.ifndef ALREADY_SET' \
@@ -258,7 +262,63 @@ LINES.inner-if-elif-else = \
'. endif' \
'.endif'
# expect: Parse_PushInput: file inner-if-elif-else.tmp, line 1
-# expect: Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is already set
+# expect: Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is defined
+
+# The guard can not only be a variable, it can also be a target.
+INCS+= target
+LINES.target= \
+ '.if !target(__target.tmp__)' \
+ '__target.tmp__: .PHONY' \
+ '.endif'
+# expect: Parse_PushInput: file target.tmp, line 1
+# expect: Skipping 'target.tmp' because '__target.tmp__' is defined
+
+# When used for system files, the target name may include '<' and '>'.
+INCS+= target-sys
+LINES.target-sys= \
+ '.if !target(__<target-sys.tmp>__)' \
+ '__<target-sys.tmp>__: .PHONY' \
+ '.endif'
+# expect: Parse_PushInput: file target-sys.tmp, line 1
+# expect: Skipping 'target-sys.tmp' because '__<target-sys.tmp>__' is defined
+
+# The target name must not include '$' or other special characters.
+INCS+= target-indirect
+LINES.target-indirect= \
+ '.if !target($${target-indirect.tmp:L})' \
+ 'target-indirect.tmp: .PHONY' \
+ '.endif'
+# expect: Parse_PushInput: file target-indirect.tmp, line 1
+# expect: Parse_PushInput: file target-indirect.tmp, line 1
+
+# If the target is not defined when including the file the next time, the file
+# is not guarded.
+INCS+= target-unguarded
+LINES.target-unguarded= \
+ '.if !target(target-unguarded)' \
+ '.endif'
+# expect: Parse_PushInput: file target-unguarded.tmp, line 1
+# expect: Parse_PushInput: file target-unguarded.tmp, line 1
+
+# The guard condition must consist of only the guard target, nothing else.
+INCS+= target-plus
+LINES.target-plus= \
+ '.if !target(target-plus) && 1' \
+ 'target-plus: .PHONY' \
+ '.endif'
+# expect: Parse_PushInput: file target-plus.tmp, line 1
+# expect: Parse_PushInput: file target-plus.tmp, line 1
+
+# If the guard target is defined before the file is included for the first
+# time, the file is not considered guarded.
+INCS+= target-already-set
+LINES.target-already-set= \
+ '.if !target(target-already-set)' \
+ 'target-already-set: .PHONY' \
+ '.endif'
+target-already-set: .PHONY
+# expect: Parse_PushInput: file target-already-set.tmp, line 1
+# expect: Parse_PushInput: file target-already-set.tmp, line 1
# Include each of the files twice. The directive-include-guard.exp file