aboutsummaryrefslogtreecommitdiff
path: root/contrib/bmake/unit-tests/directive-include-guard.mk
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/bmake/unit-tests/directive-include-guard.mk')
-rw-r--r--contrib/bmake/unit-tests/directive-include-guard.mk648
1 files changed, 648 insertions, 0 deletions
diff --git a/contrib/bmake/unit-tests/directive-include-guard.mk b/contrib/bmake/unit-tests/directive-include-guard.mk
new file mode 100644
index 000000000000..702d0f4ab9dc
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-include-guard.mk
@@ -0,0 +1,648 @@
+# $NetBSD: directive-include-guard.mk,v 1.19 2025/04/11 17:21:31 rillig Exp $
+#
+# Tests for multiple-inclusion guards in makefiles.
+#
+# A file that is guarded by a multiple-inclusion guard has one of the
+# following forms:
+#
+# .ifndef GUARD_VARIABLE
+# .endif
+#
+# .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 or the guard target is defined, the file is skipped completely, as
+# including it would not have any effect, not even on the special variable
+# '.MAKE.MAKEFILES', as that variable skips duplicate pathnames.
+#
+# See also:
+# https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
+
+# Each of the following test cases creates a temporary file named after the
+# test case and writes some lines of text to that file. That file is then
+# included twice, to see whether the second '.include' is skipped.
+
+
+# This is the canonical form of a variable-based multiple-inclusion guard.
+CASES+= variable-ifndef
+LINES.variable-ifndef= \
+ '.ifndef VARIABLE_IFNDEF' \
+ 'VARIABLE_IFNDEF=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef.tmp:1
+# expect: Skipping 'variable-ifndef.tmp' because 'VARIABLE_IFNDEF' is defined
+
+# A file that reuses a guard from a previous file (or whose guard is defined
+# for any other reason) is only processed once, to see whether it is guarded.
+# Its content is skipped, therefore the syntax error is not detected.
+CASES+= variable-ifndef-reuse
+LINES.variable-ifndef-reuse= \
+ '.ifndef VARIABLE_IFNDEF' \
+ 'syntax error' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-reuse.tmp:1
+# expect: Skipping 'variable-ifndef-reuse.tmp' because 'VARIABLE_IFNDEF' is defined
+
+# The guard variable cannot be a number, as numbers are interpreted
+# differently from bare words.
+CASES+= variable-ifndef-zero
+LINES.variable-ifndef-zero= \
+ '.ifndef 0e0' \
+ 'syntax error' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-zero.tmp:1
+# expect: Parse_PushInput: variable-ifndef-zero.tmp:1
+
+# The guard variable cannot be a number, as numbers are interpreted
+# differently from bare words.
+CASES+= variable-ifndef-one
+LINES.variable-ifndef-one= \
+ '.ifndef 1' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-one.tmp:1
+# expect: Parse_PushInput: variable-ifndef-one.tmp:1
+
+# Comments and empty lines do not affect the multiple-inclusion guard.
+CASES+= comments
+LINES.comments= \
+ '\# comment' \
+ '' \
+ '.ifndef COMMENTS' \
+ '\# comment' \
+ 'COMMENTS=\#comment' \
+ '.endif' \
+ '\# comment'
+# expect: Parse_PushInput: comments.tmp:1
+# expect: Skipping 'comments.tmp' because 'COMMENTS' is defined
+
+# An alternative form uses the 'defined' function. It is more verbose than
+# the canonical form but avoids the '.ifndef' directive, as that directive is
+# not commonly used.
+CASES+= variable-if
+LINES.variable-if= \
+ '.if !defined(VARIABLE_IF)' \
+ 'VARIABLE_IF=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if.tmp:1
+# expect: Skipping 'variable-if.tmp' because 'VARIABLE_IF' is defined
+
+# A file that reuses a guard from a previous file (or whose guard is defined
+# for any other reason) is only processed once, to see whether it is guarded.
+# Its content is skipped, therefore the syntax error is not detected.
+CASES+= variable-if-reuse
+LINES.variable-if-reuse= \
+ '.if !defined(VARIABLE_IF)' \
+ 'syntax error' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-reuse.tmp:1
+# expect: Skipping 'variable-if-reuse.tmp' because 'VARIABLE_IF' is defined
+
+# Triple negation is so uncommon that it's not recognized, even though it has
+# the same effect as a single negation.
+CASES+= variable-if-triple-negation
+LINES.variable-if-triple-negation= \
+ '.if !!!defined(VARIABLE_IF_TRIPLE_NEGATION)' \
+ 'VARIABLE_IF_TRIPLE_NEGATION=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-triple-negation.tmp:1
+# expect: Parse_PushInput: variable-if-triple-negation.tmp:1
+
+# If the guard variable is enclosed in spaces, it does not have an effect, as
+# that form is not common in practice.
+CASES+= variable-if-spaced
+LINES.variable-if-spaced= \
+ '.if !defined( VARIABLE_IF_SPACED )' \
+ 'VARIABLE_IF_SPACED=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-spaced.tmp:1
+# expect: Parse_PushInput: variable-if-spaced.tmp:1
+
+# If the guard variable condition is enclosed in parentheses, it does not have
+# an effect, as that form is not common in practice.
+CASES+= variable-if-parenthesized
+LINES.variable-if-parenthesized= \
+ '.if (!defined(VARIABLE_IF_PARENTHESIZED))' \
+ 'VARIABLE_IF_PARENTHESIZED=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-parenthesized.tmp:1
+# expect: Parse_PushInput: variable-if-parenthesized.tmp:1
+
+# A conditional other than '.if' or '.ifndef' does not guard the file, even if
+# it is otherwise equivalent to the above accepted forms.
+CASES+= variable-ifdef-negated
+LINES.variable-ifdef-negated= \
+ '.ifdef !VARIABLE_IFDEF_NEGATED' \
+ 'VARIABLE_IFDEF_NEGATED=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifdef-negated.tmp:1
+# expect: Parse_PushInput: variable-ifdef-negated.tmp:1
+
+# The variable names in the '.if' and the assignment must be the same.
+CASES+= variable-name-mismatch
+LINES.variable-name-mismatch= \
+ '.ifndef VARIABLE_NAME_MISMATCH' \
+ 'VARIABLE_NAME_DIFFERENT=' \
+ '.endif'
+# expect: Parse_PushInput: variable-name-mismatch.tmp:1
+# expect: Parse_PushInput: variable-name-mismatch.tmp:1
+
+# If the guard variable condition is enclosed in parentheses, it does not have
+# an effect, as that form is not common in practice.
+CASES+= variable-ifndef-parenthesized
+LINES.variable-ifndef-parenthesized= \
+ '.ifndef (VARIABLE_IFNDEF_PARENTHESIZED)' \
+ 'VARIABLE_IFNDEF_PARENTHESIZED=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-parenthesized.tmp:1
+# expect: Parse_PushInput: variable-ifndef-parenthesized.tmp:1
+
+# The variable name '!VARNAME' cannot be used in an '.ifndef' directive, as
+# the '!' would be a negation. It is syntactically valid in a '.if !defined'
+# condition, but this case is so uncommon that the guard mechanism doesn't
+# accept '!' in the guard variable name. Furthermore, when defining the
+# variable, the character '!' has to be escaped, to prevent it from being
+# interpreted as the '!' dependency operator.
+CASES+= variable-name-exclamation
+LINES.variable-name-exclamation= \
+ '.if !defined(!VARIABLE_NAME_EXCLAMATION)' \
+ '${:U!}VARIABLE_NAME_EXCLAMATION=' \
+ '.endif'
+# expect: Parse_PushInput: variable-name-exclamation.tmp:1
+# expect: Parse_PushInput: variable-name-exclamation.tmp:1
+
+# In general, a variable name can contain a '!' in the middle, as that
+# character is interpreted as an ordinary character in conditions as well as
+# on the left side of a variable assignment. For guard variable names, the
+# '!' is not supported in any place, though.
+CASES+= variable-name-exclamation-middle
+LINES.variable-name-exclamation-middle= \
+ '.ifndef VARIABLE_NAME!MIDDLE' \
+ 'VARIABLE_NAME!MIDDLE=' \
+ '.endif'
+# expect: Parse_PushInput: variable-name-exclamation-middle.tmp:1
+# expect: Parse_PushInput: variable-name-exclamation-middle.tmp:1
+
+# A variable name can contain balanced parentheses, at least in conditions and
+# on the left side of a variable assignment. There are enough places in make
+# where parentheses or braces are handled inconsistently to make this naming
+# choice a bad idea, therefore these characters are not allowed in guard
+# variable names.
+CASES+= variable-name-parentheses
+LINES.variable-name-parentheses= \
+ '.ifndef VARIABLE_NAME(&)PARENTHESES' \
+ 'VARIABLE_NAME(&)PARENTHESES=' \
+ '.endif'
+# expect: Parse_PushInput: variable-name-parentheses.tmp:1
+# expect: Parse_PushInput: variable-name-parentheses.tmp:1
+
+# The guard condition must consist of only the guard variable, nothing else.
+CASES+= variable-ifndef-plus
+LINES.variable-ifndef-plus= \
+ '.ifndef VARIABLE_IFNDEF_PLUS && VARIABLE_IFNDEF_SECOND' \
+ 'VARIABLE_IFNDEF_PLUS=' \
+ 'VARIABLE_IFNDEF_SECOND=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-plus.tmp:1
+# expect: Parse_PushInput: variable-ifndef-plus.tmp:1
+
+# The guard condition must consist of only the guard variable, nothing else.
+CASES+= variable-if-plus
+LINES.variable-if-plus= \
+ '.if !defined(VARIABLE_IF_PLUS) && !defined(VARIABLE_IF_SECOND)' \
+ 'VARIABLE_IF_PLUS=' \
+ 'VARIABLE_IF_SECOND=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-plus.tmp:1
+# expect: Parse_PushInput: variable-if-plus.tmp:1
+
+# The variable name in an '.ifndef' guard must be given directly, it must not
+# contain any '$' expression.
+CASES+= variable-ifndef-indirect
+LINES.variable-ifndef-indirect= \
+ '.ifndef $${VARIABLE_IFNDEF_INDIRECT:L}' \
+ 'VARIABLE_IFNDEF_INDIRECT=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-indirect.tmp:1
+# expect: Parse_PushInput: variable-ifndef-indirect.tmp:1
+
+# The variable name in an '.if' guard must be given directly, it must not
+# contain any '$' expression.
+CASES+= variable-if-indirect
+LINES.variable-if-indirect= \
+ '.if !defined($${VARIABLE_IF_INDIRECT:L})' \
+ 'VARIABLE_IF_INDIRECT=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-indirect.tmp:1
+# expect: Parse_PushInput: variable-if-indirect.tmp:1
+
+# The variable name in the guard condition must only contain alphanumeric
+# characters and underscores. The place where the guard variable is defined
+# is more flexible, as long as the variable is defined at the point where the
+# file is included the next time.
+CASES+= variable-assign-indirect
+LINES.variable-assign-indirect= \
+ '.ifndef VARIABLE_ASSIGN_INDIRECT' \
+ '$${VARIABLE_ASSIGN_INDIRECT:L}=' \
+ '.endif'
+# expect: Parse_PushInput: variable-assign-indirect.tmp:1
+# expect: Skipping 'variable-assign-indirect.tmp' because 'VARIABLE_ASSIGN_INDIRECT' is defined
+
+# The time at which the guard variable is defined doesn't matter, as long as
+# it is defined at the point where the file is included the next time.
+CASES+= variable-assign-late
+LINES.variable-assign-late= \
+ '.ifndef VARIABLE_ASSIGN_LATE' \
+ 'VARIABLE_ASSIGN_LATE_OTHER=' \
+ 'VARIABLE_ASSIGN_LATE=' \
+ '.endif'
+# expect: Parse_PushInput: variable-assign-late.tmp:1
+# expect: Skipping 'variable-assign-late.tmp' because 'VARIABLE_ASSIGN_LATE' is defined
+
+# The time at which the guard variable is defined doesn't matter, as long as
+# it is defined at the point where the file is included the next time.
+CASES+= variable-assign-nested
+LINES.variable-assign-nested= \
+ '.ifndef VARIABLE_ASSIGN_NESTED' \
+ '. if 1' \
+ '. for i in once' \
+ 'VARIABLE_ASSIGN_NESTED=' \
+ '. endfor' \
+ '. endif' \
+ '.endif'
+# expect: Parse_PushInput: variable-assign-nested.tmp:1
+# expect: Skipping 'variable-assign-nested.tmp' because 'VARIABLE_ASSIGN_NESTED' is defined
+
+# If the guard variable is defined before the file is included for the first
+# time, the file is considered guarded as well. In such a case, the parser
+# skips almost all lines, as they are irrelevant, but the structure of the
+# top-level '.if/.endif' conditional can be determined reliably enough to
+# decide whether the file is guarded.
+CASES+= variable-already-defined
+LINES.variable-already-defined= \
+ '.ifndef VARIABLE_ALREADY_DEFINED' \
+ 'VARIABLE_ALREADY_DEFINED=' \
+ '.endif'
+VARIABLE_ALREADY_DEFINED=
+# expect: Parse_PushInput: variable-already-defined.tmp:1
+# expect: Skipping 'variable-already-defined.tmp' because 'VARIABLE_ALREADY_DEFINED' is defined
+
+# If the guard variable is defined before the file is included the first time,
+# the file is processed but its content is skipped. If that same guard
+# variable is undefined when the file is included the second time, the file is
+# processed as usual.
+CASES+= variable-defined-then-undefined
+LINES.variable-defined-then-undefined= \
+ '.ifndef VARIABLE_DEFINED_THEN_UNDEFINED' \
+ '.endif'
+VARIABLE_DEFINED_THEN_UNDEFINED=
+UNDEF_BETWEEN.variable-defined-then-undefined= \
+ VARIABLE_DEFINED_THEN_UNDEFINED
+# expect: Parse_PushInput: variable-defined-then-undefined.tmp:1
+# expect: Parse_PushInput: variable-defined-then-undefined.tmp:1
+
+# The whole file content must be guarded by a single '.if' conditional, not by
+# several, as each of these conditionals would require its separate guard.
+# This case is not expected to occur in practice, as the two parts would
+# rather be split into separate files.
+CASES+= variable-two-times
+LINES.variable-two-times= \
+ '.ifndef VARIABLE_TWO_TIMES_1' \
+ 'VARIABLE_TWO_TIMES_1=' \
+ '.endif' \
+ '.ifndef VARIABLE_TWO_TIMES_2' \
+ 'VARIABLE_TWO_TIMES_2=' \
+ '.endif'
+# expect: Parse_PushInput: variable-two-times.tmp:1
+# expect: Parse_PushInput: variable-two-times.tmp:1
+
+# When multiple files use the same guard variable name, the optimization of
+# skipping the file affects each of these files.
+#
+# Choosing unique guard names is the responsibility of the makefile authors.
+# A typical pattern of guard variable names is '${PROJECT}_${DIR}_${FILE}_MK'.
+# System-provided files typically start the guard names with '_'.
+CASES+= variable-clash
+LINES.variable-clash= \
+ ${LINES.variable-if}
+# expect: Parse_PushInput: variable-clash.tmp:1
+# expect: Skipping 'variable-clash.tmp' because 'VARIABLE_IF' is defined
+
+# The conditional must come before the assignment, otherwise the conditional
+# is useless, as it always evaluates to false.
+CASES+= variable-swapped
+LINES.variable-swapped= \
+ 'SWAPPED=' \
+ '.ifndef SWAPPED' \
+ '. error' \
+ '.endif'
+# expect: Parse_PushInput: variable-swapped.tmp:1
+# expect: Parse_PushInput: variable-swapped.tmp:1
+
+# If the guard variable is undefined between the first and the second time the
+# file is included, the guarded file is included again.
+CASES+= variable-undef-between
+LINES.variable-undef-between= \
+ '.ifndef VARIABLE_UNDEF_BETWEEN' \
+ 'VARIABLE_UNDEF_BETWEEN=' \
+ '.endif'
+UNDEF_BETWEEN.variable-undef-between= \
+ VARIABLE_UNDEF_BETWEEN
+# expect: Parse_PushInput: variable-undef-between.tmp:1
+# expect: Parse_PushInput: variable-undef-between.tmp:1
+
+# If the guard variable is undefined while the file is included the first
+# time, the guard does not have an effect, and the file is included again.
+CASES+= variable-undef-inside
+LINES.variable-undef-inside= \
+ '.ifndef VARIABLE_UNDEF_INSIDE' \
+ 'VARIABLE_UNDEF_INSIDE=' \
+ '.undef VARIABLE_UNDEF_INSIDE' \
+ '.endif'
+# expect: Parse_PushInput: variable-undef-inside.tmp:1
+# expect: Parse_PushInput: variable-undef-inside.tmp:1
+
+# If the file does not define the guard variable, the guard does not have an
+# effect, and the file is included again.
+CASES+= variable-not-defined
+LINES.variable-not-defined= \
+ '.ifndef VARIABLE_NOT_DEFINED' \
+ '.endif'
+# expect: Parse_PushInput: variable-not-defined.tmp:1
+# expect: Parse_PushInput: variable-not-defined.tmp:1
+
+# The outermost '.if' must not have an '.elif' branch.
+CASES+= elif
+LINES.elif= \
+ '.ifndef ELIF' \
+ 'ELIF=' \
+ '.elif 1' \
+ '.endif'
+# expect: Parse_PushInput: elif.tmp:1
+# expect: Parse_PushInput: elif.tmp:1
+
+# When a file with an '.if/.elif/.endif' conditional at the top level is
+# included, it is never optimized, as one of its branches is taken.
+CASES+= elif-reuse
+LINES.elif-reuse= \
+ '.ifndef ELIF' \
+ 'syntax error' \
+ '.elif 1' \
+ '.endif'
+# expect: Parse_PushInput: elif-reuse.tmp:1
+# expect: Parse_PushInput: elif-reuse.tmp:1
+
+# The outermost '.if' must not have an '.else' branch.
+CASES+= else
+LINES.else= \
+ '.ifndef ELSE' \
+ 'ELSE=' \
+ '.else' \
+ '.endif'
+# expect: Parse_PushInput: else.tmp:1
+# expect: Parse_PushInput: else.tmp:1
+
+# When a file with an '.if/.else/.endif' conditional at the top level is
+# included, it is never optimized, as one of its branches is taken.
+CASES+= else-reuse
+LINES.else-reuse= \
+ '.ifndef ELSE' \
+ 'syntax error' \
+ '.else' \
+ '.endif'
+# expect: Parse_PushInput: else-reuse.tmp:1
+# expect: Parse_PushInput: else-reuse.tmp:1
+
+# The inner '.if' directives may have an '.elif' or '.else', and it doesn't
+# matter which of their branches are taken.
+CASES+= inner-if-elif-else
+LINES.inner-if-elif-else= \
+ '.ifndef INNER_IF_ELIF_ELSE' \
+ 'INNER_IF_ELIF_ELSE=' \
+ '. if 0' \
+ '. elif 0' \
+ '. else' \
+ '. endif' \
+ '. if 0' \
+ '. elif 1' \
+ '. else' \
+ '. endif' \
+ '. if 1' \
+ '. elif 1' \
+ '. else' \
+ '. endif' \
+ '.endif'
+# expect: Parse_PushInput: inner-if-elif-else.tmp:1
+# expect: Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is defined
+
+# The guard can also be a target instead of a variable. Using a target as a
+# guard has the benefit that a target cannot be undefined once it is defined.
+# The target should be declared '.NOTMAIN'. Since the target names are
+# usually chosen according to a pattern that doesn't interfere with real
+# target names, they don't need to be declared '.PHONY' as they don't generate
+# filesystem operations.
+CASES+= target
+LINES.target= \
+ '.if !target(__target.tmp__)' \
+ '__target.tmp__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target.tmp:1
+# expect: Skipping 'target.tmp' because '__target.tmp__' is defined
+
+# When used for system files, the target name may include '<' and '>', for
+# symmetry with the '.include <sys.mk>' directive. The characters '<' and '>'
+# are ordinary characters.
+CASES+= target-sys
+LINES.target-sys= \
+ '.if !target(__<target-sys.tmp>__)' \
+ '__<target-sys.tmp>__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-sys.tmp:1
+# expect: Skipping 'target-sys.tmp' because '__<target-sys.tmp>__' is defined
+
+# The target name may include variable references. These references are
+# expanded as usual. Due to the current implementation, the expressions are
+# evaluated twice: Once for checking whether the condition evaluates to true,
+# and once for determining the guard name. This double evaluation should not
+# matter in practice, as guard expressions are expected to be simple,
+# deterministic and without side effects.
+CASES+= target-indirect
+LINES.target-indirect= \
+ '.if !target($${target-indirect.tmp:L})' \
+ 'target-indirect.tmp: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-indirect.tmp:1
+# expect: Skipping 'target-indirect.tmp' because 'target-indirect.tmp' is defined
+
+# A common form of guard target is __${.PARSEFILE}__. This form can only be
+# used if all files using this form have unique basenames. To get a robust
+# pattern based on the same idea, use __${.PARSEDIR}/${.PARSEFILE}__ instead.
+# This form does not work when the basename contains whitespace characters, as
+# it is not possible to define a target with whitespace, not even by cheating.
+CASES+= target-indirect-PARSEFILE
+LINES.target-indirect-PARSEFILE= \
+ '.if !target(__$${.PARSEFILE}__)' \
+ '__$${.PARSEFILE}__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-indirect-PARSEFILE.tmp:1
+# expect: Skipping 'target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined
+
+# Two files with different basenames can both use the same syntactic pattern
+# for the target guard name, as the expressions expand to different strings.
+CASES+= target-indirect-PARSEFILE2
+LINES.target-indirect-PARSEFILE2= \
+ '.if !target(__$${.PARSEFILE}__)' \
+ '__$${.PARSEFILE}__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-indirect-PARSEFILE2.tmp:1
+# expect: Skipping 'target-indirect-PARSEFILE2.tmp' because '__target-indirect-PARSEFILE2.tmp__' is defined
+
+# Using plain .PARSEFILE without .PARSEDIR leads to name clashes. The include
+# guard is the same as in the test case 'target-indirect-PARSEFILE', as the
+# guard name only contains the basename but not the directory name. So even
+# without defining the guard target, the file is considered guarded.
+CASES+= subdir/target-indirect-PARSEFILE
+LINES.subdir/target-indirect-PARSEFILE= \
+ '.if !target(__$${.PARSEFILE}__)' \
+ '.endif'
+# expect: Parse_PushInput: subdir/target-indirect-PARSEFILE.tmp:1
+# expect: Skipping 'subdir/target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined
+
+# Another common form of guard target is __${.PARSEDIR}/${.PARSEFILE}__
+# or __${.PARSEDIR:tA}/${.PARSEFILE}__ to be truly unique.
+CASES+= target-indirect-PARSEDIR-PARSEFILE
+LINES.target-indirect-PARSEDIR-PARSEFILE= \
+ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \
+ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-indirect-PARSEDIR-PARSEFILE.tmp:1
+# expect: Skipping 'target-indirect-PARSEDIR-PARSEFILE.tmp' because '__target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined
+# The actual target starts with '__${.OBJDIR}/', see the .rawout file, but the
+# string '${.OBJDIR}/' gets stripped in post processing.
+
+# Using the combination of '.PARSEDIR' and '.PARSEFILE', a file in a
+# subdirectory gets a different guard target name than the previous one.
+CASES+= subdir/target-indirect-PARSEDIR-PARSEFILE
+LINES.subdir/target-indirect-PARSEDIR-PARSEFILE= \
+ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \
+ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: subdir/target-indirect-PARSEDIR-PARSEFILE.tmp:1
+# expect: Skipping 'subdir/target-indirect-PARSEDIR-PARSEFILE.tmp' because '__subdir/target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined
+# The actual target starts with '__${.OBJDIR}/', see the .rawout file, but the
+# string '${.OBJDIR}/' gets stripped in post processing.
+
+# If the guard target is not defined when including the file the next time,
+# the file is processed again.
+CASES+= target-unguarded
+LINES.target-unguarded= \
+ '.if !target(target-unguarded)' \
+ '.endif'
+# expect: Parse_PushInput: target-unguarded.tmp:1
+# expect: Parse_PushInput: target-unguarded.tmp:1
+
+# The guard condition must consist of only the guard target, nothing else.
+CASES+= target-plus
+LINES.target-plus= \
+ '.if !target(target-plus) && 1' \
+ 'target-plus: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-plus.tmp:1
+# expect: Parse_PushInput: target-plus.tmp:1
+
+# If the guard target is defined before the file is included the first time,
+# the file is read once and then considered guarded.
+CASES+= target-already-defined
+LINES.target-already-defined= \
+ '.if !target(target-already-defined)' \
+ 'target-already-defined: .NOTMAIN' \
+ '.endif'
+target-already-defined: .NOTMAIN
+# expect: Parse_PushInput: target-already-defined.tmp:1
+# expect: Skipping 'target-already-defined.tmp' because 'target-already-defined' is defined
+
+# A target name cannot contain the character '!'. In the condition, the '!'
+# is syntactically valid, but in the dependency declaration line, the '!' is
+# interpreted as the '!' dependency operator, no matter whether it occurs at
+# the beginning or in the middle of a target name. Escaping it as '${:U!}'
+# doesn't work, as the whole line is first expanded and then scanned for the
+# dependency operator. Escaping it as '\!' doesn't work either, even though
+# the '\' escapes the '!' from being a dependency operator, but when reading
+# the target name, the '\' is kept, resulting in the target name
+# '\!target-name-exclamation' instead of '!target-name-exclamation'.
+CASES+= target-name-exclamation
+LINES.target-name-exclamation= \
+ '.if !target(!target-name-exclamation)' \
+ '\!target-name-exclamation: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-name-exclamation.tmp:1
+# expect: Parse_PushInput: target-name-exclamation.tmp:1
+
+# If the guard target name has leading spaces, it does not have an effect,
+# as that form is not common in practice.
+CASES+= target-name-leading-space
+LINES.target-name-leading-space= \
+ '.if !target( target-name-leading-space)' \
+ 'target-name-leading-space: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-name-leading-space.tmp:1
+# expect: Parse_PushInput: target-name-leading-space.tmp:1
+
+# If the guard target name has trailing spaces, it does not have an effect,
+# as that form is not common in practice.
+CASES+= target-name-trailing-space
+LINES.target-name-trailing-space= \
+ '.if !target(target-name-trailing-space )' \
+ 'target-name-trailing-space: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-name-trailing-space.tmp:1
+# expect: Parse_PushInput: target-name-trailing-space.tmp:1
+
+# If the guard target condition is enclosed in parentheses, it does not have
+# an effect, as that form is not common in practice.
+CASES+= target-call-parenthesized
+LINES.target-call-parenthesized= \
+ '.if (!target(target-call-parenthesized))' \
+ 'target-call-parenthesized: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-call-parenthesized.tmp:1
+# expect: Parse_PushInput: target-call-parenthesized.tmp:1
+
+# If the '.if' or '.ifndef' directive spans more than a single line, it is
+# still recognized as a guard condition. This case is entirely uncommon, but
+# at the point where the guard condition is checked, line continuations have
+# already been converted to spaces.
+CASES+= multiline
+LINES.multiline= \
+ '.\' \
+ ' ifndef \' \
+ ' MULTILINE' \
+ 'MULTILINE=' \
+ '.endif'
+# expect: Parse_PushInput: multiline.tmp:1
+# expect: Skipping 'multiline.tmp' because 'MULTILINE' is defined
+
+
+# Now run all test cases by including each of the files twice and looking at
+# the debug output. The files that properly guard against multiple inclusion
+# generate a 'Skipping' line, the others repeat the 'Parse_PushInput' line.
+#
+# Some debug output lines are suppressed in the .exp file, see ./Makefile.
+.for i in ${CASES}
+. for fname in $i.tmp
+_:= ${fname:H:N.:@dir@${:!mkdir -p ${dir}!}@}
+_!= printf '%s\n' ${LINES.$i} > ${fname}
+.MAKEFLAGS: -dp
+.include "${.CURDIR}/${fname}"
+.undef ${UNDEF_BETWEEN.$i:U}
+.include "${.CURDIR}/${fname}"
+.MAKEFLAGS: -d0
+_!= rm ${fname}
+_:= ${fname:H:N.:@dir@${:!rmdir ${dir}!}@}
+. endfor
+.endfor
+
+all: