Target-specific recursive variable preprending - makefile

I know how to prepend a recursive variable without loosing its recursive characteristic at the top level of a Makefile. But I don't know how to do that when giving a target specific value. In code form
ALT1=a1
ALT2=a2
ALT3=a3
ALT4=a4
ALT5=a5
VAR=$(ALT1)
$(eval VAR=$$(ALT2) $(value VAR))
ALT1=b1
ALT2=b2
ALT3=b3
ALT4=b4
ALT5=b5
top: sub1
#echo 'In top VAR is $(flavor VAR)=$(value VAR)=$(VAR)'
sub1: VAR:=$$(ALT3) $$(VAR)
sub1: sub2
#echo 'In sub1 VAR is $(flavor VAR)=$(value VAR)=$(VAR)'
sub2: VAR:=$(ALT4) $(VAR)
sub2:
#echo 'In sub2 VAR is $(flavor VAR)=$(value VAR)=$(VAR)'
VAR+= $(ALT5)
ALT1=c1
ALT2=c2
ALT3=c3
ALT4=c4
ALT5=c5
displays
In sub2 VAR is simple=b4 b2 b1=b4 b2 b1
In sub1 VAR is simple=$(ALT3) $(VAR)=$(ALT3) $(VAR)
In top VAR is recursive=$(ALT2) $(ALT1)=c2 c1 c5
while I'd like
In sub2 VAR is recursive=$(ALT4) $(ALT3) $(ALT2) $(ALT1)=c4 c3 c2 c1 c5
In sub1 VAR is recursive=$(ALT3) $(ALT2) $(ALT1)=c3 c2 c1 c5
In top VAR is recursive=$(ALT2) $(ALT1)=c2 c1 c5
Something which gives
In sub2 VAR is recursive=$(ALT4) $(ALT2) $(ALT1)=c4 c2 c1 c5
In sub1 VAR is recursive=$(ALT3) $(ALT2) $(ALT1)=c3 c2 c1 c5
In top VAR is recursive=$(ALT2) $(ALT1)=c2 c1 c5
would be acceptable but I'd prefer the previous output. I really need only the final expansion, the flavor and the un-expanded value are just an hint of how I think this should work.
Although a more widely applicable solution would be nice, I need only one which works with GNU Make.

I believe the only supported approach for such recursive variables is appending new values with += operator. This is specially handled by make when dealing with recursive variables not to expand variable contents and not to complain about circular reference. You might want to use this approach, while reversing the actual generated values, which may also be defined once, i.e.:
$ cat Makefile
ALT1=a1
ALT2=a2
ALT3=a3
ALT4=a4
VAR=$(ALT1)
$(eval VAR=$(value VAR) $$(ALT2))
define reverse
$(strip $(eval REVERSED=)$(foreach item,$(1),$(eval REVERSED=$(item) $(REVERSED)))$(REVERSED))
endef
VAR2 = $(call reverse,$(VAR))
ALT1=b1
ALT2=b2
ALT3=b3
ALT4=b4
top: sub1
#echo 'In top VAR is $(flavor VAR)=$(value VAR)=$(VAR)'
#echo 'In top VAR2 is $(flavor VAR2)=$(value VAR2)=$(VAR2)'
sub1: VAR += $(ALT3)
sub1: sub2
#echo 'In sub1 VAR is $(flavor VAR)=$(value VAR)=$(VAR)'
#echo 'In sub1 VAR2 is $(flavor VAR2)=$(value VAR2)=$(VAR2)'
sub2: VAR += $(ALT4)
sub2:
#echo 'In sub2 VAR is $(flavor VAR)=$(value VAR)=$(VAR)'
#echo 'In sub2 VAR2 is $(flavor VAR2)=$(value VAR2)=$(VAR2)'
ALT1=c1
ALT2=c2
ALT3=c3
ALT4=c4
Note defining VAR2 which would reverse VAR when expanded. The idea is to add to VAR, but expand VAR2 instead. Output:
$ make
In sub2 VAR is recursive=$(ALT4)=c1 c2 c3 c4
In sub2 VAR2 is recursive=$(call reverse,$(VAR))=c4 c3 c2 c1
In sub1 VAR is recursive=$(ALT3)=c1 c2 c3
In sub1 VAR2 is recursive=$(call reverse,$(VAR))=c3 c2 c1
In top VAR is recursive=$(ALT1) $(ALT2)=c1 c2
In top VAR2 is recursive=$(call reverse,$(VAR))=c2 c1
Edit: Removed unnecessary .SECONDEXPANSION

