GNU Make: How to call $(wildcard) within $(eval) - makefile

I'm trying to create a generic build template for my Makefiles, kind of like they discuss in the eval documentation.
I can't seem to get the wildcard function to work within an eval. The basic code I'm having issues with looks like this.
SRC_DIR = ./src/
PROG_NAME = test
define PROGRAM_template
$(1)_SRC_DIR = $(join $(SRC_DIR), $(1)/)
$(1)_SRC_FILES = $(wildcard $$($(1)_SRC_DIR)*.c)
endef
$(eval $(call PROGRAM_template, $(PROG_NAME)))
all:
#echo $(test_SRC_DIR)
#echo $(test_SRC_FILES)
#echo $(wildcard $(wildcard $(test_SRC_DIR)*.c)
When I run make with this, the output is
./src/test
[correct list of all .c files in ./src/test/]
Basically, the wildcard call within PROGRAM_template is not being eval'd as I expect it. The call results in an empty list.
The join call is being eval'd correctly though.
So, what am I doing wrong? My guess is that
$$($(1)_SRC_DIR)
is not correct, but I can't figure out the right way to do it.
EDIT
Once this was solved, it didn't take long for me to hit another issue with eval.
I posted it as a new question at
Workaround for GNU Make 3.80 eval bug

You need to double escape virtually all of the functions and variables when you use eval. In most cases, the only things that don't need to be double-escaped are function arguments (because the call function will fully expand them). In this case, you technically don't need to double-escape join or SRC_DIR either, but it will simplify your life if you just always double-escape all variables and functions when using eval.
The reason you need the double escapes is that expansion happens twice when using eval. The eval function itself performs expansion, and then expansion is done again when the block is finally parsed as makefile syntax (i.e. when it is actually evaluated).
The way you've got it written, wildcard is invoked on the string literal $( test_SRC_DIR)*.c. If you want, you can see this for yourself by replacing wildcard with info in your version and see what happens.
You need to hold off on actually invoking wildcard until the second expansion, so that it's argument is the result of the expansion of $(test_SRC_DIR).
Try this:
SRC_DIR = ./src/
PROG_NAME = test
define PROGRAM_template
$(1)_SRC_DIR = $$(join $$(SRC_DIR),$(1)/)
$(1)_SRC_FILES = $$(wildcard $$($(1)_SRC_DIR)*.c)
endef
$(eval $(call PROGRAM_template,$(PROG_NAME)))
all:
#echo $(test_SRC_DIR)
#echo $(test_SRC_FILES)
#echo $(wildcard $(test_SRC_DIR)*.c)
EDIT: After posting this, I thought I'd better test it out to make sure it actually works. In doing so, I discovered another problem. You should avoid putting spaces between the comma and argument when calling functions. It causes a literal space character to be prepended to the argument that is passed to the function and leads to unintended results. I've removed the spaces after the commas in the function calls in my version (while this isn't a problem for the call to join, I removed the space there as well just because it's a good habit to get into).

Related

Makefile, applying a function to a list

In a Makefile, I am trying to populate lists by transforming items from an initial list.
As in my real code, those transformations are non-trivial, I try to use a define... endef construct, to apply to each element of the initial list, containing the logic of what I want to accomplish. Then, I apply this "function" using a foreach containing a eval and call.
But something weird happens: it seems that the last element of the list is not treated by the "function".
Here is a MWE Makefile:
libraries :=
define Function
libName = lib$(1)
libraries += $(libName)
# [...more things...]
endef
libs = a b c
$(foreach lib,$(libs),$(eval $(call Function,$(lib))))
all:
$(foreach lib,$(libs),$(lib))
#echo $(libraries)
And the result of running the command make:
a b c
liba libb
I expected the second line to have an extra item libc at its end...
What did I do wrong? What did I misunderstood?
You missed the fact that the argument you provide is expanded twice: first by call, then again as part of the eval.
You can get a better idea of what is happening with eval by replacing it with info:
$(foreach lib,$(libs),$(info $(call Function,$(lib))))
This will show you the text that eval is evaluating. You'll see that here:
libraries += $(libName)
libName is being evaluated by call, before eval sees it. So it expands to the previous run's setting of libName (or the empty string in the first run).
You need to examine your define and for every variable that is a call parameter like $(1) you use it like this, so call expands it, and for every other variable or function reference you probably want to escape it with $$ so that call doesn't expand it and it's left to eval to expand:
define Function
libName = lib$(1)
libraries += $$(libName)
# [...more things...]
endef

Makefile expanding variables inside conditionals depends on order of definition

