You can only set variable for targets, not rules. %.o: %b
is a rule where %.o
is a target pattern (hence the "pattern-specific" name).
The usual way to solve this is ether hard coding values in the recipes or using rule-specific flags (maybe CVAR
and BVAR
in your case).
EDIT: Scratch that. Came up with a workaround.
It can be done by leveraging variables' recursive evaluation.
all: a.o b.o c.o
$(shell touch a.xx b.yy c.zz)
##
# Create rule-specific variable... rules
#
# @param 1 Target.
# @param 2 Prerequisite.
# @param 3 Variable name.
# @param 4 Variable value.
#
rule-var = $(eval $(rule-var-body))
define rule-var-body
$1: private $3 = $$(if $$(<:$2=),$(or $(value rule-var-$1-$3),$(value $3)),$4)
$2: $3 = $4
rule-var-$1-$3 = $$(if $$(<:$2=),$(or $(value rule-var-$1-$3),$(value $3)),$4)
endef
VAR = $(VAR_DEFAULT)
# Declare couple of test values
$(call rule-var,%.o,%.x,VAR,x-value)
$(call rule-var,%.o,%.y,VAR,y-value)
VAR_DEFAULT := z-value
ECHO_RULE_RECIPE = @echo -e '$@: $^\t(VAR = $(VAR))'
%.o: %.x
$(ECHO_RULE_RECIPE)
%.o: %.y
$(ECHO_RULE_RECIPE)
%.o: %.z
$(ECHO_RULE_RECIPE)
%.x: %.xx
$(ECHO_RULE_RECIPE)
%.y: %.yy
$(ECHO_RULE_RECIPE)
%.z: %.zz
$(ECHO_RULE_RECIPE)
The output is:
a.x: a.xx (VAR = x-value)
a.o: a.x (VAR = x-value)
b.y: b.yy (VAR = y-value)
b.o: b.y (VAR = y-value)
c.z: c.zz (VAR = z-value)
c.o: c.z (VAR = z-value)
The brains of the operation is macro rule-var
. It will wrap variable value in a prerequisite matching if-else
expression. The expression is also saved in rule-var-$1-$3
variable for other rule-specific values.
$$(if $$(<:$2=),$(or $(value rule-var-$1-$3),$(value $3)),$4)
deobfuscation:
$$(if $$(<:$2=),
will test first prerequisite value ($<
) by replacing it's pattern ($2
) with empty string.
If pattern doesn't match, use $(or $(value rule-var-$1-$3),$(value $3))
. This is a workaround for global variable shadowing. In your example %.o: %.c
doesn't have var
declared so it should use global value but both rules share the same target, it's not visible. Both are referenced by value and single $
expands the expression during variable substitution phase. So the result is neat and or
free.
- Use
$(value rule-var-$1-$3)
if it's nonzero. That is function has been called before for this target and variable name.
- Otherwise use variable's global value (
$(value $3)
).
Otherwise use the value provided ($4
).
Unfortunately, when inherited, this if-else monstrosity won't expand properly so it's declared as private
and fixed with a straightforward second rule.
In this example the following 3 rules will be declared.
%.o: private VAR = $(if $(<:%.y=),$(if $(<:%.x=),$(VAR_DEFAULT),x-value),y-value)
%.y: VAR = y-value
%.x: VAR = x-value
Limitations
Even with a workaround, variable's global counterpart is still shadowed. If you need a default value, assign it before calling rule-var
. Global value is copied as a part of the rule-specific variable but not expanded until use.