How does gmake parse and execute template definitions - gnu-make

So, I have a make template. I invoke it like:
$(eval $(call PRIVATE_LIBRARY_TEMPLATE,privatelib1,64))
And it is defined like:
define PRIVATE_LIBRARY_TEMPLATE
# Evaluate the condition multiple times because of way make processes templates
$(if $(2)=='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),32)))
$(if $(2)=='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),64)))
$(if $(2)!='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),$(2))))
# More stuff that doesn't matter here
endef
or like:
define PRIVATE_LIBRARY_TEMPLATE
# Evaluate the condition multiple times because of way make processes templates
$(if $(2)=='',$(call LIBRARYBUILD_TEMPLATE,$(1),32))
$(if $(2)=='',$(call LIBRARYBUILD_TEMPLATE,$(1),64))
$(if $(2)!='',$(call LIBRARYBUILD_TEMPLATE,$(1),$(2)))
# More stuff that doesn't matter here
endef
previously it was defined as:
define PRIVATE_LIBRARY_TEMPLATE
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$(1),32))
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$(1),64))
before I add the $(if, $(1) is passed to the LIBRARYBUILD_TEMPLATE intact, but once I add the $(if, $(1) becomes an empty string.
I've tried various combinations of $$ $(eval $$eval etc, but there is something fundamental I'm just not understanding about the way gmake is parsing this template definition.
What I am trying to do is to make $(2) optional in this template, and use it if provided, or if not provided build both 32 and 64 bit libraries.
How is the template definition being initially parsed, and then evaluated.

First off the function $(if condition,then-part,else-part) is not the same as the other conditionals like ifeq. The $(if) function condition is simply checked for empty string or not (c.f. manual):
If it expands to any non-empty string, then the condition is considered to be true. If it expands to an empty string, the condition is considered to be false.
When it comes to $(eval $(call ...), things can become tricky in which order things must be evaluated. I usually think like this:
If the evaluated result of an operation depends on an argument (like $1), then the operation needs to be delayed with secondary expansion.
So in your case I think this is what you want:
.SECONDEXPANSION:
define LIBRARYBUILD_TEMPLATE
$$(info >> LIBRARYBUILD_TEMPLATE: $1 $2)
endef
define PRIVATE_LIBRARY_TEMPLATE
$$(info > PRIVATE_LIBRARY_TEMPLATE $1 $2)
ifeq ($2,)
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$1,32))
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$1,64))
else
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$1,$2))
endif
endef
$(eval $(call PRIVATE_LIBRARY_TEMPLATE,privatelib1,64))
$(eval $(call PRIVATE_LIBRARY_TEMPLATE,privatelib2,))
Which gives the output:
> PRIVATE_LIBRARY_TEMPLATE privatelib1 64
>> LIBRARYBUILD_TEMPLATE: privatelib1 64
> PRIVATE_LIBRARY_TEMPLATE privatelib2
>> LIBRARYBUILD_TEMPLATE: privatelib2 32
>> LIBRARYBUILD_TEMPLATE: privatelib2 64

