Makefile ifeq: when are they evaluated? - makefile

The following is a very simple makefile that does not seem to work properly.
TEST=ON
buildbegin:
ifeq ($(TEST),ON)
#echo TEST PASSED
else
#echo TEST FAILED
endif
No matter what I set the TEST variable to, my ifeq statement passes. I always see TEST PASSED. Anyone see what I am doing wrong here?
EDIT:
ok. my example was not exactly accurate. What I actually have is this:
SHELL = /bin/sh
DEFAULT_TARGS:= all all_debug
DEBUG_TARGS:= all_debug
ALL_TARGS:= $(DEFAULT_TARGS) $(DEBUG_TARGS)
.PHONY: $(ALL_TARGS)
.PHONY: buildbegin
$(ALL_TARGS): buildbegin
TEST=ON
$(DEBUG_TARGS): TEST=OFF
buildbegin:
#echo $(TEST)
ifeq ($(TEST),ON)
#echo PASSED
else
#echo FAILED
endif
Running either make all or make all_debug will result in "PASSED" being printed. If I echo $(TEST) before the condition, it looks as if my rules are changing the variable, but the ifeq only ever sees whatever the default value is.

make evaluates conditionals when it reads a makefile (as you know it uses 2 passes), see: Conditional Parts of Makefiles. You can simply check this by using warning (which is good thing to debug makefiles):
buildbegin:
#echo $(TEST)
$(warning now we reached ifeq TEST=$(TEST))
ifeq ($(TEST),ON)
#echo PASSED
else
#echo FAILED
endif
You should use shell commands instead and include them in rule:
buildbegin:
#if [ "$(TEST)" = "ON" ]; then echo "PASSED"; else echo "FAILED"; fi

Here's a cleaner (I think, anyway) way to do what you want:
all:
$(MAKE) TEST=ON buildbegin
all_debug:
$(MAKE) TEST=OFF buildbegin
buildbegin:
#echo $(TEST)
ifeq ($(TEST),ON)
#echo PASSED
else
#echo FAILED
endif

