Ifdef conditional unexpected behavior - bash

I have a Makefile with command like this:
#Makefile
hello:
echo 'hello'
echo $(TAG)
ifdef TAG
$(warning MYWARNING)
else
$(error MYERROR)
endif
I use it like:
# make TAG='1.0' hello
I expect that command performs echo 'hello', then echo $(TAG) and $(warning MYWARNING) but I get:
Makefile:17: MYWARNING
Makefile:19: *** MYERROR. Stop.
What is wrong?

Let's try some simpler cases(*).
hello:
echo hello
$(error MYERROR)
This produces:
Makefile:3: *** MYERROR. Stop.
Notice that the error blocks the echo, even thought it comes afterward.
Now let's try something silly:
hello:
ifdef TAG
The result is:
ifdef TAG
make: ifdef: No such file or directory
"ifdef TAG", interpreted as a shell command, makes no sense. And it is interpreted as a shell command because it's in a recipe and preceded by a TAB.
Now let's combine them:
hello:
ifdef TAG
$(error MYERROR)
The result is Makefile:3: *** MYERROR. Stop. So the error obscures the fact that the ifdef... was incorrect.
Do we want a shell conditional, or a Make conditional? If we want to Make to act on it (with error or warning), then it must be a Make conditional, so we must not preceded it with a TAB:
hello:
ifdef TAG
$(warning MYWARNING)
else
$(error MYERROR)
endif
This works as intended.
(*) as you ought to have tried before you posted.

Related

conditional inside a Makefile target

This a Makefile:
all:
ifeq (0,0)
echo hello
endif
Note: there is a tab before if, echo, and endif.
Giving make I get:
ifeq (0,0)
/bin/sh: -c: line 0: syntax error near unexpected token `0,0'
/bin/sh: -c: line 0: `ifeq (0,0)'
make: *** [Makefile:2: all] Error 1
Why is it so?
The trouble is that each line in a recipe runs in its own subshell, so the entire conditional must be in one line or it won't work. I'm not sure which shell you're using, but I presume you've tested this conditional on the command line and it works:
ifeq (0,0)
echo hello
endif
Without the right shell I can't test it, but I think you could also do something like this:
ifeq (0,0); echo hello; endif
If so, then it will also work in the makefile:
all:
ifeq (0,0); echo hello; endif
The lines can then be wrapped using backslashes:
all:
ifeq (0,0);\
echo hello;\
endif
(Note that the only TAB needed is before the ifeq.)

How ifeq from Makefile works?

I have a Makefile with content:
define SOME_FUNC
ifeq (n,y)
$(warning TRUE)
else
$(warning FALSE)
endif
endef
.PHONY: all
all:
$(eval $(call SOME_FUNC))
After executing "make" command I've got following output:
$ make
Makefile:10: TRUE
Makefile:10: FALSE
make: Nothing to be done for 'all'.
I cannot explain why it happens.
From the documentation:
It’s important to realize that the eval argument is expanded twice; first by the eval function, then the results of that expansion are expanded again when they are parsed as makefile syntax. This means you may need to provide extra levels of escaping for “$” characters when using eval.
You need to double the dollars to have those $(warning ...) functions evaluated on interpreting ifeq instead of expanding eval/call:
$ cat Makefile
define SOME_FUNC
ifeq (n,y)
$$(warning TRUE)
else
$$(warning FALSE)
endif
endef
.PHONY: all
all:
$(eval $(call SOME_FUNC))
$ make
Makefile:11: FALSE
make: Nothing to be done for 'all'.

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?

Stop executing makefile

I implement a recipe in order to pass all the remaining string to the command, as example in this script:
Makefile
run:
# ./bin/run.sh $(filter-out $#,$(MAKECMDGOALS))
#echo $(filter-out $#,$(MAKECMDGOALS))
But when I run as example:
>make run my custom input params
my custom input params
make: *** No rule to make target `my'. Stop.
makefile try to execute also the remaining string so the error:
make: *** No rule to make target `my'. Stop.
How can I prevent this?
NB: As workaround I define a dummy recipe:
%:
#echo
So this will print an empty string instead of the error.
I want to avoid to do something like:
make run-example param="my custom param"
You can probably achieve what you want with a match-anything rule. Example (using a dummy printf recipe instead of a real one):
PARAMS := $(filter-out run,$(MAKECMDGOALS))
run:
#printf './bin/run.sh $(PARAMS)\n'
%:;
Demo:
$ make run my custom input params
./bin/run.sh my custom input params
make: 'my' is up to date.
make: 'custom' is up to date.
make: 'input' is up to date.
make: 'params' is up to date.
You can ignore the make: 'target' is up to date. messages or use the --quiet option (or --silent or -s):
$ make --quiet run my custom input params
./bin/run.sh my custom input params
If your Makefile is more complex than this, the match-anything rule could be a problem because it could catch other targets that you do not want to be caught. In this case make conditionals are an option:
ifeq ($(SECONDPASS),)
PARAMS := $(filter-out run,$(MAKECMDGOALS))
run:
#$(MAKE) --quiet $# PARAMS='$(PARAMS)' SECONDPASS=true
%:;
else
run:
#printf './bin/run.sh $(PARAMS)\n'
# other rules if any
endif
Finally, if the name of the first goal is not always the same, you can adapt this with:
GOAL := $(firstword $(MAKECMDGOALS))
PARAMS := $(filter-out $(GOAL),$(MAKECMDGOALS))
$(GOAL):
#printf './bin/$(GOAL).sh $(PARAMS)\n'
%:;
Or:
GOAL := $(firstword $(MAKECMDGOALS))
ifeq ($(SECONDPASS),)
PARAMS := $(filter-out $(GOAL),$(MAKECMDGOALS))
$(GOAL):
#$(MAKE) --quiet $# PARAMS='$(PARAMS)' SECONDPASS=true
%:;
else
$(GOAL):
#printf './bin/$(GOAL).sh $(PARAMS)\n'
# other rules if any
endif
Demo:
$ make --quiet nur foo bar
./bin/nur.sh foo bar
I don't think you should use a Makefile. You want to do your own parsing of the options, and that's more trouble to do in make.
If you're dead set on it, you could do this:
%:
#true
...which will avoid printing an empty line.
It would be better to do this in Bash, though. Here's one way you could do it:
#!/usr/bin/env bash
if [ $# -lt 1 ]; then
echo Not enough args
exit 1
fi
case "$1" in
"run")
shift
./bin/run.sh $#
;;
*)
echo "Command $1 not recognized"
exit 1
;;
esac
This seems easier and more extensible.
You can always pass / set ENV variables before executing make if you only want to pass variables to make or to a shell script.
MYPARAM1=123 MYPARAM2=456 make
OR using a subshell
(MYPARAM1=123; MYPARAM2=456; echo A=$MYPARAM1 B=$MYPARAM2; make)
(export MYPARAM1=123; export MYPARAM2=456; A=$MYPARAM1 B=$MYPARAM2; make) // exporting
You might also look at the bash-shell-special-parameters

Gnu make conditional on environment variable

I have the following in a Makefile:
ifndef MYVAR
$(error "MYVAR is not set")
else
$(warning "MYVAR is set to [$(MYVAR)]")
ifeq ($(MYVAR),"abc")
$(error "Value is known.")
else
$(error "Not known.")
endif
endif
I set MYVAR when calling make:
MYVAR=abc make
I would have expected to see "Value is known." but I get the following:
Makefile:4: "MYVAR is set to [abc]"
Makefile:8: *** "Not known.". Stop.
Could someone please enlighten me on what is wrong with my ifeq statement?
Question answered by #melpomene in the comments. As so often before, a quotation error.
ifeq ($(MYVAR),abc) or MYVAR='"abc"' make

Resources