Let's use this definition of PRIVATE_LIBRARY_TEMPLATE
define PRIVATE_LIBRARY_TEMPLATE
# Evaluate the condition multiple times because of way make processes templates
$(if $(2)=='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),32)))
$(if $(2)=='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),64)))
$(if $(2)!='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),$(2))))
endef
It is instructive to look in detail at what make does when it encounters
$(eval $(call PRIVATE_LIBRARY_TEMPLATE,privatelib1,64))
Clearly, before it can expand the $eval, make must first expand the $call:
1 is set to privatelib1
2 is set to 64
PRIVATE_LIBRARY_TEMPLATE is now expanded.
First off, a $(if …) needs expanding:
Make looks at the condition in $(if $(2)=='',$(call LIBRARYBUILD_TEMPLATE,$(1),32)), and so expands $(2)==''. You will note that 64=='' not an empty string, and so is considered true. To complete the expansion of the $(if …) , make thus chooses the true branch and goes on to expand $(call LIBRARYBUILD_TEMPLATE,$(1),32)
1 becomes privatelib1
2 becomes 32
$(call LIBRARYBUILD_TEMPLATE,privatelib1,32) becomes some text. No idea what, but since it will eventually be passed to $eval it must be valid make syntax. Let's assume it is something simple like LIB_privatelib1_32 := 1.
A second $(if …) is similarly expanded.
A third $(if …) is similarly expanded.
For the sake of argument, let's say the final expansion of the $(call PRIVATE_LIBRARY_TEMPLATE,…) is this text:
LIB_privatelib1_32 := 1
LIB_privatelib1_64 := 1
LIB_privatelib1_64 := 1
These three lines are passed to $eval.
As a side-effect, three new simple variables are defined.
The expansion of the $(eval …) though is empty.
Phew.
One obvious mistake here is that == is not valid make syntax.
Truthyness is merely whether a string has characters in it.
You probably want something like:
$(if $2,$(eval $(call LIBRARYBUILD_TEMPLATE,$1,32)))
$(if $2,$(eval $(call LIBRARYBUILD_TEMPLATE,$1,64)))
$(if $2,,$(eval $(call LIBRARYBUILD_TEMPLATE,$1,$2)))
(check out that last one for negations.)

Related

GNU Make dynamically created variable name

This is my make recipe, I'm passing nonle value *, although bamboo_nonle_password variable is having password, still function is failing
validatevar.%: $(if $(IS_BAMBOO),)
#echo "\n################################################################################"
#echo ">>> validatevar"
#echo ">>> Start: Validate bamboo variables "
ifndef bamboo_$*_password
#echo "Bamboo variable bamboo_$*_password is empty"
exit 1
endif
ifndef bamboo_$*_server
#echo "Bamboo variable bamboo_$*_server is empty"
exit 1
endif
#echo "<<< Done: validatevar"
#echo "################################################################################\n\n"
You crazy person :)
Make will expand the recipe just before it wants to execute it.
At this point make knows what the % corresponds to,
and so can ensure $* is set appropriately.
Unfortunately, the ifndef…endif pair is evaluated as the makefile is read,
not as the recipe is expanded.
This suggests the answer:
use macros.
Maybe $(origin …) or $(flavor …) in something like
validatevar.%:
#echo
$(if $(filter undefined,$(origin bamboo_$*_password)),#echo "Bamboo variable bamboo_$*_password is undefined")
$(if $(filter undefined,$(origin bamboo_$*_password)),exit 1)
Naturally you can tidy this up a bit.
Maybe:
die-if-undefined = $(if $(filter undefined,$(origin $1)),$(error "$1" is undefined))
validatevar.%:
$(call die-if-undefined,bamboo_$*_password)
$(call die-if-undefined,bamboo_$*_server)
#echo
#echo
Two things to note:
The whole of the recipe is expanded in one go, before any of the lines are executed.
The upshot of this is that it doesn't matter where you expand the $(error) assertions.
I have put them at the top.
ifndef also detects empty variables, as well as undefined ones. You may wish to adjust die-if-undefined to take account of this (and rename it to die-if-empty).
(This would all be a lot easier if make would only take a --error-undefined-variables parameter,
just to match the existing --warn-undefined-variables parameter.)

GNU make pattern rules with different file base names

I have a data processing job that I would like to automate with Make. Hundreds of files need to be processed, in several steps.
Unfortunately, the base name will change for at least one of the steps, but it would be easy to write these dependencies into a separate file that then is included.
However, I'd like to avoid also writing the build instructions (which are quite complicated) for all these files separately.
I envisage something along these lines:
# automatically generated rules, included into make file
dir1/test.bb: dir2/test_other_name.aa
# (many more rules like the above, linking xxx.bb to yyy.aa)
# pattern rule
%.bb: %.aa
# build step using $# $>
What I would like is the pattern rule to provide the rules, and the explicit rule defining the dependencies. Can something like this be achieved?
When make's noddy patterns don't cut the mustard,
just write out the rules explicitly.
(This has the happy side effect of not using pattern rules.)
Let's say you have a function src-to-target which will generate the target filename (i.e., $(call src-to-target,dir2/test_other_name.aa) expands to dir1/test.bb.
Also, you have a list of sources in ${srcs}, and ${recipe} is a list of shell commands using $#, $< etc.
define src-to-target = ... # $1:source
define recipe =
echo Building $# from $<
⋮
endef
define generate-rule = # $1:source
target := $(call src-to-taget,$1)
targets += $${target}
$${target}: $1 ; $${recipe}
endef
$(foreach _,${srcs},$(eval $(call generate-rule,$_)))
.PHONY: all
all: ${targets} ; : $# Success
The $(foreach ...) does all the work here.
So, looking at that in painful detail,
First expand ${srcs}
Set $_ to the first in the list (dir2/test_other_name.aa say)
Expand $(call generate-rule,$_)
Expand $(call generate-rule,dir2/test_other_name.aa)
$1 is set to dir2/test_other_name.aa, and the expansion of $(generate-rule) follows, leading to this block of text
target := dir1/test.bb
targets += ${target}
${target}: dir2/test_other_name.aa ; ${recipe}
As a side effect, $(eval) swallows the above text. The expansion of the $(eval) though is empty.
$_ is set to the next source file.
Wash, lather, rinse, repeat
Once the $(foreach) is complete,
${targets} contains the complete list of targets.
Parallel safe too.
What's not to like?

Get parent dir, grandparent dir and so on in gnu make?

I have a variable path, for example this path from $(shell pwd):
C:\a\b\c\d\e\f
what i want to get is this and save it in a variable:
C:\a\b\c\d\e
C:\a\b\c\d
C:\a\b\c
C:\a\b
C:\a
C:\
how to do it in gnu make? And how to stop if there is no more parent (reached C:)
Ok, I'm going to switch into posix path mode.
No appologgies.
Life is too short to mess around with windows paths when cygwin is available.
(I will note though that $(dir) recognises backslashes.)
So, a function.
You just spit out the argument,
and then call the function again but this time with the last path component snipped off.
Something like
parents = $1 $(call parents,$(dir $1))
First problem:
$(dir a/b/c/d) returns a/b/c/.
Fine,
except that $(dir /a/b/c/) just gives you a/b/c/ again.
You need to strip that final slash before the call:
parents = $1 $(call parents,$(patsubst %/,%,$(dir $1)))
OK.
Problem now is this recursive call never terminates the sequence.
We need to stop calling parents once $1 has no slashes in it.
Several methods come to mind.
One way is to transliterate / into
(that's the job of $(subst …)),
then stop if the resulting number of words ($(words …)) is 1 ($(filter …)):
parents = \
$1 \
$(if $(filter-out,1,$(words $(subst /, ,$1))), \
$(call parents,$(patsubst %/,%,$(dir $1))))
(Hope I've got that nesting right.) Giving:
$ cat Makefile
parents = \
$1 \
$(if $(filter-out 1,$(words $(subst /, ,$1))), \
$(call parents,$(patsubst %/,%,$(dir $1))))
$(error [$(call parents,/a/b/c/d/e/f)])
$ make
Makefile:6: *** [/a/b/c/d/e/f /a/b/c/d/e /a/b/c/d /a/b/c /a/b /a ]. Stop.
:-)
Footnote: Dunno what you are trying to achieve,
but I reckon there is probably a more make-like way of doing it!

using variables in multiline define in gnumake

The Makefile:
define t1
a = $1
$(info $(a) -- $(1))
endef
list = x y z
$(foreach v,$(list),$(eval $(call t1,$(v))))
The output
-- x
x -- y
y -- z
The problem is delaying of value of 'a' by one invocation. Even a := $1 shows the same issue.
Any quick fixes ?
This is gnu make 3.81 .
This is one of the confusing things about using call and eval together: when to add extra quoting. The problem is that the call function is invoked first, and that will expand the argument once. Then eval is called and that evaluates its argument (the output from call).
In your example, the info function is being evaluated by call, the first time, and so in that situation a has not been set yet (because it's not set until the eval function runs). So you need to escape that function, and the expansion of a:
define t1
a = $1
$$(info $$(a) -- $(1))
endef

Call command in Unix Make

I'm trying to understand a makefile, can anyone tell me what the following line does:
#echo cp -f --preserve=mode,timestamps $(call $1,$<) $(call $1,$#)
Especially I don't get what is the significance of $1 and call here.
There's no way to know what this does, since it's completely out of context.
It looks to me like this value is supposed to be passed to another instance of $(call ...). So for example if your makefile has:
QUOTE = '$1'
COPY = #echo cp -f --preserve=mode,timestamps $(call $1,$<) $(call $1,$#)
then later you would see something like:
foo: bar ; $(call COPY,QUOTE)
The first call would expand to the COPY value with $1 replaced with QUOTE, so it would be:
#echo cp -f --preserve=mode,timestamps $(call QUOTE,bar) $(call QUOTE,foo)
then that gets expanded, and you end up with:
#echo cp -f --preserve=mode,timestamps 'bar' 'foo'
But without more information we can't say more.
The 'call' command is a GNU extension in GNUmake; it is not supported in POSIX make or most other makes. It basically expands a macro with arguments. Something like
$(call A,b,c,d)
will expand the macro A with the arguments b, c, and d. The arguments are assigned to the temporary macros $(1), $(2), ... which may be present in the definition of A
See the GNUmake documentation

Resources