Parameterise the shell command using make variables. This avoids recursive make and even the shell (make will fork the command directly and not go via a shell if the command contains no shell metacharacters (<>"&; and the like)).
Something like:
result<ON> := PASSED
result<OFF> := FAILED
buildbegin:
#echo ${result-<${TEST}>}
The <...> is simply a convention to indicate some sort of indirection.

Although the question is old, I want to share a simpler way.
It is to use MAKECMDGOALSvariable that represents the list of targets.
In your case, a solution maybe:
buildbegin:
#echo $(MAKECMDGOALS)
ifeq ($(MAKECMDGOALS),all)
#echo PASSED
else ifeq ($(MAKECMDGOALS),debug_all)
#echo FAILED
endif
debug_all: buildbegin
all: buildbegin
Usage: make debug_all or make all

Related

Makefile variable value not available during ifeq

I have the following Makefile
SHELL=/bin/bash
.SHELLFLAGS=-O extglob -o errexit -o pipefail -o nounset -c
.PHONY: testing
define getFileContents
$(shell cat ./test.txt)
endef
TEST_STATIC=dummy
deploy:
$(eval TEST=$(getFileContents))
#echo "$(TEST)"
ifeq ($(TEST),dummy)
#echo "$(TEST) is FAILED"
else
#echo "$(TEST) is PASS"
endif
ifneq (,$(findstring dummy,$(TEST)))
#echo "$(TEST) is FAILED"
else
#echo "$(TEST) is PASS"
endif
ifeq ($(TEST_STATIC),dummy)
#echo "$(TEST) is FAILED"
else
#echo "$(TEST) is PASS"
endif
ifneq (,$(findstring dummy,$(TEST_STATIC)))
#echo "$(TEST) is FAILED"
else
#echo "$(TEST) is PASS"
endif
No matter what value I put in ./test.txt, I always go into PASS in both the ifeq & the findstring conditions but the variable's values show up properly in the echo statements. So the value is not available during the evaluation of ifeq
However, the if-else behaves properly for the TEST_STATIC variable.
Any help would be appreciated. Thanks.
ifeq is parsed while the makefile is read in. Even if it looks like it's part of a recipe, it isn't. You can tell that it isn't, because it isn't indented with a TAB character. Anything not indented by TAB, is part of the makefile not part of the recipe, and is parsed when the makefile is read in, not when the rule is run.
So by the time your rule is running and it gets to your eval, all the ifeq statements have long been expanded and dealt with.
In general, it's virtually never a good idea to use eval inside a recipe. It will almost never do what you're hoping for.
If you need to test some value inside a recipe then you have to write shell code to do it, not makefile code, and indent the shell code with a TAB so it's passed to the shell.

How to print text in a makefile outside a target?

For example, I am trying to test whether this works in my makefile preamble:
ifneq (,$(shell latexmk --version 2>/dev/null))
echo Works
else
echo Does not Works
endif
all:
do things...
Which does the error:
*** recipe commences before first target. Stop.
Then, how to prints things outside rules?
Makefile does not allow commands outside rules, or outside result:=$(shell ...).
In GNU Make there are $(info ...), $(warning ...) and $(error ...) built-in functions. Note that syntactically they are text substitutions, yet their return value is always an empty string (except $(error ...) which never returns), as it's with $(eval ...) etc. So they could be used almost everywhere.
Yet another option is $(file >/dev/stdout,...) (under Windows use "con").
After I found this question, https://unix.stackexchange.com/questions/464754/how-to-see-from-which-file-descriptor-output-is-coming
I think this kinda works:
ifneq (,$(shell latexmk --version 2>/dev/null))
useless := $(shell echo Works 1>&2)
else
useless := $(shell echo Does not Works 1>&2)
useless := $(error exiting...)
endif
all:
echo Hey sister, do you still believe in love I wonder...
Bonus:
Can I make a makefile abort outside of a rule?

Change directory before running any targets

I have the following use case where I read a variant at the top of a Makefile and then according this variable I must change directory to execute all the targets. I'd avoid to repeat in each target something like
my_target:
cd $(MY_DIR) &...
Any approach I could use to achieve that?
It's not 100% clear to me what you need but something like the following should work
ifndef submake
export submake=1
variant := somedir
$(MAKECMDGOALS):
$(MAKE) -C $(variant) -f $(realpath $(MAKEFILE_LIST)) $#
else
#actual targets defined here
foo:
#echo $#
bar:
#echo $#
endif
Go to the directory (cd) where you want Make to operate, and then
make -f /path/to/Makefile

If comparison in make file

I have a makefile. I need to check the exit status of a command and the performing comparison, if exit status is 0 then perform some display action. But i am getting the same message if its success or failure for both the scenario.
Please find the below code and Help me what is the right way to do this:-
FILES = test1.sh test2.sh
manoj: $(FILES)
ls $(FILES)
$(eval exitstatus="$(shell echo $$?)")
#echo $(exitstatus)
ifeq (0,$(exitstatus))
$(error something going wrong..........)
endif
clean: pwd
Getting same output as :
testmake.mk:4: *** something going wrong........... Stop.
for both ifeq ifeq (0,$(exitstatus)) and ifneq ifeq (0,$(exitstatus))
I want to perform some action if the condition is success otherwise nothing want to do.
In the recipe shell commands are used, not make directives.
And when target's recipe is executed that means all its prerequisites do exist:
manoj: $(FILES)
#echo "$(FILES) do exist at this point."

Makefile problem with conditional used in define used in commands part of a rule

I have a section of makefile that has this sort of structure:
bob:
ifdef DEBUG
#echo running
endif
#echo chug chug chug
ifdef DEBUG
#echo done
endif
bobit:
#echo "before"
#make bob
#echo "after"
I'm simplifying greatly here, all the echo's are actually non trivial blocks of commands and there is more conditional stuff, but this captures the essence of my problem.
For technical reasons I don't want to get into right now, I need to get rid of that submake, but because the echo's represent nontrivial amounts of code I don't want to just copy and past the body of bob in place of the submake.
Ideally what I'd like to do is something like this
define BOB_BODY
ifdef DEBUG
#echo running
endif
#echo chug chug chug
ifdef DEBUG
#echo done
endif
endef
bob:
$(BOB_BODY)
bobit:
#echo "before"
$(BOB_BODY)
#echo "after"
Unfortunately the conditionals seem to be shafting me, they produce "ifdef: Command not found" errors, I tried getting around this with various combinations of eval and call, but can't seem to figure out a way to get it to work.
How do I make this work? and is it even the right way to approach the problem?
The way I have fixed this is to use bash conditionals instead, which actually makes a certain amount of sense since we are playing with commands and not make rules.
So my ideal solution from above becomes something like
define BOB_BODY
#if [[ -n "$(DEBUG)" ]]; then \
echo running; \
fi;
#echo chug chug chug
#if [[ -n "$(DEBUG)" ]]; then \
echo done; \
fi
endef
bob:
$(BOB_BODY)
bobit:
#echo "before"
$(BOB_BODY)
#echo "after"
You can simply change the order of ifdef/define:
ifdef DEBUG
define BOB_BODY
#echo running
#echo chug chug chug
#echo done
endef
else
define BOB_BODY
#echo chug chug chug
endef
endif
UPDATE
define CHUG
#echo chug chug chug
endef
ifdef DEBUG
define BOB_BODY
#echo running
$(CHUG)
#echo done
endef
else
define BOB_BODY
$(CHUG)
endef
endif

Resources