Reputation: 362
I understand that you can define pattern-specific variables for rules with no prerequisites, like this:
%.o: var = 2
This will set variable var
to 2 for recipes that apply to all targets ending in .o. This is clearly stated in the GNU Make documentation. But how do you define a pattern-specific variable for pattern rules that contain a pattern prerequisite, like this:
%.o: %.c
Say I have this section of makefile:
%.o: %.c
(recipe here)
%.o: %.b
(recipe here)
I want to define a pattern-specific variable only for the %.o: %.b
rule, but I don't see how to do it (if it's even possible). I'd like to do something like this, but of course it doesn't work:
%.o: %.c
(recipe here)
%.o: %.b: var = 2
(recipe here)
Is there a way to do this?
Upvotes: 4
Views: 2231
Reputation: 362
I found another solution, although it could get a little difficult to read depending on how many prerequisite comparisons are made. Perhaps there's a way to refactor it.
# Return 1 if prerequisite ends in `.c`.
# Return 2 if prerequisite ends in `.b`.
build-var = $(if $(filter %.c,$<),1,$(if $(filter %.b,$<),2))
%.o: private var = $(build-var)
%.o: %.c
(recipe here)
%.o: %.b
(recipe here)
The instruction var = $(build-var)
will be invoked for any target that ends in .o
. But when build-var
is expanded, its value comes from examining the end of the prerequisite to see what type of file it is.
The if
statement evaluates to true if its first argument is a non-empty string, and filter
returns a non-empty string if, in my example, the prerequisite contains .c
. So, walking through the build-var
line, if the prerequisite (denoted by the automatic variable $<
) ends in .c
, then return the value 1
. Else, start a second if
statement that returns the value 2
if the prerequisite ends in .b
. If the prerequisite doesn't end in either .c
or .b
then build-var is set to nothing (an empty string).
Upvotes: 2
Reputation: 3469
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.
$(value rule-var-$1-$3)
if it's nonzero. That is function has been called before for this target and variable name.$(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.
Upvotes: 1