Related

Reassign Makefile variable for target

This is a simple Makefile:
...
VAR :=
FLAGS := --flag=$(VAR)
target_1: VAR = 1
target_1: $(DEPS_1)
$(CMD) $(FLAGS)
...
target_2: VAR = 2
target_2: $(DEPS_2)
$(CMD) $(FLAGS)
...
I want to reassign variable VAR to make make recalculate value of FLAGS to use it for different targets, is there a way to do something like this?
Don't use simple expansion for FLAGS:
FLAGS := --flag=$(VAR)
is wrong; you want this:
FLAGS = --flag=$(VAR)
If you expand FLAGS immediately, the all the variables are expanded to whatever their value is at that point in the makefile. There is no opportunity to redo that later, because the variable reference is gone: it's replaced with the value.

Generate grid neighborhood as prerequisites in Makefile

I am trying to (ab)use a Makefile to describe a process of data generation. My data are a raster data mosaic tiles covering the world, in chunks of 30°×20° (longitude×latitude), where the tile labels are derived from the GMTED names:
lat := 70S 50S 30S 10S 10N 30N 50N 70N
lon := 180W 150W 120W 090W 060W 030W 000E 030E 060E 090E 120E 150E
tiles := $(foreach LAT,$(lat),$(foreach LON,$(lon),$(LAT)$(LON)))
For some step in the computation, each tile and its 8 neighbors are needed. So for example, one rule is
distances-extended-50S180W.tif: distances-50S180W.tif distances-70S150W.tif distances-70S150E.tif distances-50S150W.tif distances-50S150E.tif distances-30S150W.tif distances-30S150E.tif distances-70S180W.tif distances-30S180W.tif
python extend 50S180W
(so the lons wrap, but for the lats, the extremes have just 6 instead of 9 prerequisites.) How can I make this more concise than generating all these 96 rules externally and including them into the makefile?
Here is a list-oriented method:
lat := southpole 70S 50S 30S 10S 10N 30N 50N 70N northpole
lon := 180W 150W 120W 090W 060W 030W 000E 030E 060E 090E 120E 150E
# rotate-repeat(list,n,len):
# do "n" times: output the first "len" elements of "list", then rotate "list" one left
# "list" must be >= "len"
rotate-repeat = $(if $2,$(wordlist 1,$3,$1) $(call rotate-repeat,$(wordlist 2,$(words $1),$1) $(firstword $1),$(wordlist 2,1000,$2),$3))
# create repeated list of latitudes (including poles)
lat_ := $(foreach _,$(lon),$(lat))
# create repeated and shifted list of longitudes
lon_ := $(call rotate-repeat,$(lon),$(lon),$(words $(lat)))
# triple-cartprod(list1,list2,func)
# create the cartesian product of the two leading triples in the given
# lists and call func with them, then recurse until lists empty
triple-cartprod = $(call $3,\
$(word 1,$1)$(word 1,$2) $(word 2,$1)$(word 1,$2) $(word 3,$1)$(word 1,$2) \
$(word 1,$1)$(word 2,$2) $(word 2,$1)$(word 2,$2) $(word 3,$1)$(word 2,$2) \
$(word 1,$1)$(word 3,$2) $(word 2,$1)$(word 3,$2) $(word 3,$1)$(word 3,$2)) \
$(if $(word 4,$1),$(call triple-cartprod,$(wordlist 2,1000,$1),$(wordlist 2,1000,$2),$3))
# how to make a rule: we throw away rules for the poles and filter
# them out from the prereqs
make-rule = $(if $(findstring pole,$(word 5,$1)),,$(info $(word 5,$1) : $(filter-out %pole,$(wordlist 1,4,$1) $(wordlist 6,9,$1))))
# main:
$(call triple-cartprod,$(lon_),$(lat_),make-rule)
Not sure it's worth the effort but if you use GNU make a slightly simpler and less error prone solution than the full enumeration could be something like:
##################
# utility macros #
##################
# cross-product of two lists; $1: first list; $2: second list
define xp
$(sort $(foreach a,$1,$(foreach b,$2,$a$b)))
endef
#############
# variables #
#############
lat := 70S 70S 50S 30S 10S 10N 30N 50N 70N 70N
ilat := 2 3 4 5 6 7 8 9
lon := 150E 180W 150W 120W 090W 060W 030W 000E 030E 060E 090E 120E 150E 180W
ilon := 2 3 4 5 6 7 8 9 10 11 12 13
# compute north (n-LAT), central (c-LAT) and south (s-LAT) of all latitudes; $1: integer latitude
define ncs
n-$1 := $$(word $$(shell expr $1 + 1),$(lat))
c-$1 := $$(word $1,$(lat))
s-$1 := $$(word $$(shell expr $1 - 1),$(lat))
endef
$(foreach l,$(ilat),$(eval $(call ncs,$l)))
# compute west (w-LON), middle (m-LON) and east (e-LON) of all longitudes; $1: interger longitude
define wme
w-$1 := $$(word $$(shell expr $1 - 1),$(lon))
m-$1 := $$(word $1,$(lon))
e-$1 := $$(word $$(shell expr $1 + 1),$(lon))
endef
$(foreach l,$(ilon),$(eval $(call wme,$l)))
# instantiate tile rule; $1: integer latitude, $2: integer longitude
define tile-rule
$1-$2-point := $$(c-$1)$$(m-$2)
$1-$2-prereq := $$(call xp,$$(n-$1) $$(c-$1) $$(s-$1),$$(w-$2) $$(m-$2) $$(e-$2))
distances-extended-$$($1-$2-point).tif: $$(patsubst %,distances-%.tif,$$($1-$2-prereq))
python extend $$($1-$2-point)
endef
$(foreach la,$(ilat),$(foreach lo,$(ilon),$(eval $(call tile-rule,$(la),$(lo)))))
Latitudes and longitudes are represented not only as labels but also as integers (variables ilat and ilon), which simplifies the computation of north, south, west and east of positions.
The word function is used to convert integers to labels.
The saturation of latitudes (north of 70N is 70N and south of 70S is 70S) are guaranteed by left and right extensions of the lat variable and by the bounds of the ilat variable. Same for the wrapping of longitudes with the extended lon variable and the bounds of the ilon variable.
The tile-rule macro instantiates all rules. The only subtlety here is the use of the sort function in the xp macro. It sorts but also removes duplicates (for extremes).
Demo with a modified tile-rule macro to create the files and print the instantiated rules:
$ cat Makefile
...
.PHONY: all clean
# instantiate tile rule; $1: integer latitude, $2: integer longitude
define tile-rule
$1-$2-point := $$(c-$1)$$(m-$2)
distances-$$($1-$2-point).tif:
#touch "$$#"
$1-$2-prereq := $$(call xp,$$(n-$1) $$(c-$1) $$(s-$1),$$(w-$2) $$(m-$2) $$(e-$2))
distances-extended-$$($1-$2-point).tif: $$(patsubst %,distances-%.tif,$$($1-$2-prereq))
#printf '%s:' "$$#"
#printf ' %s' "$$^"
#printf '\n\tpython extend $$($1-$2-point)\n'
#touch "$$#"
all: distances-extended-$$($1-$2-point).tif
clean::
#rm -f distances-$$($1-$2-point).tif distances-extended-$$($1-$2-point).tif
endef
$(foreach la,$(ilat),$(foreach lo,$(ilon),$(eval $(call tile-rule,$(la),$(lo)))))
$ make clean
$ make -j1 all
make -j1 all
distances-extended-70S180W.tif: distances-50S150E.tif distances-50S150W.tif distances-50S180W.tif distances-70S150E.tif distances-70S150W.tif distances-70S180W.tif
python extend 70S180W
...
distances-extended-50S180W.tif: distances-30S150E.tif distances-30S150W.tif distances-30S180W.tif distances-50S150E.tif distances-50S150W.tif distances-50S180W.tif distances-70S150E.tif distances-70S150W.tif distances-70S180W.tif
python extend 50S180W
...

