summaryrefslogtreecommitdiff
path: root/usr.bin/make/unit-tests/varname-makeflags.mk
blob: 4173c5a920953f6b31364bdab0be517bf56545ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# $NetBSD: varname-makeflags.mk,v 1.8 2023/06/01 07:27:30 rillig Exp $
#
# Tests for the environment variable 'MAKEFLAGS', from which additional
# command line arguments are read before the actual command line arguments.
#
# After reading the makefiles and before making the targets, the arguments
# that were collected in '.MAKEFLAGS' and '.MAKEOVERRIDES' are written back to
# the environment variable 'MAKEFLAGS'.

all: spaces_stage_0 dollars_stage_0 append_stage_0 override_stage_0


.if !make(*stage*)

# The unit tests are run with an almost empty environment.  In particular,
# the variable MAKEFLAGS is not set.
.  if ${MAKEFLAGS:Uundefined} != "undefined"
.    error
.  endif

# The special variable .MAKEFLAGS is influenced though.
# See varname-dot-makeflags.mk for more details.
.  if ${.MAKEFLAGS} != " -r -k"
.    error
.  endif


# In POSIX mode, the environment variable MAKEFLAGS can contain letters only,
# for compatibility.  These letters are exploded to form regular options.
OUTPUT!=	env MAKEFLAGS=ikrs ${MAKE} -f /dev/null -v .MAKEFLAGS
.  if ${OUTPUT} != " -i -k -r -s -V .MAKEFLAGS"
.    error
.  endif

# As soon as there is a single non-alphabetic character in the environment
# variable MAKEFLAGS, it is no longer split.  In this example, the word
# "d0ikrs" is treated as a target, but the option '-v' prevents any targets
# from being built.
OUTPUT!=	env MAKEFLAGS=d0ikrs ${MAKE} -r -f /dev/null -v .MAKEFLAGS
.  if ${OUTPUT} != " -r -V .MAKEFLAGS"
.    error ${OUTPUT}
.  endif

.endif


# When options are parsed, the option and its argument are appended as
# separate words to the MAKEFLAGS for the child processes.  Special characters
# in the option arguments are not quoted though.
spaces_stage_0:
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
	@echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"
	@${MAKE} -f ${MAKEFILE} spaces_stage_1 -d00000 -D"VARNAME WITH SPACES"

# At this point, the 'VARNAME WITH SPACES' is no longer recognizable as a
# single command line argument.  In practice, variable names don't contain
# spaces.
spaces_stage_1:
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
	@echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"


# Demonstrate that '$' characters are altered when they are passed on to child
# make processes via MAKEFLAGS.
dollars_stage_0:
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'

	# The '$$$$' becomes a literal '$$' when building the '${MAKE}'
	# command line, making the actual argument 'DOLLARS=$${varname}'.
	# At this stage, MAKEFLAGS is not yet involved.
	@${MAKE} -f ${MAKEFILE} dollars_stage_1 DOLLARS='$$$${varname}'

.if make(dollars_stage_1)
# At this point, the variable 'DOLLARS' contains '$${varname}', which
# evaluates to a literal '$' followed by '{varname}'.
.  if ${DOLLARS} != "\${varname}"
.    error
.  endif
.endif
dollars_stage_1:
	# At this point, the stage 1 make provides the environment variable
	# 'MAKEFLAGS' to its child processes, even if the child process is not
	# another make.
	#
	# expect: dollars_stage_1: env MAKEFLAGS=< -r -k DOLLARS=\$\{varname\}>
	#
	# The 'DOLLARS=\$\{varname\}' assignment is escaped so that the stage
	# 2 make will see it as a single word.
	@echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"

	# At this point, evaluating the environment variable 'MAKEFLAGS' leads
	# to strange side effects as the string '\$\{varname\}' is interpreted
	# as:
	#
	#	\		a literal string of a single backslash
	#	$\		the value of the variable named '\'
	#	{varname\}	a literal string
	#
	# Since the variable named '\' is not defined, the resulting value is
	# '\{varname\}'.  Make doesn't handle isolated '$' characters in
	# strings well, instead each '$' has to be part of a '$$' or be part
	# of a subexpression like '${VAR}'.
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'

	# The modifier ':q' preserves a '$$' in an expression value instead of
	# expanding it to a single '$', but it's already too late, as that
	# modifier applies after the expression has been evaluated.  Except
	# for debug logging, there is no way to process strings that contain
	# isolated '$'.
	@echo '$@: MAKEFLAGS:q=<'${MAKEFLAGS:q}'>'

	@${MAKE} -f ${MAKEFILE} dollars_stage_2

.if make(dollars_stage_2)
# At this point, the variable 'DOLLARS' contains '${varname}', and since
# 'varname' is undefined, that expression evaluates to an empty string.
.  if ${DOLLARS} != ""
.    error
.  endif
varname=	varvalue
.  if ${DOLLARS} != "varvalue"
.    error
.  endif
.  undef varname
.endif
dollars_stage_2:
	@echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"
	@echo '$@: dollars=<'${DOLLARS:Q}'>'
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
	@${MAKE} -f ${MAKEFILE} dollars_stage_3

dollars_stage_3:
	@echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"
	@echo '$@: dollars=<'${DOLLARS:Uundefined:Q}'>'
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'


# Demonstrates in which exact order the MAKEFLAGS are built from the parent
# MAKEFLAGS and the flags from the command line, in particular that variable
# assignments are passed at the end, after the options.
append_stage_0:
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
	@${MAKE} -Dbefore-0 -f ${MAKEFILE} append_stage_1 VAR0=value -Dafter-0

append_stage_1:
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
	@${MAKE} -Dbefore-1 -f ${MAKEFILE} append_stage_2 VAR1=value -Dafter-1

append_stage_2:
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
	@${MAKE} -Dbefore-2 -f ${MAKEFILE} append_stage_3 VAR2=value -Dafter-2

append_stage_3:
	@echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'


# Demonstrates the implementation details of 'MAKEFLAGS', in particular that
# it is an environment variable rather than a global variable.
override_stage_0:
	@${MAKE} -f ${MAKEFILE} STAGE=1 VAR=value override_stage_1

.if make(override_stage_1)
# While parsing the makefiles, 'MAKEFLAGS' is the value of the environment
# variable, in this case provided by stage 0.
.  if ${MAKEFLAGS:M*} != "-r -k"
.    error
.  endif
MAKEFLAGS=	overridden	# temporarily override it
.  if ${MAKEFLAGS} != "overridden"
.    error
.  endif
.undef MAKEFLAGS		# make the environment variable visible again
.  if ${MAKEFLAGS:M*} != "-r -k"
.    error
.  endif
.endif
override_stage_1:
	@echo '$@: run MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
	@${MAKE} -f ${MAKEFILE} STAGE=2 override_stage_2

override_stage_2:
	@echo '$@: STAGE=<${STAGE}> VAR=<${VAR}>'