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