Home | History | Annotate | Line # | Download | only in unit-tests
directive-include-guard.mk revision 1.7
      1 # $NetBSD: directive-include-guard.mk,v 1.7 2023/06/20 09:25:34 rillig Exp $
      2 #
      3 # Tests for multiple-inclusion guards in makefiles.
      4 #
      5 # A file that is guarded by a multiple-inclusion guard has one of the
      6 # following forms:
      7 #
      8 #	.ifndef GUARD_VARIABLE
      9 #	.endif
     10 #
     11 #	.if !defined(GUARD_VARIABLE)
     12 #	.endif
     13 #
     14 #	.if !target(guard-target)
     15 #	.endif
     16 #
     17 # When such a file is included for the second or later time, and the guard
     18 # variable is set or the guard target defined, including the file has no
     19 # effect, as all its content is skipped.
     20 #
     21 # See also:
     22 #	https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
     23 
     24 
     25 # This is the canonical form of a multiple-inclusion guard.
     26 INCS+=	guarded-ifndef
     27 LINES.guarded-ifndef= \
     28 	'.ifndef GUARDED_IFNDEF' \
     29 	'GUARDED_IFNDEF=' \
     30 	'.endif'
     31 # expect: Parse_PushInput: file guarded-ifndef.tmp, line 1
     32 # expect: Skipping 'guarded-ifndef.tmp' because 'GUARDED_IFNDEF' is defined
     33 
     34 # Comments and empty lines have no influence on the multiple-inclusion guard.
     35 INCS+=	comments
     36 LINES.comments= \
     37 	'\# comment' \
     38 	'' \
     39 	'.ifndef COMMENTS' \
     40 	'\# comment' \
     41 	'COMMENTS=\#comment' \
     42 	'.endif' \
     43 	'\# comment'
     44 # expect: Parse_PushInput: file comments.tmp, line 1
     45 # expect: Skipping 'comments.tmp' because 'COMMENTS' is defined
     46 
     47 # An alternative form uses the 'defined' function.  It is more verbose than
     48 # the canonical form.  There are other possible forms as well, such as with a
     49 # triple negation, but these are not recognized as they are not common.
     50 INCS+=	guarded-if
     51 LINES.guarded-if= \
     52 	'.if !defined(GUARDED_IF)' \
     53 	'GUARDED_IF=' \
     54 	'.endif'
     55 # expect: Parse_PushInput: file guarded-if.tmp, line 1
     56 # expect: Skipping 'guarded-if.tmp' because 'GUARDED_IF' is defined
     57 
     58 # Triple negation is so uncommon that it's not recognized.
     59 INCS+=	triple-negation
     60 LINES.triple-negation= \
     61 	'.if !!!defined(TRIPLE_NEGATION)' \
     62 	'TRIPLE_NEGATION=' \
     63 	'.endif'
     64 # expect: Parse_PushInput: file triple-negation.tmp, line 1
     65 # expect: Parse_PushInput: file triple-negation.tmp, line 1
     66 
     67 # A conditional other than '.if' or '.ifndef' marks the file as non-guarded,
     68 # even if it would actually work as a multiple-inclusion guard.
     69 INCS+=	ifdef-negated
     70 LINES.ifdef-negated= \
     71 	'.ifdef !IFDEF_NEGATED' \
     72 	'IFDEF_NEGATED=' \
     73 	'.endif'
     74 # expect: Parse_PushInput: file ifdef-negated.tmp, line 1
     75 # expect: Parse_PushInput: file ifdef-negated.tmp, line 1
     76 
     77 # The variable names in the '.if' and the assignment must be the same.
     78 INCS+=	varname-mismatch
     79 LINES.varname-mismatch= \
     80 	'.ifndef VARNAME_MISMATCH' \
     81 	'OTHER_NAME=' \
     82 	'.endif'
     83 # expect: Parse_PushInput: file varname-mismatch.tmp, line 1
     84 # expect: Parse_PushInput: file varname-mismatch.tmp, line 1
     85 
     86 # The guard condition must consist of only the guard variable, nothing else.
     87 INCS+=	ifndef-plus
     88 LINES.ifndef-plus= \
     89 	'.ifndef IFNDEF_PLUS && IFNDEF_SECOND' \
     90 	'IFNDEF_PLUS=' \
     91 	'IFNDEF_SECOND=' \
     92 	'.endif'
     93 # expect: Parse_PushInput: file ifndef-plus.tmp, line 1
     94 # expect: Parse_PushInput: file ifndef-plus.tmp, line 1
     95 
     96 # The guard condition must consist of only the guard variable, nothing else.
     97 INCS+=	if-plus
     98 LINES.if-plus= \
     99 	'.if !defined(IF_PLUS) && !defined(IF_SECOND)' \
    100 	'IF_PLUS=' \
    101 	'IF_SECOND=' \
    102 	'.endif'
    103 # expect: Parse_PushInput: file if-plus.tmp, line 1
    104 # expect: Parse_PushInput: file if-plus.tmp, line 1
    105 
    106 # The variable name in an '.ifndef' guard must be given directly, it must not
    107 # contain any '$' expression.
    108 INCS+=	ifndef-indirect
    109 LINES.ifndef-indirect= \
    110 	'.ifndef $${IFNDEF_INDIRECT:L}' \
    111 	'IFNDEF_INDIRECT=' \
    112 	'.endif'
    113 # expect: Parse_PushInput: file ifndef-indirect.tmp, line 1
    114 # expect: Parse_PushInput: file ifndef-indirect.tmp, line 1
    115 
    116 # The variable name in an '.if' guard must be given directly, it must not contain
    117 # any '$' expression.
    118 INCS+=	if-indirect
    119 LINES.if-indirect= \
    120 	'.if !defined($${IF_INDIRECT:L})' \
    121 	'IF_INDIRECT=' \
    122 	'.endif'
    123 # expect: Parse_PushInput: file if-indirect.tmp, line 1
    124 # expect: Parse_PushInput: file if-indirect.tmp, line 1
    125 
    126 # The variable name in the guard condition must only contain alphanumeric
    127 # characters and underscores.  The guard variable is more flexible, it can be
    128 # set anywhere, as long as it is set when the guarded file is included next.
    129 INCS+=	varassign-indirect
    130 LINES.varassign-indirect= \
    131 	'.ifndef VARASSIGN_INDIRECT' \
    132 	'$${VARASSIGN_INDIRECT:L}=' \
    133 	'.endif'
    134 # expect: Parse_PushInput: file varassign-indirect.tmp, line 1
    135 # expect: Skipping 'varassign-indirect.tmp' because 'VARASSIGN_INDIRECT' is defined
    136 
    137 # The time at which the guard variable is set doesn't matter, as long as it is
    138 # set when the file is included the next time.
    139 INCS+=	late-assignment
    140 LINES.late-assignment= \
    141 	'.ifndef LATE_ASSIGNMENT' \
    142 	'OTHER=' \
    143 	'LATE_ASSIGNMENT=' \
    144 	'.endif'
    145 # expect: Parse_PushInput: file late-assignment.tmp, line 1
    146 # expect: Skipping 'late-assignment.tmp' because 'LATE_ASSIGNMENT' is defined
    147 
    148 # The time at which the guard variable is set doesn't matter, as long as it is
    149 # set when the file is included the next time.
    150 INCS+=	two-conditions
    151 LINES.two-conditions= \
    152 	'.ifndef TWO_CONDITIONS' \
    153 	'.  if 1' \
    154 	'TWO_CONDITIONS=' \
    155 	'.  endif' \
    156 	'.endif'
    157 # expect: Parse_PushInput: file two-conditions.tmp, line 1
    158 # expect: Skipping 'two-conditions.tmp' because 'TWO_CONDITIONS' is defined
    159 
    160 # If the guard variable is defined before the file is included for the first
    161 # time, the file is not considered guarded.
    162 INCS+=	already-set
    163 LINES.already-set= \
    164 	'.ifndef ALREADY_SET' \
    165 	'ALREADY_SET=' \
    166 	'.endif'
    167 ALREADY_SET=
    168 # expect: Parse_PushInput: file already-set.tmp, line 1
    169 # expect: Parse_PushInput: file already-set.tmp, line 1
    170 
    171 # The whole file content must be guarded by a single '.if' conditional, not by
    172 # several, even if they have the same effect.
    173 INCS+=	twice
    174 LINES.twice= \
    175 	'.ifndef TWICE_FIRST' \
    176 	'TWICE_FIRST=' \
    177 	'.endif' \
    178 	'.ifndef TWICE_SECOND' \
    179 	'TWICE_SECOND=' \
    180 	'.endif'
    181 # expect: Parse_PushInput: file twice.tmp, line 1
    182 # expect: Parse_PushInput: file twice.tmp, line 1
    183 
    184 # When multiple files use the same guard variable name, they exclude each
    185 # other.  It's the responsibility of the makefile authors to choose unique
    186 # variable names.  Typical choices are ${PROJECT}_${DIR}_${FILE}_MK.  This is
    187 # the same situation as in the 'already-set' test, and the file is not
    188 # considered guarded.
    189 INCS+=	reuse
    190 LINES.reuse= \
    191 	${LINES.guarded-if}
    192 # expect: Parse_PushInput: file reuse.tmp, line 1
    193 # expect: Parse_PushInput: file reuse.tmp, line 1
    194 
    195 # The conditional must come before the assignment, otherwise the conditional
    196 # is useless, as it always evaluates to false.
    197 INCS+=	swapped
    198 LINES.swapped= \
    199 	'SWAPPED=' \
    200 	'.ifndef SWAPPED' \
    201 	'.endif'
    202 # expect: Parse_PushInput: file swapped.tmp, line 1
    203 # expect: Parse_PushInput: file swapped.tmp, line 1
    204 
    205 # If the guard variable is undefined at some later point, the guarded file is
    206 # included again.
    207 INCS+=	undef-between
    208 LINES.undef-between= \
    209 	'.ifndef UNDEF_BETWEEN' \
    210 	'UNDEF_BETWEEN=' \
    211 	'.endif'
    212 # expect: Parse_PushInput: file undef-between.tmp, line 1
    213 # expect: Parse_PushInput: file undef-between.tmp, line 1
    214 
    215 # If the guarded file undefines the guard variable, the guarded file is
    216 # included again.
    217 INCS+=	undef-inside
    218 LINES.undef-inside= \
    219 	'.ifndef UNDEF_INSIDE' \
    220 	'UNDEF_INSIDE=' \
    221 	'.undef UNDEF_INSIDE' \
    222 	'.endif'
    223 # expect: Parse_PushInput: file undef-inside.tmp, line 1
    224 # expect: Parse_PushInput: file undef-inside.tmp, line 1
    225 
    226 # The outermost '.if' must not have an '.elif' branch.
    227 INCS+=	if-elif
    228 LINES.if-elif = \
    229 	'.ifndef IF_ELIF' \
    230 	'IF_ELIF=' \
    231 	'.elif 1' \
    232 	'.endif'
    233 # expect: Parse_PushInput: file if-elif.tmp, line 1
    234 # expect: Parse_PushInput: file if-elif.tmp, line 1
    235 
    236 # The outermost '.if' must not have an '.else' branch.
    237 INCS+=	if-else
    238 LINES.if-else = \
    239 	'.ifndef IF_ELSE' \
    240 	'IF_ELSE=' \
    241 	'.else' \
    242 	'.endif'
    243 # expect: Parse_PushInput: file if-else.tmp, line 1
    244 # expect: Parse_PushInput: file if-else.tmp, line 1
    245 
    246 # The inner '.if' directives may have an '.elif' or '.else'.
    247 INCS+=	inner-if-elif-else
    248 LINES.inner-if-elif-else = \
    249 	'.ifndef INNER_IF_ELIF_ELSE' \
    250 	'INNER_IF_ELIF_ELSE=' \
    251 	'.  if 0' \
    252 	'.  elif 0' \
    253 	'.  else' \
    254 	'.  endif' \
    255 	'.  if 0' \
    256 	'.  elif 1' \
    257 	'.  else' \
    258 	'.  endif' \
    259 	'.  if 1' \
    260 	'.  elif 1' \
    261 	'.  else' \
    262 	'.  endif' \
    263 	'.endif'
    264 # expect: Parse_PushInput: file inner-if-elif-else.tmp, line 1
    265 # expect: Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is defined
    266 
    267 # The guard can not only be a variable, it can also be a target.
    268 INCS+=	target
    269 LINES.target= \
    270 	'.if !target(__target.tmp__)' \
    271 	'__target.tmp__: .PHONY' \
    272 	'.endif'
    273 # expect: Parse_PushInput: file target.tmp, line 1
    274 # expect: Skipping 'target.tmp' because '__target.tmp__' is defined
    275 
    276 # When used for system files, the target name may include '<' and '>'.
    277 INCS+=	target-sys
    278 LINES.target-sys= \
    279 	'.if !target(__<target-sys.tmp>__)' \
    280 	'__<target-sys.tmp>__: .PHONY' \
    281 	'.endif'
    282 # expect: Parse_PushInput: file target-sys.tmp, line 1
    283 # expect: Skipping 'target-sys.tmp' because '__<target-sys.tmp>__' is defined
    284 
    285 # The target name must not include '$' or other special characters.
    286 INCS+=	target-indirect
    287 LINES.target-indirect= \
    288 	'.if !target($${target-indirect.tmp:L})' \
    289 	'target-indirect.tmp: .PHONY' \
    290 	'.endif'
    291 # expect: Parse_PushInput: file target-indirect.tmp, line 1
    292 # expect: Parse_PushInput: file target-indirect.tmp, line 1
    293 
    294 # If the target is not defined when including the file the next time, the file
    295 # is not guarded.
    296 INCS+=	target-unguarded
    297 LINES.target-unguarded= \
    298 	'.if !target(target-unguarded)' \
    299 	'.endif'
    300 # expect: Parse_PushInput: file target-unguarded.tmp, line 1
    301 # expect: Parse_PushInput: file target-unguarded.tmp, line 1
    302 
    303 # The guard condition must consist of only the guard target, nothing else.
    304 INCS+=	target-plus
    305 LINES.target-plus= \
    306 	'.if !target(target-plus) && 1' \
    307 	'target-plus: .PHONY' \
    308 	'.endif'
    309 # expect: Parse_PushInput: file target-plus.tmp, line 1
    310 # expect: Parse_PushInput: file target-plus.tmp, line 1
    311 
    312 # If the guard target is defined before the file is included for the first
    313 # time, the file is not considered guarded.
    314 INCS+=	target-already-set
    315 LINES.target-already-set= \
    316 	'.if !target(target-already-set)' \
    317 	'target-already-set: .PHONY' \
    318 	'.endif'
    319 target-already-set: .PHONY
    320 # expect: Parse_PushInput: file target-already-set.tmp, line 1
    321 # expect: Parse_PushInput: file target-already-set.tmp, line 1
    322 
    323 
    324 # Include each of the files twice.  The directive-include-guard.exp file
    325 # contains a single entry for the files whose multiple-inclusion guard works,
    326 # and two entries for the files that are not protected against multiple
    327 # inclusion.
    328 #
    329 # Some debug output lines are suppressed in the .exp file, see ./Makefile.
    330 .for i in ${INCS}
    331 .  for fname in $i.tmp
    332 _!=	printf '%s\n' ${LINES.$i} > ${fname}
    333 .MAKEFLAGS: -dp
    334 .include "${.CURDIR}/${fname}"
    335 .undef ${i:Mundef-between:%=UNDEF_BETWEEN}
    336 .include "${.CURDIR}/${fname}"
    337 .MAKEFLAGS: -d0
    338 _!=	rm ${fname}
    339 .  endfor
    340 .endfor
    341 
    342 all:
    343