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