makefile: variable evaluation in function call? - makefile

Say I have a makefile like this:
DIRS=foo bar
I'd like to enumerate files in subdirectories of these directories, so I can pass all of them at once to a program. This isn't working:
UNIT_FILES=()
find_units:
for c in $(DIRS); do UNIT_FILES+=($(wildcard $(c)/systemd/*)); done
However, this works:
UNIT_FILES=()
find_units:
UNIT_FILES+=($(wildcard foo/systemd/*))
So I know the issue is with my variable evaluation in the call to wildcard. I've tried variations on this, including $(c), $$c and the like, but I'm just taking stabs in the dark here.
Can someone point me in the right direction?

Here :
for c in $(DIRS); do UNIT_FILES+=($(wildcard $(c)/systemd/*)); done
You're using a shell for-loop that will be called like this by make :
$(SHELL) -c "for c in $(DIRS); do UNIT_FILES+=($(wildcard $(c)/systemd/*)); done"
Nothing is actually expanded into UNIT_FILES because this command is not executed in the Makefile level.
You can achieve what you want with make builtin function foreach :
UNIT_FILES += $(foreach DIR, $(DIRS), $(wildcard $(DIR)/systemd/*))
Which is expanded into :
UNIT_FILES += $(wildcard foo/systemd/*) $(wildcard bar/systemd/*)

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

How can I add a directory to the search path of GNU Make?

I have a makefile that looks something like this:
include anotherFile.mk
all:
someStuff
The file anotherFile.mk is like this:
include yetAnotherFile.mk
export SOME_VAR = 93
The problem is that anotherFile.mk and yetAnotherFile.mk are in a different directory from my Makefile. So my makefile can't just be changed to this:
include $(OTHER_PROJECT_PATH)/anotherFile.mk
all:
someStuff
The problem with this approach is that the include statement in anotherFile.mk will fail because it will be searching in the current directory.
A partial solution that I found is to pass the --include-dir=$OTHER_PROJECT_PATH flag to the invocation of make, but that's a bit user-unfriendly.
So my question is: Is there something I can put inside my makefile that will add to the directories that make searches for when executing an include? Something like MAKE_INCLUDE_DIRS += $(OTHER_PROJECT_PATH)
Surprisingly there doesn't seem to be a good answer to that question. Forcing .INCLUDE_DIR doesn't help and there doesn't seem to be any way around invoking make with --include-dir=$OTHER_PROJECT_PATH.
It is however possible to put the appropriate recursive make invocation inside the makefile but, in order to get it to work for all reasonable cases it quickly becomes too complicated to be worth it. In summary it requires:
a top level condition to check if the OTHER_PROJECT_PATH is in .INCLUDE_DIR
the appropriate target with the recipe invoking make recursively
possibly additional targets if there are multiple command goals
the real make file enclosed in the else part of the conditional
You Makefile would look like this:
OTHER_PROJECT_PATH := other
ifeq (,$(filter $(OTHER_PROJECT_PATH), $(.INCLUDE_DIRS)))
# this is the mechanism to add the include dir in a recursive make
$(or $(firstword $(MAKECMDGOALS)),all):
$(MAKE) -I$(OTHER_PROJECT_PATH) $(MAKECMDGOALS)
# add empty targets for additional goals if needed
ifneq (,$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)))
$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)):
endif
else
# this is where the real makefile starts
all more:
echo $#: $< $^
include a.mak
endif
It still does not seem possible from a makefile, but if you have a script that sets up environment variables, you can use MAKEFLAGS (e.g. export MAKEFLAGS=I/your/path ordentlich on Linux, or SET on Windows)

Getting parent directory's basename in makefile

I need to automate a variable alignment in my Makefile. My Makefile's full file path is:
/home/e2/branchname/projectname/modulename/Makefile
In my Makefile, I have a variable BUILD_DIR, a part of which should be equal to the branchname in the full path.
So I did this:
BRANCH_NAME= $(shell cd ../.. && basename "$PWD" && cd projectname/modulename)
BUILD_DIR=$(HOME)/$(BRANCH_NAME)/build
Apparently I expected BUILD_DIR to be ~/branchname/build here, but after make I got ~/WD/build instead. I think it's most likely that I got a wrong BRANCH_NAME. Is there something wrong with what I did? And if yes I'd like to get some advice about how to do it correctly.
Thanks.
It's because $ has a special meaning to Make, so if you want to pass that up to shell you have to "escape" it. In case of Make, you escape the dollar sign by doubling. So you have to use $$PWD.
Also, what you are doing is not really the best way - it is always best to avoid the shell and use Make functionality if possible. In your case, the best way to do what you want is this:
BUILD_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/../../build)
You have to put the above line in the makefile in question, near the top, so that it is before you include any other makefiles.
I came up with this:
ENVIRONMENT := $(shell basename $(dir $(abspath $(dir $$PWD))))
If this were executed, you'd have:
ENVIRONMENT=projectname

Parameterized recipe in makefile?

I have a file "ORIGINAL", which, if updated, I would like to copy, modify, and distribute to a few places on the drive. The modification is made by a little bash script which takes one parameter, a parameter unique for each spawned remote file.
In my Makefile, I can do this with a separate rule/recipe for each parameter, like so:
parameters = AWK BAT CAT DOG
$(DEST_FILE_AWK) : $(ORIGINAL)
./copyAndModify "AWK" ## Creates $(ORIGINAL)_AWK, substed copy of ORIGINAL
mv - f $(ORIGINAL)_AWK $(DEST_FILE_AWK)
$(DEST_FILE_BAT) : $(ORIGINAL)
./copyAndModify "BAT" ## Creates $(ORIGINAL)_BAT, substed copy of ORIGINAL
mv - f $(ORIGINAL)_BAT $(DEST_FILE_BAT)
The dereferenced values of DEST_FILE_AWK and DEST_FILE_BAT have nothing to do with each other, but other than that, the two recipes above are exactly the same with the only difference the parameter, so I can't help but want to merge them into one super rule/recipe with a multiple target rule line.
But I just can't make it happen. I've tried all kinds of foreach() and other stuff in the target section of the rule, but the problem is that no matter what, I can't get the value of the parameter into the recipe part.
Is there a way?
With the information provided here the best you can do (assuming you're using GNU make) is an eval/call combination. As anishsane suggests, depending on the value of the DEST_FILE_* variables it might be possible to do something simpler.
But this should work:
define COPY_TO_DEST
$$(DEST_FILE_$1) : $$(ORIGINAL)
./copyAndModify "$1"
mv - f $$(ORIGINAL)_$1 $$#
endef
parameters = AWK BAT CAT DOG
$(foreach P,$(parameters),$(eval $(call COPY_TO_DEST,$P)))
It can be done without $(eval), at least in gnu make :)
Start with one recipe that specifies all of the targets, i.e. the list of targets is on the left side of the recipe. Let's assume we have a variable that holds the names of all these targets.
Now observe that both functions and variables will be evaluated separately for a given recipe as it gets expanded for each of the targets. Recall that, say $# is just a variable, and will be substituted separately for each target. Function calls behave the same.
Provide a list of types, and a list of type:target pairs. I presume that there's no need to put the targets into separate variables like you did ($(DEST_FILE_AWK) etc).
The TARGET_FOR_TYPE function takes the pairs and the types and generates a list of destination files.
The TYPE variable is assigned once for each target, computed by the TYPE_FOR_TARGET function. That way the repeated function call doesn't pollute the recipe :)
Note that the DESTINATIONS list contains plain filenames, without any further indirection.
types = AWK BAT
ORIGINAL = an_original
DESTINATIONS = \
AWK:dest_for_awk \
BAT:dest_for_bat
TARGET_FOR_TYPE = $(patsubst $(1):%,%,$(filter $(1):%,$(DESTINATIONS)))
TYPE_FOR_TARGET = $(patsubst %:$(1),%,$(filter %:$(1),$(DESTINATIONS)))
# Usage example for the functions above:
$(info type: $(call TYPE_FOR_TARGET,dest_for_awk))
$(info target: $(call TARGET_FOR_TYPE,AWK))
$(info $())
DEST_FILES = $(foreach type,$(types),$(call TARGET_FOR_TYPE,$(type)))
all: $(DEST_FILES)
$(DEST_FILES) : TYPE=$(call TYPE_FOR_TARGET,$#)
$(DEST_FILES) : $(ORIGINAL)
#echo ./copyAndModify $(TYPE)
#echo mv - f $(ORIGINAL)_$(TYPE) $#

Using the call function correctly in a makefile

I am trying to compile for different software directories with different optimization levels etc. I created the following makefile to do so:
OWNER = betsy molly fred
DOG = poodle mutt doberman
COLOUR = brown red yellow
ATTR = big small
LEGS = 0 3
#we want every possible combination to be excercised
OUTPUT_STUFF = $(foreach own,$(OWNER),$(foreach dog,$(DOG),$(foreach col,$(COLOUR),$(foreach attr,$(ATTR),$(foreach legs,$(LEGS),new/$(own)/$(dog)/$(col)/$(attr)/$(legs)/dogInfo.txt)))))
.PHONY: all
all: $(OUTPUT_STUFF)
define PROGRAM_template
own = $(1)
dog = $(2)
col = $(3)
attr = $(4)
legs = $(5)
BUILD_DIR = new/$(own)/$(dog)/$(col)/$(attr)/$(legs)
#for each build directory, we are going to put a file in it containing the build dir. string
$$(BUILD_DIR)/dogInfo.txt:
#echo "$$#"
mkdir $$(BUILD_DIR)
#echo "$$(BUILD_DIR)" > $$(BUILD_DIR)/dogInfo.txt
endef
#call the function many times
$(foreach own,$(OWNER),$(foreach dog,$(DOG),$(foreach col,$(COLOUR),$(foreach attr,$(ATTR),$(foreach legs,$(LEGS),$(eval $(call PROGRAM_template,$(own),$(dog),$(col),$(attr),$(legs))))))))
As you can see, this simple test program loops through different combinations of owner, dog etc. The end goal is to have a directory, new, that has all owners as dirs, and in those, all dogs, etc. At the bottom is just a file with the path in it.
When I run this, the output is:
new/betsy/poodle/brown/big/0/dogInfo.txt
mkdir new/fred/doberman/yellow/small/3
mkdir: cannot create directory `new/fred/doberman/yellow/small/3': No such file or directory
make: *** [new/betsy/poodle/brown/big/0/dogInfo.txt] Error 1
So, for some reason, the target is ok, but the seemingly exact same variable is the last in my loops. Fundamentally, I don't understand what is happening that well.
Weird foreach + user-defined function behavior in Makefiles seems to answer, but I don't fully get it. In my mind, when the function is called, it fills in all instances with one $, and the escaped ones become $(BUILD_DIR). It then 'pastes' the code to the temporary file, and after it's done all the calls it evaluates the file, substituting the variables as normal.
One (ugly) solution I thought of is to make the BUILD_DIR variable different every time like so:
B_D_$(1)_$(2)_$(3)_$(4)_$(5) = ~~~
Alex is correct (although I think he means recipe, not receipt :-)). The best way to debug complex eval issues is to replace the eval function with a call to info instead. So if you have something like:
$(foreach A,$(STUFF),$(eval $(call func,$A)))
then you can rewrite this as:
$(foreach A,$(STUFF),$(info $(call func,$A)))
Now make will print out to you exactly what the eval is going to parse. It's usually pretty clear, looking at the makefile output, what the problem is. In your case you'll see something like this in the output (leaving out all the extra variable settings):
BUILD_DIR = new/betsy/poodle/brown/big/0
$(BUILD_DIR)/dogInfo.txt:
#echo "$$#"
mkdir $(BUILD_DIR)
#echo "$(BUILD_DIR)" > $(BUILD_DIR)/dogInfo.txt
BUILD_DIR = new/betsy/poodle/brown/big/3
$(BUILD_DIR)/dogInfo.txt:
#echo "$$#"
mkdir $(BUILD_DIR)
#echo "$(BUILD_DIR)" > $(BUILD_DIR)/dogInfo.txt
etc. Notice how you're setting the global variable BUILD_DIR every time. In make, variables have only one value (at a time). While make is reading the makefile it expands the target and prerequisite lists immediately, so whatever value BUILD_DIR has at that time will be used for targets/prerequisites, so this works for you.
But when make finishes reading the makefile, the value of BUILD_DIR will always be the last thing you set it to; in this case new/fred/doberman/yellow/small/3. Now make starts to invoke the recipes for each target, and when it does that it will expand BUILD_DIR in the recipes then, and so ALL the recipes will get that same value.
As Alex points out, you should ensure that your recipe uses only automatic variables like $#, which are set correctly for each rule. If you do that you'll notice that you don't really need to redefine the rule at all because it's actually the same recipe for all the targets. And if you notice THAT, you'll notice you don't need the whole eval or call complexity in the first place.
All you have to do is compute the names of all the targets, then write a single rule:
ALLDOGINFO = $(foreach own,$(OWNER),$(foreach dog,$(DOG),$(foreach col,$(COLOUR),$(foreach attr,$(ATTR),$(foreach legs,$(LEGS),new/$(own)/$(dog)/$(col)/$(attr)/$(legs)/dogInfo.txt)))))
$(ALLDOGINFO):
#echo "$#"
mkdir $(dir $#)
#echo "$(dir $#)" > $#
If you don't want the trailing slash you have to use $(patsubst %/,%,$(dir $#)) instead.
The problem is that when $$(BUILD_DIR) is evaluated in receipt, the loop is already complete. The solution is to rewrite the receipt:
$$(BUILD_DIR)/dogInfo.txt:
#echo "$$#"
mkdir $$(#D)
#echo "$$(#D)" > $$#
I don't think your problem is necessarily with something to do with make. This command:
mkdir new/fred/doberman/yellow/small/3
will fail if one of the parent directories (for example, yellow) doesn't already exist. The error it spits out in this case is the one you're getting, so it seems likely this is the case. If you want a command that makes all parent directories of a given directory as needed, you should run mkdir -p, like this:
mkdir -p $$(BUILD_DIR)
See the mkdir man page for a full description of what -p does.

Resources