Make: Target-specific variable assignment in function - makefile

Is it possible do the following in one $(call ...) inside a prerequisite list?
Assign an argument to a (private/non-inheriting) variable.
Add the argument to the target list.
That is, the following should assign the string value to the variable $(var), while adding value as a prerequisite for the target foo.
foo: $(call assign-value-to-var,value)
# Do stuff

Yeah, why not?
assign-value-to-var=$(eval foo: private var:=$1)$1
foo: $(call assign-value-to-var,value)
# Do stuff

Related

Why do I need eval to set variable in makefile function?

If I have the following at the top of a gnu makefile:
$(if _,a:=1)
$(info a=$a)
Compilation fails (make-3.81: *** No rule to make target '=1', needed by 'a'. Stop., or *** missing separator. Stop. if I exclude the colon). I know I can get around this by using an eval, but I'm not understanding why it's required. Shouldn't this expand to a:=1, be parsed, and set a to 1?
I'm not understanding why it's required
Well, such is the design.
Evaluation procedure always performs a (recursive) expansion (except for "the initial evaluation" where subsequent expansion can be effectively stopped by =, i.e. "lazy assignment"), but expansion procedure never does evaluation, unless it's explicitly told so (basically, $(eval ...) is such an order to switch from expansion to evaluation).
Consider this:
x := 0
y := 1
# z is set to "y<space><equal><space><dollar>x"
z = y = $x
# [0] [1] [y = $x]
$(info [$x] [$y] [$(value z)])
# error as "y = $x" will not be re-evaluated
#$z
# effectively sets y to "0"
$(eval $z)
# [0] [0] [y = $x]
$(info [$x] [$y] [$(value z)])
From make's point of view $(if cond,x=y) does not differ much from $z --- it's expanded but not evaluated. No matter where it stands. Just think anything of a form $(...) to be "data", not "code".
In your case the if function evaluates as its second argument (a:=1) but this is not evaluated in turn as a make variable assignment. With your version of make it becomes a rule. This is probably due to the way make syntactically parses the makefiles. It does not consider that this if statement can be a variable assignment because (before expansion) it looks like none of the valid make variable assignments. So the if statement gets expanded but it is too late for make to consider the result as a variable assignment...
Instead you can use:
a := $(if _,1)
or:
ifneq (_,)
a := 1
endif
or:
$(if _,$(eval a := 1))
or:
$(eval $(if _,a := 1))
The two last forms work because by definition the result of the expansion of the argument of the eval function is processed as plain make statements.

How to filter out a pattern with space in Makefile

I want to filter out a pattern as "-Wl, Bdynamic -lmylib" from a long LDFLAGS list. But filter-out function can only handle space-separated list, is there other method to do this in Makefile?
The only way to do it is by replacing spaces with some other character that you know doesn't appear in the variable value. For example suppose you know that the ^ character never appears in your variable value, then you can do something like this:
# Create a variable containing a space
E :=
S := $E $E
LDFLAGS := $(subst ^,$S,$(subst -Wl^Bdynamic^-lmylib,,$(subst $S,^,$(LDFLAGS))))

How do you define pattern-specific variables for makefile pattern rules containing a patterned prerequisite?

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?
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.
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).

Make return error 127

Am trying to match specific string after doing subst on a variable ; substitution is ok but while trying to match make returns error 127.
Does anyone know what's going on here ? why make should return error ?
$> make -f strsearch.mk
CODE : BBROY_OF_GREAT_BRITAIN_HAD_A_GREAT_WIFE
MORSE = BBROY OF GREAT BRITAIN HAD A GREAT WIFE
**make: MORSE: Command not found
make: * [search_new] Error 127
Here is the snippet of makefile:
VAR1 := BBROY_OF_GREAT_BRITAIN_HAD_A_GREAT_WIFE
search_new:
#echo CODE : $(VAR1)
MORSE = $(subst _, , $(VAR1))
#echo word count : ($words $(MORSE))
#echo After substitution Britain matches: $(filter %BRITAIN%, $(MORSE))
Your variable declaration is in the wrong place or form:
The form you use is for global assignment and cannot be inside a target block.
Either make it a global variable outside a target block
Or use the shell and eval to set the variable value. See the accepted answer for how to approach this issue: Define make variable at rule execution time
A 3rd option is to define the variable as a sort-of prerequisite which is then assigned at the time the dependencies are evaluated.

String parsing inside a makefile

I need to parse a string inside a Makefile and fetch the value assigned to the variable parsed in the string.
String contains this data "Days=false;export Day;EXTRACT=true;export EXTRACT".
First i need to check if a variable(for eg Days) is present in the string.if it exists want to get the value of the variable.
i tried findstring function i can verify variable is present in string but i find how to get the value assigned to the variable.str1 := Days='true';export Days;EXTRACT=true;export EXTRACT;
a := $(findstring Days=, $(str1))
str1 := Days='true';export Days;EXTRACT=true;export EXTRACT;
define VARIABLE_EQ_VALUE_IN_STRING
$(eval $(filter $1=%, $(subst ;, ,$2)))
endef
$(call VARIABLE_EQ_VALUE_IN_STRING,Days, $(str1))
After this code, $(Days) is 'true'
On the other hand, if str1 did not contain an assignment to Days then after this code, Days would be undefined.

Resources