# $NetBSD: directive-include-guard.mk,v 1.11 2023/06/21 21:21:52 sjg 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, including the file has no effect, # as all its content is skipped. # # 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. INCS+= variable-ifndef LINES.variable-ifndef= \ '.ifndef VARIABLE_IFNDEF' \ 'VARIABLE_IFNDEF=' \ '.endif' # expect: Parse_PushInput: file variable-ifndef.tmp, line 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. INCS+= variable-ifndef-reuse LINES.variable-ifndef-reuse= \ '.ifndef VARIABLE_IFNDEF' \ 'syntax error' \ '.endif' # expect: Parse_PushInput: file variable-ifndef-reuse.tmp, line 1 # expect: Skipping 'variable-ifndef-reuse.tmp' because 'VARIABLE_IFNDEF' is defined # Comments and empty lines do not affect the multiple-inclusion guard. INCS+= comments LINES.comments= \ '\# comment' \ '' \ '.ifndef COMMENTS' \ '\# comment' \ 'COMMENTS=\#comment' \ '.endif' \ '\# comment' # expect: Parse_PushInput: file comments.tmp, line 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. INCS+= variable-if LINES.variable-if= \ '.if !defined(VARIABLE_IF)' \ 'VARIABLE_IF=' \ '.endif' # expect: Parse_PushInput: file variable-if.tmp, line 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. INCS+= variable-if-reuse LINES.variable-if-reuse= \ '.if !defined(VARIABLE_IF)' \ 'syntax error' \ '.endif' # expect: Parse_PushInput: file variable-if-reuse.tmp, line 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. INCS+= variable-if-triple-negation LINES.variable-if-triple-negation= \ '.if !!!defined(VARIABLE_IF_TRIPLE_NEGATION)' \ 'VARIABLE_IF_TRIPLE_NEGATION=' \ '.endif' # expect: Parse_PushInput: file variable-if-triple-negation.tmp, line 1 # expect: Parse_PushInput: file variable-if-triple-negation.tmp, line 1 # A conditional other than '.if' or '.ifndef' does not guard the file, even if # it is otherwise equivalent to the above accepted forms. INCS+= variable-ifdef-negated LINES.variable-ifdef-negated= \ '.ifdef !VARIABLE_IFDEF_NEGATED' \ 'VARIABLE_IFDEF_NEGATED=' \ '.endif' # expect: Parse_PushInput: file variable-ifdef-negated.tmp, line 1 # expect: Parse_PushInput: file variable-ifdef-negated.tmp, line 1 # The variable names in the '.if' and the assignment must be the same. INCS+= variable-name-mismatch LINES.variable-name-mismatch= \ '.ifndef VARIABLE_NAME_MISMATCH' \ 'VARIABLE_NAME_DIFFERENT=' \ '.endif' # expect: Parse_PushInput: file variable-name-mismatch.tmp, line 1 # expect: Parse_PushInput: file variable-name-mismatch.tmp, line 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 ignored there. Furthermore, when defining the variable, the # character '!' has to be escaped, to prevent it from being interpreted as the # '!' dependency operator. INCS+= variable-name-exclamation LINES.variable-name-exclamation= \ '.if !defined(!VARIABLE_NAME_EXCLAMATION)' \ '${:U!}VARIABLE_NAME_EXCLAMATION=' \ '.endif' # expect: Parse_PushInput: file variable-name-exclamation.tmp, line 1 # expect: Parse_PushInput: file variable-name-exclamation.tmp, line 1 # 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. INCS+= variable-name-exclamation-middle LINES.variable-name-exclamation-middle= \ '.ifndef VARIABLE_NAME!MIDDLE' \ 'VARIABLE_NAME!MIDDLE=' \ '.endif' # expect: Parse_PushInput: file variable-name-exclamation-middle.tmp, line 1 # expect: Parse_PushInput: file variable-name-exclamation-middle.tmp, line 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. INCS+= variable-name-parentheses LINES.variable-name-parentheses= \ '.ifndef VARIABLE_NAME(&)PARENTHESES' \ 'VARIABLE_NAME(&)PARENTHESES=' \ '.endif' # expect: Parse_PushInput: file variable-name-parentheses.tmp, line 1 # expect: Parse_PushInput: file variable-name-parentheses.tmp, line 1 # The guard condition must consist of only the guard variable, nothing else. INCS+= variable-ifndef-plus LINES.variable-ifndef-plus= \ '.ifndef VARIABLE_IFNDEF_PLUS && VARIABLE_IFNDEF_SECOND' \ 'VARIABLE_IFNDEF_PLUS=' \ 'VARIABLE_IFNDEF_SECOND=' \ '.endif' # expect: Parse_PushInput: file variable-ifndef-plus.tmp, line 1 # expect: Parse_PushInput: file variable-ifndef-plus.tmp, line 1 # The guard condition must consist of only the guard variable, nothing else. INCS+= 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: file variable-if-plus.tmp, line 1 # expect: Parse_PushInput: file variable-if-plus.tmp, line 1 # The variable name in an '.ifndef' guard must be given directly, it must not # contain any '$' expression. INCS+= variable-ifndef-indirect LINES.variable-ifndef-indirect= \ '.ifndef $${VARIABLE_IFNDEF_INDIRECT:L}' \ 'VARIABLE_IFNDEF_INDIRECT=' \ '.endif' # expect: Parse_PushInput: file variable-ifndef-indirect.tmp, line 1 # expect: Parse_PushInput: file variable-ifndef-indirect.tmp, line 1 # The variable name in an '.if' guard must be given directly, it must not # contain any '$' expression. INCS+= variable-if-indirect LINES.variable-if-indirect= \ '.if !defined($${VARIABLE_IF_INDIRECT:L})' \ 'VARIABLE_IF_INDIRECT=' \ '.endif' # expect: Parse_PushInput: file variable-if-indirect.tmp, line 1 # expect: Parse_PushInput: file variable-if-indirect.tmp, line 1 # The variable name in the guard condition must only contain alphanumeric # characters and underscores. The guard variable is more flexible, it can be # defined anywhere, as long as it is defined at the point where the file is # included the next time. INCS+= variable-assign-indirect LINES.variable-assign-indirect= \ '.ifndef VARIABLE_ASSIGN_INDIRECT' \ '$${VARIABLE_ASSIGN_INDIRECT:L}=' \ '.endif' # expect: Parse_PushInput: file variable-assign-indirect.tmp, line 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. INCS+= variable-assign-late LINES.variable-assign-late= \ '.ifndef VARIABLE_ASSIGN_LATE' \ 'VARIABLE_ASSIGN_LATE_OTHER=' \ 'VARIABLE_ASSIGN_LATE=' \ '.endif' # expect: Parse_PushInput: file variable-assign-late.tmp, line 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. INCS+= 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: file variable-assign-nested.tmp, line 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. INCS+= variable-already-defined LINES.variable-already-defined= \ '.ifndef VARIABLE_ALREADY_DEFINED' \ 'VARIABLE_ALREADY_DEFINED=' \ '.endif' VARIABLE_ALREADY_DEFINED= # expect: Parse_PushInput: file variable-already-defined.tmp, line 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. INCS+= 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: file variable-defined-then-undefined.tmp, line 1 # expect: Parse_PushInput: file variable-defined-then-undefined.tmp, line 1 # The whole file content must be guarded by a single '.if' conditional, not by # several, even if they have the same effect. This case is not expected to # occur in practice, as the two parts would rather be split into separate # files. INCS+= 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: file variable-two-times.tmp, line 1 # expect: Parse_PushInput: file variable-two-times.tmp, line 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 '_'. INCS+= variable-clash LINES.variable-clash= \ ${LINES.variable-if} # expect: Parse_PushInput: file variable-clash.tmp, line 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. INCS+= variable-swapped LINES.variable-swapped= \ 'SWAPPED=' \ '.ifndef SWAPPED' \ '. error' \ '.endif' # expect: Parse_PushInput: file variable-swapped.tmp, line 1 # expect: Parse_PushInput: file variable-swapped.tmp, line 1 # If the guard variable is undefined between the first and the second time the # file is included, the guarded file is included again. INCS+= 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: file variable-undef-between.tmp, line 1 # expect: Parse_PushInput: file variable-undef-between.tmp, line 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. INCS+= variable-undef-inside LINES.variable-undef-inside= \ '.ifndef VARIABLE_UNDEF_INSIDE' \ 'VARIABLE_UNDEF_INSIDE=' \ '.undef VARIABLE_UNDEF_INSIDE' \ '.endif' # expect: Parse_PushInput: file variable-undef-inside.tmp, line 1 # expect: Parse_PushInput: file variable-undef-inside.tmp, line 1 # If the file does not define the guard variable, the guard does not have an # effect, and the file is included again. INCS+= variable-not-defined LINES.variable-not-defined= \ '.ifndef VARIABLE_NOT_DEFINED' \ '.endif' # expect: Parse_PushInput: file variable-not-defined.tmp, line 1 # expect: Parse_PushInput: file variable-not-defined.tmp, line 1 # The outermost '.if' must not have an '.elif' branch. INCS+= if-elif LINES.if-elif= \ '.ifndef IF_ELIF' \ 'IF_ELIF=' \ '.elif 1' \ '.endif' # expect: Parse_PushInput: file if-elif.tmp, line 1 # expect: Parse_PushInput: file if-elif.tmp, line 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. INCS+= if-elif-reuse LINES.if-elif-reuse= \ '.ifndef IF_ELIF' \ 'syntax error' \ '.elif 1' \ '.endif' # expect: Parse_PushInput: file if-elif-reuse.tmp, line 1 # expect: Parse_PushInput: file if-elif-reuse.tmp, line 1 # The outermost '.if' must not have an '.else' branch. INCS+= if-else LINES.if-else= \ '.ifndef IF_ELSE' \ 'IF_ELSE=' \ '.else' \ '.endif' # expect: Parse_PushInput: file if-else.tmp, line 1 # expect: Parse_PushInput: file if-else.tmp, line 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. INCS+= if-else-reuse LINES.if-else-reuse= \ '.ifndef IF_ELSE' \ 'syntax error' \ '.else' \ '.endif' # expect: Parse_PushInput: file if-else-reuse.tmp, line 1 # expect: Parse_PushInput: file if-else-reuse.tmp, line 1 # The inner '.if' directives may have an '.elif' or '.else', and it doesn't # matter which of their branches are taken. INCS+= 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: file inner-if-elif-else.tmp, line 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. INCS+= target LINES.target= \ '.if !target(__target.tmp__)' \ '__target.tmp__: .NOTMAIN' \ '.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 '>', for # symmetry with the '.include ' directive. The characters '<' and '>' # are ordinary characters. INCS+= target-sys LINES.target-sys= \ '.if !target(____)' \ '____: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-sys.tmp, line 1 # expect: Skipping 'target-sys.tmp' because '____' 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. INCS+= target-indirect LINES.target-indirect= \ '.if !target($${target-indirect.tmp:L})' \ 'target-indirect.tmp: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-indirect.tmp, line 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. INCS+= target-indirect-PARSEFILE LINES.target-indirect-PARSEFILE= \ '.if !target(__$${.PARSEFILE}__)' \ '__$${.PARSEFILE}__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-indirect-PARSEFILE.tmp, line 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. INCS+= target-indirect-PARSEFILE2 LINES.target-indirect-PARSEFILE2= \ '.if !target(__$${.PARSEFILE}__)' \ '__$${.PARSEFILE}__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-indirect-PARSEFILE2.tmp, line 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. INCS+= subdir/target-indirect-PARSEFILE LINES.subdir/target-indirect-PARSEFILE= \ '.if !target(__$${.PARSEFILE}__)' \ '__$${.PARSEFILE}__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file subdir/target-indirect-PARSEFILE.tmp, line 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 truely unique. INCS+= target-indirect-PARSEDIR-PARSEFILE LINES.target-indirect-PARSEDIR-PARSEFILE= \ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-indirect-PARSEDIR-PARSEFILE.tmp, line 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. INCS+= subdir/target-indirect-PARSEDIR-PARSEFILE LINES.subdir/target-indirect-PARSEDIR-PARSEFILE= \ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file subdir/target-indirect-PARSEDIR-PARSEFILE.tmp, line 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. 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: .NOTMAIN' \ '.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 the first time, # the file is not considered guarded. INCS+= target-already-defined LINES.target-already-defined= \ '.if !target(target-already-defined)' \ 'target-already-defined: .NOTMAIN' \ '.endif' target-already-defined: .NOTMAIN # expect: Parse_PushInput: file target-already-defined.tmp, line 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'. INCS+= target-name-exclamation LINES.target-name-exclamation= \ '.if !target(!target-name-exclamation)' \ '\!target-name-exclamation: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-name-exclamation.tmp, line 1 # expect: Parse_PushInput: file target-name-exclamation.tmp, line 1 # 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 ${INCS} . 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: