Makefile If-Then Else and Loops - makefile

Can someone explain how to use if-then statements and for loops in Makefiles? I can't seem to find any good documentation with examples.

Conditional Forms
Simple
conditional-directive
text-if-true
endif
Moderately Complex
conditional-directive
text-if-true
else
text-if-false
endif
More Complex
conditional-directive
text-if-one-is-true
else
conditional-directive
text-if-true
else
text-if-false
endif
endif
Conditional Directives
If Equal Syntax
ifeq (arg1, arg2)
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq "arg1" 'arg2'
ifeq 'arg1' "arg2"
If Not Equal Syntax
ifneq (arg1, arg2)
ifneq 'arg1' 'arg2'
ifneq "arg1" "arg2"
ifneq "arg1" 'arg2'
ifneq 'arg1' "arg2"
If Defined Syntax
ifdef variable-name
If Not Defined Syntax
ifndef variable-name
foreach Function
foreach Function Syntax
$(foreach var, list, text)
foreach Semantics
For each whitespace separated word in "list", the variable named by "var" is set to that word and text is executed.

Here's an example if:
ifeq ($(strip $(OS)),Linux)
PYTHON = /usr/bin/python
FIND = /usr/bin/find
endif
Note that this comes with a word of warning that different versions of Make have slightly different syntax, none of which seems to be documented very well.

Have you tried the GNU make documentation? It has a whole section about conditionals with examples.

You do see for loops alot of the time, but they are usually not needed. Here is an example of how one might perform a for loop without resorting to the shell
LIST_OF_THINGS_TO_DO = do_this do_that
$(LIST_OF_THINGS_TO_DO):
run $# > $#.out
SUBDIRS = snafu fubar
$(SUBDIRS):
cd $# && $(MAKE)

Related

How to replace a character in a variable in tcsh script while using inside a makefile function