I want to define a variable differently depending on another variables value in a makefile. I thought using conditionals would solve the problem, like this in the makefile:
ifeq ($(BOOT_FLAG),installed)
BOOT_TEST=$(BOOT_FLAG)
else
BOOT_TEST=no
endif
BOOT_DEFINE=$(BOOT_FLAG)
BOOT_FLAG=installed
.PHONY: all
all:
#echo $(BOOT_TEST)
#echo $(BOOT_DEFINE)
I expected the output to be:
installed
installed
but I got this instead:
no
installed
apparently the ifeq does not expand the BOOT_FLAG to installed
but setting of the BOOT_DEFINE variable manages to expand it correctly.
I read in the manual that:
"make evaluates conditionals when it reads a makefile. Consequently, you cannot use automatic variables in the tests of conditionals because they are not defined until commands are run"
but the BOOT_FLAG is not an automatic variable. Also if I move the definition of BOOT_FLAG to before the ifeq, then it works as I want it. However, I want to keep the current order of the definitions (and I don't understand why make does an exception to the order independence of the definitions when using conditions)
The answer is right there in the statement you quoted:
make evaluates conditionals when it reads a makefile.
Since make has evaluated the conditional when it read that line in the makefile, and the variable has not been defined when it read that line, there's no way that variables set after the conditional can take effect.
Just because the documentation lists one consequence of this behavior (the one that most people get confused by) doesn't mean that this is the only consequence of this behavior.
However, I want to keep the current order of the definitions
You can't.
(and I don't understand why make does an exception to the order independence of the definitions when using conditions)
It would be virtually impossible, and even if it could be done the resulting behavior would be almost indecipherable except in the most trivial situations. If you don't believe me, try to write down an algorithm describing how that could work. Remember to consider things like simple variable assignments, nested conditionals, variables used in target and prerequisite lists, variables that are intentionally reset in different parts of makefiles, etc.
ETA You could do it, by putting the ifeq into a define variable then using eval later, after BOOT_FLAG is set, to expand it. Seems gross to me but...
This is because makefile is evaulating the ifeq as it parses the file.
So when it gets to the ifeq..., then BOOT_FLAG is yet not set, so BOOT_TEST = no
Then you set BOOT_FLAG.
Then once all the variables are parsed, makefile will go through and run your rule - so in this case BOOT_DEFINE is evaluated to $(BOOT_FLAG) final value of installed
Try this:
$(info start - BOOT_FLAG=$(BOOT_FLAG))
ifeq ($(BOOT_FLAG),installed)
BOOT_TEST=$(BOOT_FLAG)
else
BOOT_TEST=no
endif
$(info after if - BOOT_FLAG=$(BOOT_FLAG))
BOOT_DEFINE=$(BOOT_FLAG)
BOOT_FLAG=installed
$(info after assignment - BOOT_FLAG=$(BOOT_FLAG))
.PHONY: all
all:
#echo $(BOOT_TEST)
#echo $(BOOT_DEFINE)
You will see various values printed at different times during the makefile parsing. On the first pass it evaluates the variables (and if's) and then on the second pass it can do the target rules.
As others noted the problem is that ifeq is expanded and evaluated in-place.
If you want to postpone the evaluation until some late moment, you must keep the whole expression inside of a recursive variable. Then the conditional could be implemented by $(if ...) function, instead of ifeq (okay, $(eval ifeq...) should also be doable, but... well, gross).
Of course, this is quite an overhead for such simple case, but nonetheless it could be done like this:
BOOT_TEST=$(if $(subst _installed,,_$(BOOT_FLAG)),no,installed)
BOOT_DEFINE=$(BOOT_FLAG)
BOOT_FLAG=installed
.PHONY: all
all:
#echo $(BOOT_TEST)
#echo $(BOOT_DEFINE)

one level expansion of makefile macro while still usable as a macro

I am trying to build a macro in Makefile which I can expand once but can still work as a macro after being expanded. This is useful to me as the first level expansion will fill in recursive parameters that won't last. Here's an example:
all: aperiod
TGT = hello
hello.TGT = world
world.TGT = period
define CREATE_TARGET
.SECONDARY: $(1)
$(3)$(1): $(4)$(2)
#echo $$$$(#)
$(foreach t,$($(1).TGT),$(call CREATE_TARGET,$(t),$(1),$$(1),$$(1)))
endef
define CREATE
$(call CREATE_TARGET,$(TGT),,$$(1),)
endef
CREATE_EXP := $(call CREATE)
TGT :=
$(eval $(call CREATE_EXP,a))
Error when running make:
make: *** No rule to make target aperiod', needed byall'. Stop.
TGT contains a changing set of values. I want CREATE_EXP to contain the full expanded creation method which accepts a parameter to give prefixes to the targets.
So optimally, I can call make aperiod and get hello world period, or call make bperiod after $(eval $(call CREATE_EXP,b)) and get the same thing
This is a highly reduced test case!
The value of CREATE_EXP is correct, but won't work for me as a macro anymore.
$(info $(value CREATE_EXP))
.SECONDARY: hello
$(1)hello:
#echo $$(#)
.SECONDARY: world
$(1)world: $(1)hello
#echo $$(#)
.SECONDARY: period
$(1)period: $(1)world
#echo $$(#)
I would like to know why Make behaves this way, as well as if there is a better way to accomplish the general gist of what I am trying to do.
EDIT: I found a solution to accomplish this, although I am still curious as to whether a call to $(call) can create a macro that still needs expansion.
define CREATE
define CREATE_EXP
$(call CREATE_TARGET,$(TGT),,$$(1),)
endef
endef
Use $(eval $(call CREATE))
The first time through, make will expand the variables inside. This allows for the recursive expansion as well as the creation of a function macro.
I would have to think more deeply about "a better way" and really understand what you're trying to do, but to answer "why make behaves this way": you are assigning CREATE_EXP as a simply-expanded variable, with :=:
CREATE_EXP := $(call CREATE)
That information is stored along with the variable and when make expands something like $(CREATE_EXP) it knows that the value of CREATE_EXP has already been expanded and it shouldn't be expanded again. That's the entire point, really, of using :=.
Here's an alternate model that might work for you:
$(foreach 1,a,$(eval $(CREATE_EXP)))
(I haven't tried this). The difference here is that first we set the variable 1 as the foreach variable, then we call eval in that context. Although the $(CREATE_EXP) expands to the value without further expansion, then eval will parse it as a makefile and expand it again, with 1=a set.
Just a note: this:
CREATE_EXP := $(call CREATE)
Is absolutely identical to this:
CREATE_EXP := $(CREATE)
If you pass no arguments to call it's the same as a simple macro expansion.
You might be interested to read the set of blog posts here: http://make.mad-scientist.net/category/metaprogramming/ (start from the oldest first).

Using notdir within substitution reference as opposed to patsubst

In my makefile, I am stripping the file paths from a list of objects, and replacing that path with a build directory.
Using patsubst, this seems to work fine, however using substitution replacements it doesnt seem to work, e.g:
OBJS=/path/to/obj1.o /another/path/obj2.o
BUILD_DIR=build
$(info patsubst = $(patsubst %, $(BUILD_DIR)/%, $(notdir $(OBJS))))
$(info substref = $( $(notdir $(OBJS)):%=$(BUILD_DIR)/%) )
The output of this is :
patsubst = build/obj1.o build/obj2.o
substref =
Is this a shortcoming of substitution references or am I doing something wrong?
Im using GNU Make 4.1, but would like my makefile to be valid with other/older versions.
That syntax is not right. A substitution reference requires a variable name in the first part, and make will expand that variable and the substitution will be performed on the expansion. You are giving $(notdir $(OBJS)) as the thing to substitute on, and that expands to a set of strings, not a variable name.
patsubst works fine here so why do you need to use a substitution reference?

makefile - escape parentheses

I have a folder on my HD which contains parentheses in its name. Like: C:/stuff (really cool)/. The make $(wildcard ..) function does not work properly with this directory.
$(wildcard C:/stuff (really cool)/*.jpg)`
evaluates to no results at all. I guess this is due to the fact that the closing parentheses in the directory-name is treated as the closing parentheses for the $(wildcard ..) function. Escaping the ( and ) with a backslash does not work. What also does not work, is putting the directory-name into a variable and then using the wildcard function.
DIR = C:/stuff (really cool)
all:
#echo "$(wildcard $(DIR)/*.jpg)"
No results at all, again.
How should I properly escape the parentheses?
The following does not work, kept to warn others:
Special characters are typically "protected", or escaped, with enclosing quotes. To create a variable with parenthesis, you use double quotes.
DIR = "C:/stuff (really cool)"
In your case, the biggest problem is the spaces that will result in your path being broken down into several parts instead of one.
This works:
GNU Make lets you escape spaces with \\ so your call to $wildcard would turn into
$(wildcard C:/stuff\\ (really\\ cool)/*.jpg)
You did not mention in what kind of environment you are running, but both of the following print mechanism work for me from a regular DOS prompt as well as from msys/mingw:
DIR = C:/stuff\ (really\ cool)
$(info Files in subdir are: $(wildcard $(DIR)/*.jpg))
all:
#echo "Files in subdir are: $(wildcard $(DIR)/*.jpg)"
The double quotes around the echo-ed expression are only necessary for msys/mingw.

Resources