Home | History | Annotate | Line # | Download | only in unit-tests
varmod-assign.mk revision 1.18
      1 # $NetBSD: varmod-assign.mk,v 1.18 2023/12/31 10:09:01 rillig Exp $
      2 #
      3 # Tests for the obscure ::= variable modifiers, which perform variable
      4 # assignments during evaluation, just like the = operator in C.
      5 
      6 all:	mod-assign-empty
      7 all:	mod-assign-parse
      8 all:	mod-assign-shell-error
      9 
     10 # In the following loop expression,
     11 # the '::?=' modifier applies the assignment operator '?=' 3 times. The
     12 # operator '?=' only has an effect for the first time, therefore the variable
     13 # FIRST ends up with the value 1.
     14 .if "${1 2 3:L:@i@${FIRST::?=$i}@} first=${FIRST}" != " first=1"
     15 .  error
     16 .endif
     17 
     18 # In the following loop expression,
     19 # the modifier '::=' applies the assignment operator '=' 3 times. The
     20 # operator '=' overwrites the previous value, therefore the variable LAST ends
     21 # up with the value 3.
     22 .if "${1 2 3:L:@i@${LAST::=$i}@} last=${LAST}" != " last=3"
     23 .  error
     24 .endif
     25 
     26 # In the following loop expression,
     27 # the modifier '::+=' applies the assignment operator '+=' 3 times. The
     28 # operator '+=' appends 3 times to the variable, therefore the variable
     29 # APPENDED ends up with the value "1 2 3".
     30 .if "${1 2 3:L:@i@${APPENDED::+=$i}@} appended=${APPENDED}" != " appended=1 2 3"
     31 .  error
     32 .endif
     33 
     34 # In the following loop expression,
     35 # the modifier '::!=' applies the assignment operator '!=' 3 times. Just as
     36 # with the modifier '::=', the last value is stored in the RAN variable.
     37 .if "${1 2 3:L:@i@${RAN::!=${i:%=echo '<%>';}}@} ran=${RAN}" != " ran=<3>"
     38 .  error
     39 .endif
     40 
     41 # When a '::=' modifier is evaluated as part of an .if condition, it happens
     42 # in the command line scope.
     43 .if "${FIRST}, ${LAST}, ${APPENDED}, ${RAN}" != "1, 3, 1 2 3, <3>"
     44 .  error
     45 .endif
     46 
     47 # Tests for nested assignments, which are hard to read and therefore seldom
     48 # used in practice.
     49 
     50 # The condition "1" is true, therefore THEN1 gets assigned a value,
     51 # and the inner IT1 as well.  Nothing surprising here.
     52 .if "${1:?${THEN1::=then1${IT1::=t1}}:${ELSE1::=else1${IE1::=e1}}} ${THEN1}${ELSE1}${IT1}${IE1}" != " then1t1"
     53 .  error
     54 .endif
     55 
     56 # The condition "0" is false, therefore ELSE2 gets assigned a value,
     57 # and the inner IE2 as well.  Nothing surprising here as well.
     58 .if "${0:?${THEN2::=then2${IT2::=t2}}:${ELSE2::=else2${IE2::=e2}}} ${THEN2}${ELSE2}${IT2}${IE2}" != " else2e2"
     59 .  error
     60 .endif
     61 
     62 # The same effects happen when the variables are defined elsewhere.
     63 SINK3:=	${1:?${THEN3::=then3${IT3::=t3}}:${ELSE3::=else3${IE3::=e3}}} ${THEN3}${ELSE3}${IT3}${IE3}
     64 SINK4:=	${0:?${THEN4::=then4${IT4::=t4}}:${ELSE4::=else4${IE4::=e4}}} ${THEN4}${ELSE4}${IT4}${IE4}
     65 .if ${SINK3} != " then3t3"
     66 .  error
     67 .endif
     68 .if ${SINK4} != " else4e4"
     69 .  error
     70 .endif
     71 
     72 mod-assign-empty:
     73 	# Assigning to the empty variable would obviously not work since that
     74 	# variable is write-protected.  Therefore it is rejected early with a
     75 	# "Bad modifier" message.
     76 	@echo $@: ${::=value}
     77 
     78 	# In this variant, it is not as obvious that the name of the
     79 	# expression is empty.  Assigning to it is rejected as well, with the
     80 	# same "Bad modifier" message.
     81 	@echo $@: ${:Uvalue::=overwritten}
     82 
     83 	# The :L modifier sets the value of the expression to its variable
     84 	# name.  The name of the expression is "VAR", therefore assigning to
     85 	# that variable works.
     86 	@echo $@: ${VAR:L::=overwritten} VAR=${VAR}
     87 
     88 mod-assign-parse:
     89 	# The modifier for assignment operators starts with a ':'.
     90 	# An 'x' after that is an invalid modifier.
     91 	# expect: make: Unknown modifier ":x"
     92 	@echo ${ASSIGN::x}
     93 
     94 	# When parsing an assignment operator fails because the operator is
     95 	# incomplete, make falls back to the SysV modifier.
     96 	@echo ${SYSV::=sysv\:x}${SYSV::x=:y}
     97 
     98 	@echo ${ASSIGN::=value	# missing closing brace
     99 
    100 mod-assign-shell-error:
    101 	# If the command succeeds, the variable is assigned.
    102 	@${SH_OK::!= echo word; true } echo ok=${SH_OK}
    103 
    104 	# If the command fails, the variable keeps its previous value.
    105 	@${SH_ERR::=previous}
    106 	@${SH_ERR::!= echo word; false } echo err=${SH_ERR}
    107 
    108 # XXX: The ::= modifier expands its right-hand side exactly once.
    109 # This differs subtly from normal assignments such as '+=' or '=', which copy
    110 # their right-hand side literally.
    111 APPEND.prev=		previous
    112 APPEND.var=		${APPEND.prev}
    113 APPEND.indirect=	indirect $${:Unot expanded}
    114 APPEND.dollar=		$${APPEND.indirect}
    115 .if ${APPEND.var::+=${APPEND.dollar}} != ""
    116 .  error
    117 .endif
    118 .if ${APPEND.var} != "previous indirect \${:Unot expanded}"
    119 .  error
    120 .endif
    121 
    122 
    123 # The assignment modifier can be used in an expression that is
    124 # enclosed in parentheses.  In such a case, parsing stops at the first ')',
    125 # not at the first '}'.
    126 VAR=	previous
    127 _:=	$(VAR::=current})
    128 .if ${VAR} != "current}"
    129 .  error
    130 .endif
    131 
    132 
    133 # Before var.c 1.888 from 2021-03-15, an expression using the modifier '::='
    134 # expanded its variable name once too often during evaluation.  This was only
    135 # relevant for variable names containing a '$' sign in their actual name, not
    136 # the usual VAR.${param}.
    137 .MAKEFLAGS: -dv
    138 param=		twice
    139 VARNAME=	VAR.$${param}	# Indirect variable name because of the '$',
    140 				# to avoid difficult escaping rules.
    141 
    142 ${VARNAME}=	initial-value	# Sets 'VAR.${param}' to 'expanded'.
    143 .if defined(VAR.twice)		# At this point, the '$$' is not expanded.
    144 .  error
    145 .endif
    146 .if ${${VARNAME}::=assigned-value} # Here the variable name gets expanded once
    147 .  error			# too often.
    148 .endif
    149 .if defined(VAR.twice)
    150 .  error The variable name in the '::=' modifier is expanded once too often.
    151 .endif
    152 .if ${${VARNAME}} != "assigned-value"
    153 .  error
    154 .endif
    155 .MAKEFLAGS: -d0
    156 
    157 
    158 # Conditional directives are evaluated in command line scope.  An assignment
    159 # modifier that creates a new variable creates it in the command line scope.
    160 # Existing variables are updated in their previous scope, and environment
    161 # variables are created in the global scope, as in other situations.
    162 .MAKEFLAGS: CMD_CMD_VAR=cmd-value
    163 CMD_GLOBAL_VAR=global-value
    164 export CMD_ENV_VAR=env-value
    165 
    166 .MAKEFLAGS: -dv
    167 # expect-reset
    168 # expect: Command: CMD_CMD_VAR = new-value
    169 # expect: Global: CMD_GLOBAL_VAR = new-value
    170 # expect: Global: CMD_ENV_VAR = new-value
    171 # expect: Global: ignoring delete 'CMD_NEW_VAR' as it is not found
    172 # expect: Command: CMD_NEW_VAR = new-value
    173 .if ${CMD_CMD_VAR::=new-value} \
    174   || ${CMD_GLOBAL_VAR::=new-value} \
    175   || ${CMD_ENV_VAR::=new-value} \
    176   || "${CMD_NEW_VAR::=new-value}"
    177 .  error
    178 .endif
    179 .MAKEFLAGS: -d0
    180 
    181 
    182 # In target scope, assignments only happen in a few cases.  To extract the
    183 # debug log for this test, the debug log would have to be enabled for the
    184 # other targets as well, thus producing lots of irrelevant output.
    185 #
    186 # Running './make -r -f varmod-assign.mk target | grep ": TARGET"' results in:
    187 #	target: TARGET_TARGET_VAR = new-value
    188 #	Global: TARGET_GLOBAL_VAR = new-value
    189 #	Global: TARGET_ENV_VAR = new-value
    190 #	target: TARGET_NEW_VAR = new-value
    191 .MAKEFLAGS: TARGET_CMD_VAR=cmd-value
    192 TARGET_GLOBAL_VAR=global-value
    193 export TARGET_ENV_VAR=env-value
    194 .MAKEFLAGS: ${make(target):?-dv:}
    195 target: .PHONY TARGET_TARGET_VAR=target-value
    196 	: ${TARGET_TARGET_VAR::=new-value}
    197 	: ${TARGET_CMD_VAR::=new-value}
    198 	: ${TARGET_GLOBAL_VAR::=new-value}
    199 	: ${TARGET_ENV_VAR::=new-value}
    200 	: ${TARGET_NEW_VAR::=new-value}
    201