I have a makefile with the below content :
mytarget:
#$(call my_func, echo \"hello world\")
define my_func
$(eval var := $(1))
if [[ "$(var)" != "" ]]; then \
$(var); \
fi;
endef
while executing with make command , this prints "hello world" (enclosed with the double quotes)
All I want is to get rid of the " from the echo "hello world" before the conditional if check.
I tried with below statement but of no use :
var2 = $(echo $var | tr '"' ''
then use $(var2) in the if condition instead of $(var)
It is not working.
Any help will be very much appreciated.
Thanks
First, you should never add # to your makefile recipes until they are working 100%. Otherwise you're trying to debug your makefiles blindfolded.
Your entire makefile script seems like it could just be written as:
mytarget:
$(call my_func, echo "hello world")
my_func = $1
and it would work exactly the same way: if my_func is called with an empty argument then $1 expands to the empty string, which is what you're trying to test anyway. Presumably you have some reason for wanting a more complicated configuration but, as I mentioned in my comment, it seems like an XY problem.
I'll give you a short answer: make doesn't care about quoting or about backslashes. Any quotes or backslashes you use are passed verbatim to the shell. So if you pass echo \"foo\" to the shell, it will print the quotes. That's exactly as expected.
If you remove the #, as I mention above, things will be a lot more clear to you.
So, why are you adding backslashes? It appears you're adding them because otherwise the condition if [ "$1" != "" ] will expand to if [ "echo "hello world"" != "" ] which is a syntax error.
One simple solution is to use single quotes instead of double quotes:
mytarget:
$(call my_func, echo "hello world")
define my_func
if [ '$1' != '' ]; then \
$1; \
fi;
endef
Of course, this will fail if you call my_func with a value containing singe-quotes.
You have two solutions:
First is to use a make conditional, not a shell conditional, since make doesn't care about quotes etc. That gives you:
mytarget:
$(call my_func, echo "hello world")
my_func = $(if $1,$1)
but that's the same as my first suggestion, just using $1 by itself.
If you really, really have to do this in the shell then you have to quote things for the shell. Make's subst function can do that for you:
mytarget:
$(call my_func, echo "hello world")
define my_func
if [ '$(subst ','\'',$1)' != '' ]; then \
$1; \
fi;
endef
which replaces all instances of ' with '\''.

Log wrapper in makefile?

I m trying to write a simple log wrapper in makefile as shown below
define do_log
ifeq ($(1),1)
$(info ---------- $(shell date +%H:%M:%S) $(2))
else ifeq ($(1),2)
$(warning ^^^^^^^^^^ $(shell date +%H:%M:%S) $(2))
else ifeq ($(1),3)
$(error !!!!!!!!!! $(shell date +%H:%M:%S) $(2))
endif
endef
But every-time i call this function
$(call do_log,1,"hello")
All statements are getting executed
$ make
---------- 18:22:45 "hello"
Makefile:16: ^^^^^^^^^^ 18:22:45 "hello"
Makefile:16: *** !!!!!!!!!! 18:22:45 "hello". Stop.
Can someone please help me understand, on what am i doing wrong ?
ifeq etc. are more like preprocessor statements. They are only meaningful to make's makefile parser. They have no special meaning or significance to variable expansion etc., just like how you can't use C/C++ preprocessor statements at runtime.
The behavior you see is because call first expands the variable, and when that happens only variables are considered. This means that call sees something like this:
<text> $(1) <text>
$(info ---------- $(shell date +%H:%M:%S) $(2))
<text> $(1) <text>
$(warning ^^^^^^^^^^ $(shell date +%H:%M:%S) $(2))
<text> $(1) <text>
$(error !!!!!!!!!! $(shell date +%H:%M:%S) $(2))
<text>
and all those variable/function references are expanded. In general you cannot use ifeq etc. in a define variable unless you expect to use it with eval (which parses its string using the makefile parser).
Since you've not really shown us how you want to use this it's hard to suggest The best alternative, but to literally translate what you're doing here you could use:
define do_log
$(if $(filter-out 1,$(1)),,$(info ---------- $(shell date +%H:%M:%S) $(2)))
$(if $(filter-out 2,$(1)),,$(warning ^^^^^^^^^^ $(shell date +%H:%M:%S) $(2)))
$(if $(filter-out 3,$(1)),,$(error !!!!!!!!!! $(shell date +%H:%M:%S) $(2)))
endef
This can be achieved with eval :
define do_log
ifeq ($(1),1)
$$(info ---------- $(shell date +%H:%M:%S) $(2))
else ifeq ($(1),2)
$$(warning ^^^^^^^^^^ $(shell date +%H:%M:%S) $(2))
else ifeq ($(1),3)
$$(error !!!!!!!!!! $(shell date +%H:%M:%S) $(2))
endif
endef
$(eval $(call do_log,3,"hello"))

GNU make: what difference between eval funciton and expand variable into makefile directly

the Makefile define a multi-line variable, which define a rule, reference it by eval function work fine:
define a
b:
echo b
endef
$(eval $(a))
excute result:
$ make
echo b
b
but failed if reference variable directly:
define a :=
b:
echo b
endef
$(a)
execute make will fail:
make: *** No rule to make target 'echo', needed by 'b'. Stop.
but if I write rule in one line, that will work:
define a :=
b: ; echo b
endef
$(a)
make is ok:
$ make
echo b
b
why multi-line variable we need eval function, what GNU make do when expand variable into makefile directly.
I found not material about this in the whole GNU make manual.

Variable in makefile conditional?

Apparently there is no boolean type in GNU Make conditionals so this seemed like the best solution:
$(DEF_TARGET):
if [ "$(CHECK)" != "y" ]; then \
var=foo; \
$(if $(filter foo,$(var)),result=true,result=false); \
fi
The problem is that no matter if var=foo or var=bar, result will always be false. Replacing $(var) with foo or bar will yeld correct result.
Why will this not work? Are there any better solutions to the problem?
Following makefile is run with the command make -f make.txt
.PHONY: all
all:
X=aaa; \
Y=aaa; \
if [[ '$(filter $(X),$(Y))' ]]; then echo "matched!"; else echo "not matched!"; fi
output:
X=aaa; \
Y=aaa; \
if [[ '' ]]; then echo "matched!"; else echo "not matched!"; fi
not matched!
Why does it fail when X and Y are assigned values in the target recipe?
Apparently there is no boolean type in GNU Make conditionals
Clearly there is, else the presence of an $(if ...) macro would not make sense!
The way to read recipes
(i.e., the block of shell commands that build the target)
is to understand that make stores a recipe as a single recursively expanded variable.
The recipe is only expanded when make needs to pass some commands to the shell.
The recipe is expanded once in its entirety.
Each line of the resulting expansion is then executed one-by-one.
Each execution is run in a new instance of the shell.
So,
taking your original makefile,
let's assume that you have asked make to build ${DEF_TARGET}.
Make expands the recipe:
if [ "$(CHECK)" != "y" ]; then \
var=foo; \
$(if $(filter foo,$(var)),result=true,result=false); \
fi
${CHECK} becomes nothing (say)
$(if $(filter foo,$(var)),result=true,result=false)
First, ${var} is expanded and also becomes empty (say).
Note that the line var=foo in the recipe is not interpreted by make!
It is a shell command.
Next, $(filter foo,) is expanded and also is empty.
Next make expands $(if ,result=true,result=false), which produces result=false of course.
if [ "" != "y" ]; then \
var=foo; \
result=false; \
fi
Make sees this as a just one line due to the back-slashes.
So,
do not confuse shell variables and make variables.
All the make variables are gone by the time the shell gets its hands on the recipe.
Make does not know anything about shell variables.
Your original shell snippet could be something like:
result=$(if $(filter y,${CHECK}),true,false)
(TIMTOWTDI applies).
The shell gets result=true or result=false depending on the value of the make variable CHECK.
Makefile
.PHONY: all
all:
#echo "$(filter $(X),$(Y))"
Tests
$ make -f make.txt X='xxx yyy' Y='aaa bbb'
$ make -f make.txt X='xxx yyy' Y='aaa xxx'
xxx
$ make -f make.txt X='bbb yyy' Y='aaa bbb'
bbb
GNU Bash treats non empty strings as true in a boolean context. So a recipe with shell level condition might be:
all:
if [[ '$(filter $(X),$(Y))' ]]; then echo "matched!"; else echo "not matched!"; fi

makefile iteration and retrieving the filename

In a makefile, I am trying to iterate through c files and use the path as well as the filename.
For example for /dir/dir2/file.c
I want to execute "cc /dir/dir2/file.c -o file"
I do not understand why basename and patsubst do not work. It just shows me the path as is.
Can anyone please help?
test_files := Test/src/test_*.c
compile_tests:
#for f in $(test_filenames); do \
echo ">>> $(basename $(patsubst %.c, %, $$f ))";\
done
You cannot mix and match make functions with shell operations. Make will fully expand all the variables and functions first, then it passes the result of the expansion to the shell and the shell runs it as a script.
You are trying to use a make function inside a shell loop, but the make function is expanded first, then the loop will run on the results. The basename and patsubst run on the literal string $f, which doesn't have any pathname and does not match the %.c pattern, so those functions have no effect.
If you want to do it this way you must use 100% shell operations, or else modify the variable before the shell gets it like this:
test_filenames := $(wildcard Test/src/test_*.c)
compile_tests:
#for f in $(basename $(patsubst %.c,%,$(test_filenames))); do \
echo ">>> $$f";\
done
ETA: if you want to do it all in the shell, you can use:
test_filenames := $(wildcard Test/src/test_*.c)
compile_tests:
#for f in $(test_filenames); do \
echo ">>> $$(basename $$f .c)";\
done
Or, perhaps more clearly:
test_filenames := $(wildcard Test/src/test_*.c)
compile_tests:
#for f in $(test_filenames); do \
echo ">>> `basename $$f .c`";\
done

Resources