Makefile issue with multiple variable assignment

I have issues assigning the same variable multiple times in Makefile.
I am making a kind of modular makefile where I only deal with components instead of every source file and include directory.
This first solution works fine
MainRootDir = Components/Main
include $(RootDir)/$(MainRootDir)/Main.mak
Debug_Includes += $(addprefix $(MainRootDir)/,$(Main_Includes))
Debug_ASM_Sources += $(addprefix $(MainRootDir)/,$(Main_ASM_Static))
Debug_CC_Sources += $(addprefix $(MainRootDir)/,$(Main_CC_Static))
Debug_ASM_Sources += $(addprefix $(CfgDir)/, $(Main_ASM_Config))
Debug_CC_Sources += $(addprefix $(CfgDir)/, $(Main_CC_Config))
AdderRootDir = Components/Adder
include $(RootDir)/$(AdderRootDir)/Adder.mak
Debug_Includes += $(addprefix $(AdderRootDir)/,$(Adder_Includes))
Debug_ASM_Sources += $(addprefix $(AdderRootDir)/,$(Adder_ASM_Static))
Debug_CC_Sources += $(addprefix $(AdderRootDir)/,$(Adder_CC_Static))
Debug_ASM_Sources += $(addprefix $(CfgDir)/, $(Adder_ASM_Config))
Debug_CC_Sources += $(addprefix $(CfgDir)/, $(Adder_CC_Config))
However, if I try to standardize the block by defining variables and Re-Assigning new value before adding the software component each time, it stops working:
SwcName = Main
SwcRootDir = Components/$(SwcName)
include $(RootDir)/$(SwcRootDir)/$(SwcName).mak
Debug_Includes += $(addprefix $(SwcRootDir)/,$($(SwcName)_Includes))
Debug_ASM_Sources += $(addprefix $(SwcRootDir)/,$($(SwcName)_ASM_Static))
Debug_CC_Sources += $(addprefix $(SwcRootDir)/,$($(SwcName)_CC_Static))
Debug_ASM_Sources += $(addprefix $(CfgDir)/, $($(SwcName)_ASM_Config))
Debug_CC_Sources += $(addprefix $(CfgDir)/, $($(SwcName)_CC_Config))
SwcName = Adder
SwcRootDir = Components/$(SwcName)
include $(RootDir)/$(SwcRootDir)/$(SwcName).mak
Debug_Includes += $(addprefix $(SwcRootDir)/,$($(SwcName)_Includes))
Debug_ASM_Sources += $(addprefix $(SwcRootDir)/,$($(SwcName)_ASM_Static))
Debug_CC_Sources += $(addprefix $(SwcRootDir)/,$($(SwcName)_CC_Static))
Debug_ASM_Sources += $(addprefix $(CfgDir)/, $($(SwcName)_ASM_Config))
Debug_CC_Sources += $(addprefix $(CfgDir)/, $($(SwcName)_CC_Config))
The issue is that the source files of component Main are defined multiple times.
As if the Adder block is also expanding SwcName with the value Main.
Any idea how to prevent this?
You are using recursively expanded variables (var = ...), which is the default. So make defers the evaluation of the right hand side of your assignments until the variable's value is really needed. Example:
a = 1
b += $(a)
a = 2
b += $(a)
.PHONY: all
all:
#echo $(b)
will print 2 2 instead of the 1 2 you expect because what make actually stored in variable b was $(a) $(a), which it evaluated only before passing the recipe of all to the shell. And at that time a has only one single value: the last one it has been assigned, that is, 2.
You could use simply expanded variables (var := ...), instead:
b :=
a := 1
b += $(a)
a := 2
b += $(a)
.PHONY: all
all:
#echo $(b)
which will print 1 2. Do not forget the apparently useless b :=: it tells make that b is not a recursively expanded variable but a simply expanded one. Different from the default form, the value assigned to a simply expanded variable is evaluated when it is defined, its expansion is not deferred until the variable's value is needed.
But repeating large portions of code as in what you show is not optimal. Indeed, repeating things is frequently needed with humans but almost never optimal with computers. If you are using GNU make, as suggested by tripleee, you could use a kind of user-defined function:
Debug_Includes :=
Debug_ASM_Sources :=
...
# $(1) is the current SwcName
define MY_FUNC
SwcRootDir := Components/$(1)
include $$(RootDir)/$$(SwcRootDir)/$$(1).mak
Debug_Includes += $$(addprefix $$(SwcRootDir)/,$$($(1)_Includes))
Debug_ASM_Sources += $$(addprefix $$(SwcRootDir)/,$$($$(1)_ASM_Static))
...
endef
and then:
$(foreach SwcName,Main Adder,$(eval $(call MY_FUNC,$(SwcName))))
Do not forget the $$ in the macro definition. They are needed. See the GNU make manual for the full explanation.
This is a matter of recursive vs simply expanded variables. I'm going to guess that Debug_Includes is not initialized with a := earlier on, which means it defaults to be a recursively expanded variable. Therefore it is set to $(RootDir)/$(SwcRootDir)/$(SwcName).mak, and the internal variables are only expanded when it is referenced (at which point SwcRootDir will be Components/$(SwcName)).
Try initializing your variables with := at the top of your file to make them simple. Then they will be assigned values right away, and the internal variables will be set to what they were at the time of the definition.

