Makefile recursive install? - makefile

Suppose I have the following: A chain of dependencies, each of which may or may not be present, and each of which installs in the exact same way (with the exception being the string name of the dependency). In order to get rid of repetitive makefile code, I prototype the following function:
define install_utility =
$(1):
# recursion
$(foreach bar, $(1)_dependencies,$(eval $(call install_utility,$(bar)))
ifeq(`which $(1)`,) # check for existence of dependency
echo will install $(1) # show me make is executing expected commands
endif
endef
foo_dependencies=A B
foo=foo
eval $(call install_utility,$(foo))
# Expected results:
will install A
will install B
will install foo
# Actual result: no error message, just:
... (infinite loop that prints nothing)
And when I run, I get the following error: an infinite loop.
This seems like really simple functionality. However, I am having trouble getting it to work in make. Is there a manner in which I am "expected" to do this in make?
Have tinkered a bit... and getting a variety of errors depending on whether I tab out the recursion:
$(1):
$(foreach bar, ...
ifeq(`which $(1)`,)
...
# error messages:
make: *** no rule to make target '$(foo_dependencies)
ifeq(`which', needed by 'foo'. Stop.

I don't really understand what you're trying to do... it looks extremely non-make-like to me.
But, this definitely will not work:
ifeq(`which $(1)`,)
If you want this to be part of the recipe, you have to indent it with a TAB character and write it in shell syntax, not make syntax.
If you want this to be part of the makefile (not the recipe) you need to write it in correct make syntax: first you have to include a space between the ifeq and the (. Second, make does not support backquotes. If you want to run a shell command you have to use the $(shell ...) function.

Related

Make: evaluating result of call: recipe commences before first target

I have several files for my GNU make setup. In this.mk, I have
define this_template
THIS = $(1)
THIS_DIR = $(2)
THIS_LIBNAME = $(3)
THIS_EXTERNAL_DIRS = $(4)
...
endef
In my Makefile, I have
include this.mk
... # define VAR1 and VAR2
include util/make.mk
...
util/make.mk contains one line:
$(eval $(call this_template,UTIL,$(VAR1),plutil,$(VAR2)))
However, when I run make, I get
util/make.mk:1: *** recipe commences before first target. Stop.
Reading up on other questions that relate to this error message, what I'm understanding is that this error is caused by evaluating a string which begins in a way that looks like it's inside of a recipe. However, what I'm evaluating does not.
This error means that (a) the line begins with a TAB (or more specifically, with the character defined as .RECIPE_PREFIX if your version of GNU make supports it), and (b) it is not recognized as any sort of make command such as a rule introduction, etc.
Given what you've shared with us here, that cannot happen. So there must be something going on that you haven't shared with us. Maybe one of the other included makefiles is modifying the this_template variable to contain something else.
The way to debug eval problems is always the same no matter what they are: change the eval to info so that make will print out what it will evaluate. This usually makes it pretty obvious what the problem is. So use:
$(info $(call this_template,UTIL,$(VAR1),plutil,$(VAR2)))
$(eval $(call this_template,UTIL,$(VAR1),plutil,$(VAR2)))
and see what make shows you.

When should a call to *eval* be evaluated in a make recipe

I have a few software projects which are distributed as RPMs. They are versioned using semantic versioning to which we affix a release number. Using the regular conventions, this is MAJOR.MINOR.PATCH-REL_NUM. Though beyond the scope of this article, the release numbers are stored in git. The release target in the makefile looks something like this:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
# Although the third step, this was re-ordered to step 1
$(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
make rpm RPM_RELEASE_NUM=$(RELEASE_NUMBER)
While debugging, I eventually discovered that, although the call to eval was the third step in the recipe, it was actually being evaluated first! This is why the RPM always had a release number one less than the number I was watching get pushed to the remote.
I have done much googling on this and I haven't found any hits that explain the order of evaluation with regard to eval when used in recipes. Perhaps it isn't even with respect to eval but functions in general. Furthermore, I haven't found verbiage on this in the GNU manuals for make either (if it's there, kindly point out what chapter). I've worked around the problem so it's not a bother, I'm just wondering, is this expected and if so, why?
The missing bit, that no one above is getting, is simple: when make is going to run a recipe it expands all lines of the recipe first, before it starts the first line. So:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
# Although the third step, this was re-ordered to step 1
$(eval RELEASE_NUMBER=$(shell $(BLD_ROOT)/path/to/rel_num.txt))
make rpm RPM_RELEASE_NUM=$(RELEASE_NUMBER)
when make decides to run the release target it first expands all the lines in the recipe, which means the eval is expanded, then it runs the resulting lines. That's why you're getting the behavior you're seeing.
I don't really see why you need to use eval here at all; why not just use:
release:
$(MAKE) clean
$(BLD_ROOT)/tools/incr_rel_num
$(MAKE) rpm RPM_RELEASE_NUM="$$(cat $(BLD_ROOT)/path/to/rel_num.txt))"
(BTW, you should never use bare make inside your makefiles; you should always use $(MAKE) (or ${MAKE}, same thing).
The $(eval ...) function
generates a fragment of make-sytax which becomes part of the parsed makefile.
The makefile is parsed entirely before any recipes are executed and when recipes
are executed all make-statements, make-expressions and make-variables have been
evaluated away.
So it does not make sense to consider an $(eval ...) call as being one
of the lines of a recipe. It might generate values that are used in the make-expansion
of the recipe, but if so then this happens when the makefile is parsed, before the recipe is run.
Thus in your example, the line:
$(eval RELEASE_NUMBER=$(shell $(BLD_ROOT)/path/to/rel_num.txt))
which I assume should really be:
$(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
is evaluated when the makefile is parsed, and let's say it results in the
make-variable RELEASE_NUMBER acquiring the value 1.0, because, when the
makefile is parsed, the file $(BLD_ROOT)/path/to/rel_num.txt) contains
1.0. In that case your recipe:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
$(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
make rpm RPM_RELEASE_NUM=$(RELEASE_NUMBER)
will resolve to the like of:
release:
make clean
some_build_dir/tools/incr_rel_num
make rpm RPM_RELEASE_NUM=1.0
You will observe when make runs the recipe that it prints no line that
is "the expansion of" $(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt)),
because there is no such thing in the recipe. It doesn't matter that:
some_build_dir/tools/incr_rel_num
is presumably a command that writes, say, 1.1 or 2.0 in the file some_build_dir/path/to/rel_num.txt.
That action simply has no effect on the recipe. Nothing that executed in the recipe
can change the recipe.
$(eval ...) has no business in your recipe. What you want to achieve is simply:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
RELEASE_NUMBER=$$(cat $(BLD_ROOT)/path/to/rel_num.txt) && \
make rpm RPM_RELEASE_NUM=$$RELEASE_NUMBER
where $$ is what you do in a makefile to escape $ and, in this case,
leave it for the shell when the recipe is executed.
This recipe expands to 3 shell commands executed in sequence:
$ make clean
$ some_build_dir/tools/incr_rel_num
$ RELEASE_NUMBER=$(cat some_build_dir/path/to/rel_num.txt) && \
make rpm RPM_RELEASE_NUM=$RELEASE_NUMBER
and might as well be simplified further to:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
make rpm RPM_RELEASE_NUM=$$(cat $(BLD_ROOT)/path/to/rel_num.txt)
You are correct, there are multiple levels of evaluation. The content on what is inside eval is evaluated a first time before that the function is actually called. If you want the content of eval to be evaluated at the time eval is called, you have to escape the $ sign by putting it twice, like this :
$(eval RELEASE_NUMBER=$$(shell $(BLD_ROOT)/path/to/rel_num.txt))
To view what is really inside eval at the time it's called you can use the same syntax with info instead of eval :
$(info RELEASE_NUMBER=$$(shell $(BLD_ROOT)/path/to/rel_num.txt))
Now I'm not sure about the part which is evaluated too soon so the $ symbols that I doubled may not be the good one(s), but using the info function will help you to find the correct command.

ifndef include guard in Gnu Make breaks on nested conditional

I’m trying to implement include guards in Gnu Make. In this Makefile, the first inclusion is OK, while the second one fails with an error.
ifndef INCLUDED
INCLUDED = 1
$(info Including)
define macro
ifneq ($(1),)
define inner_macro
macro content...
endef
else
define inner_macro
endef
endif
endef
endif
The same effect can be simulated by explicitly giving INCLUDED = 1 before the inclusion, e.g. on command line.
Gnu Make 4.1 under Gentoo says Makefile:14: *** missing separator. Stop., while Gnu Make 3.81 under Debian Wheezy says Makefile:14: *** extraneous `endef'. Stop.. On the first inclusion, they both say:
Including
make: *** No targets. Stop.
If I try $(eval $(call macro,whatever)) after the first inclusion, it defines inner_macro as expected.
I used make INCLUDED=1 and make commands respectively to get the described behavior.
The same happens when I clear the environment and disable built-in rules and variables: env -i make -rR INCLUDE=1. When I use -p to dump the database, without INCLUDED=1, the macro is defined as it should be, but with INCLUDED=1, empty inner_macro is defined. This is consistent across both the versions of Make. This hints me that when the condition is false, Make parses the Makefile differently and thinks the else inside macro’s definition belongs to the ifndef. Other condition types behave all the same.
If I remove both the definitions of inner_macro, the problem goes away.
I read the manual pages info make conditional\ syntax and info make multi-line (formerly defining), but I found no caveat there and I still think I am doing nothing wrong.
Am I correct with my conclusions?
Is this a bug in Make, or am I invoking undefined behavior?
How should I implement include guards in Gnu Make?
That's a bug. Report it on Savannah.
There's something wrong with the tracking of nested define/endef inside a not-taken ifdef/ifndef condition. If you don't use nested define/endef then it works; for example (obviously you may not be able to do this in your environment):
ifndef INCLUDED
INCLUDED = 1
$(info Including)
define macro
ifneq ($(1),)
inner_macro = macro content...
else
inner_macro =
endif
endef
endif

Make 3.82 - Backward incompatibility issue?

I'm having some issues after try to run some small Makefile with make 3.82.
error:
[me#localhost make]$ make
Makefile:3: *** empty variable name. Stop.
This works with make 3.81, but not with the new one. I know there are some backward compatibilities with the old version.
I have two Makefiles, a base one and main one.
This is my Makebase
define TestFile
ifeq ($$(shell test $(1) $(2) || echo 1),1)
$$(error $(2) mmm, not found)
endif
endef
define CheckIt
$(eval $(call TestFile,-d,$(1)))
endef
define CheckDir
p := $(foreach d,$1,$(call CheckIt,$d))
endef
define SomeCheck
$(call CheckDir,$(1))
endef
This is my Makefile
include Makebase
$(call SomeCheck, ~/test/make)
As I said, it works fine in make 3.81.
Any help will be appreciated.
Thanks
BR
So, I have no idea what this was intended to do in GNU make 3.81. As Etan points out, when I run your makefile with GNU make 3.81 I get this error:
make: *** No rule to make target `=', needed by `p'. Stop.
That's because a call function cannot expand to a variable assignment, so make interprets the p := as if it were p: = (that is, a target p with a prerequisite of =). I don't see how this is actually what you want. If you don't see this error all I can assume is that somewhere in your makefile, someone has declared a recipe with target = (ugh!!)
In GNU make 3.82 I see the empty variable name message. The reason for this is that GNU make 3.82 introduced parser enhancements which caused some backwards-incompatibility. The NEWS file gives this warning:
As a result of parser enhancements, three backward-compatibility issues
exist: first, a prerequisite containing an "=" cannot be escaped with a
backslash any longer. You must create a variable containing an "=" and
use that variable in the prerequisite.
An unnoticed side-effect of this is that an equals sign with no value before it in the prerequisites list is now considered a target-specific variable where the variable name is empty, whereas before it was assumed to be a target since it didn't meet the requirements for a variable assignment. I am not sure this is a bug... in general I'm not a fan of "tricking" the parser with odd corner cases so I actually prefer the newer behavior.
This entire define is quite bogus:
define CheckDir
p := $(foreach d,$1,$(call CheckIt,$d))
endef
Why? Because the CheckIt user-defined function contains nothing but an eval statement. But eval statements are expanded and the results parsed by make, so they always expand to the empty string. Therefore, the entire foreach loop expands to the empty string. Therefore even if this were interpreted as you (apparently) intended by make, it would always simply expand to:
p :=
which doesn't seem very useful. If you change the above define to simply:
define CheckDir
$(foreach d,$1,$(call CheckIt,$d))
endef
then it will always work, and you won't see these weird problems.
I'm not going to comment on how bogus this makefile is in general... :)

Conditional inclusion/execution of Makefile commands in a variable?

There is a Makefile that I am using, which I got from somewhere, and which is quite big. I have also found some things that I'd like changed occasionally in the makefile - and the easiest way to do that for me is to define (or not) a (switch) variable (say, OVWRCHOICE) at the start of the makefile; and then later on in the makefile code, do something like:
ifdef OVWRCHOICE
MYOPT = override
....
endif
... which is all dandy and fine.
The thing is, eventually I also need to change parts in the "override" part as well, so I'd like to have it at the start of the file. So, as this "override" part contains several make commands -- I tried to use define, to have a variable which will contain the commands (which would be executed at the ifdef OVWRCHOICE... part).
So I arrived at this simple example:
# uncomment as needed;
OVWRCHOICE = YES
define SET_OVWRCHOICE
MYOPT = override
endef
export SET_OVWRCHOICE
# ... many lines of code ...
MYOPT = default
# ... many lines of code...
# without indent: Makefile:18: *** missing separator. Stop.
# with tab indent: Makefile:18: *** commands commence before first target. Stop.
ifdef OVWRCHOICE
$(SET_OVWRCHOICE)
endif
all:
#echo $(MYOPT)
... which fails with the errors noted. Of course, if I use the first snippet in the post instead, all runs fine, and make prints out the expected result: "override".
How would I go about in achieving something like this? Not sure if "inclusion" or "execution" of "Makefile commands" are even the right terms in this context; so I have a hard time in finding a starting point for a search :)
Got it - it is described in Eval Function - GNU `make'; the right construct is:
ifdef OVWRCHOICE
$(eval $(call SET_OVWRCHOICE))
endif
Hope this helps someone,
Cheers!
Oh well, didn't really know where to archive this snippet, so back to this old question of mine :) this is off topic for OP; but here goes:
To test how environment variables are processed by a makefile, here is a simple example:
Foo=something
all :
ifdef DEBUG
#echo "Debug defined"
else
#echo "Debug NOT defined"
endif
... and here is the test for it:
$ make
Debug NOT defined
$ make DEBUG
make: *** No rule to make target `DEBUG'. Stop.
$ DEBUG make
DEBUG: command not found
$ DEBUG= make
Debug NOT defined
$ DEBUG=1 make
Debug defined
... so obviously, the right syntax to set that variable inside the makefile from the command line is: "DEBUG=1 make"

Resources