Makefile string comparison #Ifeq - makefile

In one of my projects, I am facing the error below. I have two environments and I'm observing the ifeq functionality difference between the two environments.
In the first environment, the below code is working fine.
FIRST = 1
ifeq ( ($FIRST),1 )
ENABLE_CODE+= -D'ENABLE_PROGRAM'
endif
The same code is not working in the second environment. It only works if I modify the code
ifeq ( ($FIRST),1)
to
ifeq ( ( $FIRST),'1')
Could somebody help me to sort this out?

As told in the comment you should wrap FIRST into quotes: `$(FIRST). Note, the Makefile and Bash has different syntax. Makefile require round quotes around variable, bash - not.
Also please remove extra whitespaces inside ifeq. Note in some cases command ifeq ( $(FIRST),1 ) may compare with unstriped line and fail because of extra whitespaces: it may compare to '1 ' instead of simple '1'
So makefile will looks like
FIRST = 1
ifeq ($(FIRST),1)
ENABLE_CODE += -D'ENABLE_PROGRAM'
endif
all:
#echo FIRST $(FIRST)
#echo ENABLE_CODE $(ENABLE_CODE)

Related

I can't get Makefile ifeq syntax to work (checking age of a file)

I am attempting to use an inheritance hack (which is already working) along with checking a file date to create a sort of reusable, auto-updating base makefile from which multiple projects can inherit; and which will update the parent Makefile using a git pull, but (and this is where I'm stuck) I only want it to try the git pull once per day, so that it doesn't add up to a lot of wasted time waiting on a git pull when the chance of updates being available is almost nothing.
The basic idea is this:
baseMakefilePath=../../baseMakefile
do_some_work: .check-for-update
#echo "working..."
.check-for-update:
# is the file > 24 hours old?
ifeq ( $(find . -mtime +24h -name '.check-makefile-update'), ./.check-makefile-update )
#make .update
else
# if the file doesn't exist at all yet, pull the update
ifeq ( $(find . -name '.check-makefile-update'), '' )
#make .update
else
#echo "last update was recent, not updating..."
endif
endif
.update:
cd $(baseMakefilePath) && git pull
touch .check-makefile-update
My theory is that by updating the modified timestamp on the .check-makefile-update file using touch it should only run the git pull once per day.
However, I can't even get a dirt simple ifeq() conditional to work:
test:
ifeq (a, a)
#echo "a basic test works"
else
#echo "idunno"
endif
With this basic Makefile (note: this is the ONLY contents of the Makefile when I test it), I get this error:
$ make test
ifeq (a, a)
/bin/sh: -c: line 0: syntax error near unexpected token `a,'
/bin/sh: -c: line 0: `ifeq (a, a)'
make: *** [test] Error 2
I get the same result if I try to run the first Makefile:
$ make do_some_work
# is the file > 24 hours old?
ifeq ( , ./.check-makefile-update )
/bin/sh: -c: line 0: syntax error near unexpected token `,'
/bin/sh: -c: line 0: `ifeq ( , ./.check-makefile-update )'
make: *** [.check-for-update] Error 2
I think the stripped down example shows that there's something funky going on (or my understanding of ifeq is fundamentally flawed) but for what it's worth, I also tried quoting various combinations of the ifeq arguments, with both single and double quotes.
I'm out of ideas, but I feel like I'm sooo close to a working solution! What am I doing wrong?
If it matters, I'm on OSX 10.14.5, and my primary shell is zsh. It is not an absolute requirement but would be good if the solution can also run on modern versions of Windows (with WSL), too.
Remember that there are Make conditionals, and shell conditionals.
Here:
test:
ifeq (a, a)
#echo "a basic test works"
else
#echo "idunno"
endif
I assume you're trying to use a Make conditional, but if those whitespace margins are TABs, then you're inadvertantly telling Make that those lines are shell commands which it should pass to the shell as they are. The shell tries to interpret ifeq (a, a) and complains of a syntax error.
Remove some of the TABs (leaving the ones in front of the actual shell commands):
test:
ifeq (a, a)
#echo "a basic test works"
else
#echo "idunno"
endif
and it works.
Your understanding of ifeq is fundamentally flawed :)
A makefile is a combination of two different syntaxes: the makefile itself is written in make syntax, and the recipes are written in shell syntax.
Those two syntaxes are not in any way compatible: you can't use make syntax when you are writing recipes and you can't use shell syntax when you are writing makefiles.
How do you know which is which? The simplest way to think about it is that if the first character on your line is indented with a TAB character, it's shell syntax and if it's not, it's make syntax. The reality is more subtle but that rule is always true.
I'm sure you can now see what's wrong with your makefile AND also why you get the errors you do:
test:
ifeq (a, a)
#echo "a basic test works"
else
#echo "idunno"
endif
you are trying to pass make syntax to the shell.

How to conditionally set Makefile variable to something if it is empty?

I want to set a variable if it is empty. I tried in this way:
....
TEST := $(something)
...
TEST ?= $(something else)
The first $(something) may return an empty string, however the conditional assignment ?= works only if the previous variable is not set, not if empty.
Any elegant solution to set the variable if empty?
EDIT
I found this solution:
....
TEST := $(something)
...
TEST += $(something else)
TEST := $(word 1, $(TEST))
but I think that there will be one more elegant.
Any elegant solution to set the variable if empty?
GNU make is hardly known for elegant solutions. Unless you find trapdoors and minefields to be elegant. I know only of the two ways to accomplish what you want:
The standard ifeq/endif solution:
ifeq ($(TEST),)
TEST := $(something else)
endif
Use the $(if) function:
TEST := $(if $(TEST),$(TEST),$(something else))
One can try to package that construct into a function too, but that is inadvisable. The function would have the hidden pitfall of occasionally breaking the $(something else) if it contains the , (for which there are only wayward workarounds). (The built-in functions like $(if) are immune to the , bug.)
Elegance test is up to you.
Here's another alternative that I personally find quite elegant, because it's a one-liner and doesn't need the redundant else-branch:
TEST := $(or $(TEST),$(something else))
From GNU make, chapter 7.2, Syntax of Conditionals:
"Often you want to test if a variable has a non-empty value. When the value results from complex expansions of variables and functions, expansions you would consider empty may actually contain whitespace characters and thus are not seen as empty. However, you can use the strip function to avoid interpreting whitespace as a non-empty value. For example:
ifeq ($(strip $(foo)),)
text-if-empty
endif
will evaluate text-if-empty even if the expansion of $(foo) contains whitespace characters."
Folks, I think there's a simpler solution
KDIR ?= "foo"
From: What is ?= in Makefile
Just in case anyone stumbled upon putting the condition in the rule itself. below how I did it, thought it might help others.
In Makefile, suppose we have the following rule with check target and we need to check whether var was passed.
check:
#[ "${var}" ] && echo "all good" || ( echo "var is not set"; exit 1 )
To test this out, run the following commands
$ make check
var is not set
make: *** [check] Error 1
$ make check var=test
all good
So, Now we can pass the variable value or a default value in case it was not passed to a bash script that will be responsible to do the logic. something like the following:
#[ "${var}" ] && ./b.sh ${var} || ./b.sh 'ss'
Here's below what b.sh might look like, though you can add more logic to it.
#!/bin/sh
echo $1
In case you need to distinguish if a variable is undefined or just has an empty value, use $(origin VARNAME) function:
ifeq ($(origin VARNAME),undefined)
VARNAME := "now it's finally defined"
endif
Note that VARNAME is not surrounded by $() - you literally give the name of the variable.
Setting value to variable in Makefile if value defined
ifdef RELEASE_BRANCH
GIT_TAG=$(shell cat projects/${RELEASE_BRANCH}/GIT_TAG)
else
GIT_TAG=$(shell cat release/DEFAULT_GIT_TAG)
endif

Makefile says target is empty but it shouldn't be

I am trying to have a Makefile which is able to use different instances of g++ based on whether I give along a certain target or not. So: If I run make home I want CC to be the g++ executable in /usr/bin, and otherwise in some long path <longpath>/bin.
So I tried checking for my target:
ifeq ("$(TARGET)", "home")
GCCPATH = /usr
HSPARG = home
endif
$(info "$(TARGET)")
$(info "$#")
GCCPATH ?= <longpath>
CC = $(GCCPATH)/bin/g++
GCCLIBPATH = $(GCCPATH)/lib64
However, the outcome of this is:
$ make home
""
""
<further build information>
and GCCPATH is in all occasions equal to <longpath>.
Now my questions are:
1. What do I do wrong?
2. How to fix it?
First of all, make home doesn't set TARGET to home. So you have to execute make TARGET='home' to set it.
Secondly, make cares about spaces, including spaces after commas. So when you wrote ifeq ("$(TARGET)", "home"), make didn't toss away the space after the comma like you might have expected. So what make ended up comparing was "home" and " home".
You can see this by running make TARGET=' home' and seeing what you get. Remove that space and you'll fix the problem.
That said all your quoting isn't doing anything for make either. It doesn't generally care. Quotes are just literal characters to make in almost all places (except in ifeq "arg1" "arg2" cases, etc. and maybe one or two other places but I can't think of any offhand), so you don't need them in the calls to $(info) or even in your ifeq test since you are using the ifeq (arg1,arg2) version.

How to print out a variable in makefile

In my makefile, I have a variable 'NDK_PROJECT_PATH', my question is how can I print it out when it compiles?
I read Make file echo displaying "$PATH" string and I tried:
#echo $(NDK_PROJECT_PATH)
#echo $(value NDK_PROJECT_PATH)
Both gives me
"build-local.mk:102: *** missing separator. Stop."
Any one knows why it is not working for me?
You can print out variables as the makefile is read (assuming GNU make as you have tagged this question appropriately) using this method (with a variable named "var"):
$(info $$var is [${var}])
You can add this construct to any recipe to see what make will pass to the shell:
.PHONY: all
all: ; $(info $$var is [${var}])echo Hello world
Now, what happens here is that make stores the entire recipe ($(info $$var is [${var}])echo Hello world) as a single recursively expanded variable. When make decides to run the recipe (for instance when you tell it to build all), it expands the variable, and then passes each resulting line separately to the shell.
So, in painful detail:
It expands $(info $$var is [${var}])echo Hello world
To do this it first expands $(info $$var is [${var}])
$$ becomes literal $
${var} becomes :-) (say)
The side effect is that $var is [:-)] appears on standard out
The expansion of the $(info...) though is empty
Make is left with echo Hello world
Make prints echo Hello world on stdout first to let you know what it's going to ask the shell to do
The shell prints Hello world on stdout.
As per the GNU Make manual and also pointed by 'bobbogo' in the below answer,
you can use info / warning / error to display text.
$(error text…)
$(warning text…)
$(info text…)
To print variables,
$(error VAR is $(VAR))
$(warning VAR is $(VAR))
$(info VAR is $(VAR))
'error' would stop the make execution, after showing the error string
from a "Mr. Make post"
https://www.cmcrossroads.com/article/printing-value-makefile-variable
Add the following rule to your Makefile:
print-% : ; #echo $* = $($*)
Then, if you want to find out the value of a makefile variable, just:
make print-VARIABLE
and it will return:
VARIABLE = the_value_of_the_variable
If you simply want some output, you want to use $(info) by itself. You can do that anywhere in a Makefile, and it will show when that line is evaluated:
$(info VAR="$(VAR)")
Will output VAR="<value of VAR>" whenever make processes that line. This behavior is very position dependent, so you must make sure that the $(info) expansion happens AFTER everything that could modify $(VAR) has already happened!
A more generic option is to create a special rule for printing the value of a variable. Generally speaking, rules are executed after variables are assigned, so this will show you the value that is actually being used. (Though, it is possible for a rule to change a variable.) Good formatting will help clarify what a variable is set to, and the $(flavor) function will tell you what kind of a variable something is. So in this rule:
print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) #true
$* expands to the stem that the % pattern matched in the rule.
$($*) expands to the value of the variable whose name is given by by $*.
The [ and ] clearly delineate the variable expansion.
You could also use " and " or similar.
$(flavor $*) tells you what kind of variable it is. NOTE: $(flavor)
takes a variable name, and not its expansion.
So if you say make print-LDFLAGS, you get $(flavor LDFLAGS),
which is what you want.
$(info text) provides output.
Make prints text on its stdout as a side-effect of the expansion.
The expansion of $(info) though is empty.
You can think of it like #echo,
but importantly it doesn't use the shell,
so you don't have to worry about shell quoting rules.
#true is there just to provide a command for the rule.
Without that,
make will also output print-blah is up to date. I feel #true makes it more clear that it's meant to be a no-op.
Running it, you get
$ make print-LDFLAGS
LDFLAGS is a recursive variable set to [-L/Users/...]
All versions of make require that command lines be indented with a TAB (not space) as the first character in the line. If you showed us the entire rule instead of just the two lines in question we could give a clearer answer, but it should be something like:
myTarget: myDependencies
#echo hi
where the first character in the second line must be TAB.
#echo $(NDK_PROJECT_PATH) is the good way to do it.
I don't think the error comes from there.
Generally this error appears when you mistyped the intendation : I think you have spaces where you should have a tab.
No need to modify the Makefile.
$ cat printvars.mak
print-%:
#echo '$*=$($*)'
$ cd /to/Makefile/dir
$ make -f ~/printvars.mak -f Makefile print-VARIABLE
Run make -n; it shows you the value of the variable..
Makefile...
all:
#echo $(NDK_PROJECT_PATH)
Command:
export NDK_PROJECT_PATH=/opt/ndk/project
make -n
Output:
echo /opt/ndk/project
This makefile will generate the 'missing separator' error message:
all
#echo NDK_PROJECT_PATH=$(NDK_PROJECT_PATH)
done:
#echo "All done"
There's a tab before the #echo "All done" (though the done: rule and action are largely superfluous), but not before the #echo PATH=$(PATH).
The trouble is that the line starting all should either have a colon : or an equals = to indicate that it is a target line or a macro line, and it has neither, so the separator is missing.
The action that echoes the value of a variable must be associated with a target, possibly a dummy or PHONEY target. And that target line must have a colon on it. If you add a : after all in the example makefile and replace the leading blanks on the next line by a tab, it will work sanely.
You probably have an analogous problem near line 102 in the original makefile. If you showed 5 non-blank, non-comment lines before the echo operations that are failing, it would probably be possible to finish the diagnosis. However, since the question was asked in May 2013, it is unlikely that the broken makefile is still available now (August 2014), so this answer can't be validated formally. It can only be used to illustrate a plausible way in which the problem occurred.
The problem is that echo works only under an execution block. i.e. anything after "xx:"
So anything above the first execution block is just initialization so no execution command can used.
So create a execution blocl
If you don't want to modify the Makefile itself, you can use --eval to add a new target, and then execute the new target, e.g.
make --eval='print-tests:
#echo TESTS $(TESTS)
' print-tests
You can insert the required TAB character in the command line using CTRL-V, TAB
example Makefile from above:
all: do-something
TESTS=
TESTS+='a'
TESTS+='b'
TESTS+='c'
do-something:
#echo "doing something"
#echo "running tests $(TESTS)"
#exit 1
This can be done in a generic way and can be very useful when debugging a complex makefile. Following the same technique as described in another answer, you can insert the following into any makefile:
# if the first command line argument is "print"
ifeq ($(firstword $(MAKECMDGOALS)),print)
# take the rest of the arguments as variable names
VAR_NAMES := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
# turn them into do-nothing targets
$(eval $(VAR_NAMES):;#:))
# then print them
.PHONY: print
print:
#$(foreach var,$(VAR_NAMES),\
echo '$(var) = $($(var))';)
endif
Then you can just do "make print" to dump the value of any variable:
$ make print CXXFLAGS
CXXFLAGS = -g -Wall
You could create a vars rule in your make file, like this:
dispvar = echo $(1)=$($(1)) ; echo
.PHONY: vars
vars:
#$(call dispvar,SOMEVAR1)
#$(call dispvar,SOMEVAR2)
There are some more robust ways to dump all variables here: gnu make: list the values of all variables (or "macros") in a particular run.
if you use android make (mka) #echo $(NDK_PROJECT_PATH) will not work and gives you error *** missing separator. Stop."
use this answer if you are trying to print variables in android make
NDK_PROJECT_PATH := some_value
$(warning $(NDK_PROJECT_PATH))
that worked for me
I usually echo with an error if I wanted to see the variable value.(Only if you wanted to see the value. It will stop execution.)
#echo $(error NDK_PROJECT_PATH= $(NDK_PROJECT_PATH))
The following command does it for me on Windows:
Path | tr ; "\n"

Get exit code 1 on Makefile if statement

I'm trying to get the exit code on the ifdef statement if the statement is not true, but I tried by using exit 1 and $(call exit 1)
when using the first on the following code I get "Makefile:11: * missing separator. Stop."
...
ifdef PACKAGE
PACKAGEDIR = $(HOME)/$(PACKAGE)
else
exit 1
endif
...
By using $(call exit 1) I get no error but the makefile still keeps executing.
What I'm trying to accomplish is to exit the Makefile on the else with the error code 1
Thanks
As geekosaur says you can't put a shell command like exit 1 as a makefile operation. Makefiles are not shell scripts, although they can contain shell scripts. Shell commands can only appear within a target recipe, and nowhere else.
If you have a sufficiently new version of GNU make you can use the $(error ...) function, like this:
ifdef PACKAGE
PACKAGEDIR = $(HOME)/$(PACKAGE)
else
$(error You must define the PACKAGE variable)
endif
Also note that ifdef will be true if the variable is defined, even if it's defined to be the empty string. You may prefer:
ifneq ($(PACKAGE),)
PACKAGEDIR = $(HOME)/$(PACKAGE)
else
$(error You must define the PACKAGE variable)
endif
to ensure the variable is set to a non-empty value.
And, it's possible your version of GNU make is too old to support the $(error ...) function, although it's been around for a long time now.
You can't simply have bare code sticking somewhere in a Makefile; code (such as exit 1) is always associated with a build rule of some kind.
In this case you want the $(error) function. It may not be sufficient to drop it in the else, though, for the same reason that exit 1 itself won't work there; you may need to rephrase the whole thing as
PACKAGEDIR := $(if $(flavor PACKAGE),undefined,$(error PACKAGE must be defined!),$(HOME)/$(PACKAGE))

Resources