Makefile: If string ends with other string

I'm trying to have rule that if VAR ends with ENDS_WITH it does X else it does Y. Now, I was able to kinda achieve this with the following:
VAR := Hello, World
ENDS_WITH := rld
endswith:
ifeq ($(findstring $(ENDS_WITH)potato,$(VAR)potato),)
#echo "$(VAR) doesn't end with $(ENDS_WITH)"
# Do X
else
#echo "$(VAR) ends with $(ENDS_WITH)"
# Do Y
endif
However, this assumes neither string contains potato otherwise it will have strange behavior. (Also it's kinda a hack)
What would be the correct way to go about achieving this?
You can use this:
$(patsubst %$(ENDS_WITH),,$(lastword $(VAR)))
This will expand to an empty string if VAR ends with the value of the variable ENDS_WITH, else the non-empty string. We have to use lastword here because patsubst works on each word individually but we only care about the last one.
So:
endswith:
ifeq ($(patsubst %$(ENDS_WITH),,$(lastword $(VAR))),)
#echo "$(VAR) ends with $(ENDS_WITH)"
# Do Y
else
#echo "$(VAR) doesn't end with $(ENDS_WITH)"
# Do X
endif

Target-specific assignments in Makefile

I'm trying to modify variable inside 2nd-level target foo (Option 2). I'd expect output to be a b c, but I see a c, i.e., VAR += b is never executed. It behaves as if VAR is local for all target. But at the same time with Option 1, I see that b is appended into VAR. I suspect it is caused by := assignment in the target, but I couldn't find anything specific about that.
VAR = a
all: VAR += b
all: foo
# Option 1. with this line output is "a b z"
# foo: VAR += z
# Option 2. with this line output is "a c"
# foo: VAR := $(VAR) c
foo:
#echo $(VAR)
The explanation is that expressions with := assignments in target specific variables are evaluated by Make right at the time of reading the makefile and not when executing recipes for targets.
This seems to be not explained at all in the corresponding chapter of Make manual

Resources