If Else syntax evaluating wrong clause in GNU Makefile - bash

I am trying to get if else syntax working in Makefile
TYPE=src
RTL=src
program_%:
ifeq ($(TYPE),$(RTL))
echo "RTL"
else
echo "Test"
endif
Here is the command line
$make -f test.make prog_src
and I get the following in the output
echo "RTL"
RTL
However, when i change the if statement such that instead of hard-coded variables I do something like
program_%:
ifeq ($(TYPE),$*)
echo "RTL"
else
echo "Test"
endif
and run as follows
$make -f test.make prog_src
I get the wrong clause evaluated
echo "Test"
Test

remove the whitespaces in the condition
ifeq ($(TYPE),$(RTL))
Regarding the second part of the question:
No; this is not possible ifeq is evaluated in the context of the makefile and $* is not expanded there. You can try $(if ...) but checking equality is not directly possible:
program_%:
#echo $(if $(filter ${TYPE},$*),"RTL","Test")
"Context of makefile" means that if* sees only global make variables. Special variables like $* are available in the rule context only.
Complex rules
complex rules are possible by
define complex_rule_A
#echo "complex_rule_A"
#echo "done"
endef
define complex_rule_B
#echo "complex_rule_B"
#echo "failed"
endef
program_%:
$(if $(filter ${TYPE},$*),$(call complex_rule_A),$(call complex_rule_B))
Alternative solutions
When ${TYPE} can be enumerated, a more clean solution might be
program_%: .program_${TYPE}_%
:
.program_RTL_%:
echo else
.program_RTL_RTL:
echo RTL

Related

Make $* check for string

I am trying to check if $* matches hello . But the following is not working
build: build-hello
build-%:
ifeq ($*, hello)
echo Hello
else
echo World
endif
The conditions in the ifeq's are processed at makefile read time -- when $* is still blank. There's a couple of workarounds to this: First, you could do a build-hello: rule, which would override the build-% rule for build-hello. If, on the other hand you wanted to minimize rules, you could use the $(if) function as so:
build-%:
#echo $(if $(filter $*,hello),Hello,World)
Or, you could just use shell logic to accomplish this as well.

How do computed variables passed to included make files' functions get expanded?

To reduce the amount of repeated boiler plate in a top level makefile, I created an included make file that uses computed variable names. Where I'm having difficulty is in the excerpt from the included makefile: ftp-files.mk:
...
$($(FNMPFX)_FTP_CFG): $(CFG_MAKE_FILE) | $($(FNMPFX)_FTP_CFG_DIR)
$(call ftp_helper, $#, $($(FNMPFX)_FTP_DIR), $($(FNMPFX)_CACHE_DIR), $($(FNMPFX)_FTP_NAME))
...
In the main makefile, I was hoping to do something akin to:
CFG_MAKE_FILE := Makefile
define ftp_helper
echo "quote USER anonymous" > $(1)
echo "quote PASS" >> $(1)
echo "cd $(2)" >> $(1)
echo "lcd $(3)" >> $(1)
echo "binary" >> $(1)
echo "get $(4)" >> $(1)
echo "quit" >> $(1)
endef
FNMPFX := FILE_A
include ftp-files.mk
...
FNMPFX := FILE_Z
include ftp-files.mk
...
The trouble is that the order only prerequisite (also tried it as a normal prerequisite) expands to the last ... instanciation (?) of the included file.
What appears to be happening is a first expansion of the two targets that behave as though they were written:
# Point of confusion V
# |
$(FILE_A_FTP_CFG): $(CFG_MAKE_FILE) | $(FILE_Z_FTP_CFG_DIR)
$(call ftp_helper, $#, $(FILE_A_FTP_DIR), $(FILE_A_CACHE_DIR), $(FILE_A_FTP_NAME))
$(FILE_Z_FTP_CFG): $(CFG_MAKE_FILE) | $(FILE_Z_FTP_CFG_DIR)
$(call ftp_helper, $#, $(FILE_Z_FTP_DIR), $(FILE_Z_CACHE_DIR), $(FILE_Z_FTP_NAME))
Is this possible?
My work around was to include that simple target rule in the main Makefile (ftp-file.mk is reasonably wordy at 100 lines), so having those two lines (repeated) throughout the main Makefile isn't too burdensome.
Can somebody suggest a working alternative?
In general you can't read a whole Makefile like a shell script. It's read in multiple phases, and certain things are done with the entire contents of the Makefile before proceeding to the next step. The rules are rather complex, but suffice it to say that it looks like the last assigned value is used:
$ cat Makefile
variable := original
first:
echo $(variable)
variable := other
second:
echo $(variable)
$ make first
echo other
other
$ make second
echo other
other

Set variable in a rule and have it available in another rule

I've the following makefile:
.ONESHELL:
SHELL := /bin/bash
build-%:
#[ $(findstring -, $*) ] && DIR_ENV=$(subst -,/,$*) || DIR_ENV=$*
#echo ${DIR_ENV}
I'm trying to have available the DIR_ENV but without no luck. I know that every command executed is executed in its own shell so no sharing of variavbles. However I've added ONESHELL directive. But it still doesn't work. What am I'm missing?
You are missing the fact the string ${DIR_ENV} is evaluated by make first and the resulting value is a null string. Use this
build-%:
#[ $(findstring -, $*) ] && DIR_ENV=$(subst -,/,$*) || DIR_ENV=$*
#echo $${DIR_ENV}
Also the Make syntax
$(if $(findstring -,$*),$(subst -,/,$*),$*)
Does the if/then/else logic in Make, not the shell

Running expressions (without compilation of files) in Makefile without targets?

I wanted to test some expressions of the ifeq kind that run a shell command that I read somewhere, so I wrote this tiny mymakefile (all lines being indented with a tab):
ifeq ($(shell echo test 2>/dev/null; echo $$?),0)
$(info I am inside)
endif
... and I tried to run it:
$ make -f mymakefile
make: *** No targets. Stop.
How could I test expressions like this inside their own makefile? Do I need to define a default target, or not? And how should the commands be formatted (indented with a tab, or space, or not indented at all?)
Well, I got somewhere - apparently, one must specify a target; but since I'm a make noob, I would love to see a more qualified answer.
I found this link https://www.gnu.org/software/make/manual/html_node/Conditional-Example.html that gave me a hint.. Anyways, this is mymakefile now:
.PHONY: default
default: mytarget;
ifeq ($(shell echo test 2>/dev/null; echo $$?),0)
$(info I am inside)
else
$(info I am outside)
endif
mytarget:
\t (TAB) echo A
So, the mytarget here is just a dummy, which simply does an echo A; running this prints:
$ make -f mymakefile
I am outside
echo A
A
If you don't want the echo A printed, suppress it with at sign: #echo A.
The echo A line has to be indented with a TAB - else error "mymakefile:11: *** missing separator. Stop.".
Strangely, if I indent the two $(info... lines with a TAB, then "I am outside" is printed last (?!), but when they are not indented (or indented with spaces), then it is printed first (as per the order in the file).

How to abort makefile if variable not set?

How could I abort a make/makefile execution based on a makefile's variable not being set/valued?
I came up with this, but works only if caller doesn't explicitly run a target (i.e. runs make only).
ifeq ($(MY_FLAG),)
abort: ## This MUST be the first target :( ugly
#echo Variable MY_FLAG not set && false
endif
all:
#echo MY_FLAG=$(MY_FLAG)
I think something like this would be a good idea, but didn't find anything in make's manual:
ifndef MY_FLAG
.ABORT
endif
TL;DR: Use the error function:
ifndef MY_FLAG
$(error MY_FLAG is not set)
endif
Note that the lines must not be indented. More precisely, no tabs must precede these lines.
Generic solution
In case you're going to test many variables, it's worth defining an auxiliary function for that:
# Check that given variables are set and all have non-empty values,
# die with an error otherwise.
#
# Params:
# 1. Variable name(s) to test.
# 2. (optional) Error message to print.
check_defined = \
$(strip $(foreach 1,$1, \
$(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
$(if $(value $1),, \
$(error Undefined $1$(if $2, ($2))))
And here is how to use it:
$(call check_defined, MY_FLAG)
$(call check_defined, OUT_DIR, build directory)
$(call check_defined, BIN_DIR, where to put binary artifacts)
$(call check_defined, \
LIB_INCLUDE_DIR \
LIB_SOURCE_DIR, \
library path)
This would output an error like this:
Makefile:17: *** Undefined OUT_DIR (build directory). Stop.
Notes:
The real check is done here:
$(if $(value $1),,$(error ...))
This reflects the behavior of the ifndef conditional, so that a variable defined to an empty value is also considered "undefined". But this is only true for simple variables and explicitly empty recursive variables:
# ifndef and check_defined consider these UNDEFINED:
explicitly_empty =
simple_empty := $(explicitly_empty)
# ifndef and check_defined consider it OK (defined):
recursive_empty = $(explicitly_empty)
As suggested by #VictorSergienko in the comments, a slightly different behavior may be desired:
$(if $(value $1) tests if the value is non-empty. It's sometimes OK if the variable is defined with an empty value. I'd use $(if $(filter undefined,$(origin $1)) ...
And:
Moreover, if it's a directory and it must exist when the check is run, I'd use $(if $(wildcard $1)). But would be another function.
Target-specific check
It is also possible to extend the solution so that one can require a variable only if a certain target is invoked.
$(call check_defined, ...) from inside the recipe
Just move the check into the recipe:
foo :
#:$(call check_defined, BAR, baz value)
The leading # sign turns off command echoing and : is the actual command, a shell no-op stub.
Showing target name
The check_defined function can be improved to also output the target name (provided through the $# variable):
check_defined = \
$(strip $(foreach 1,$1, \
$(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
$(if $(value $1),, \
$(error Undefined $1$(if $2, ($2))$(if $(value #), \
required by target `$#')))
So that, now a failed check produces a nicely formatted output:
Makefile:7: *** Undefined BAR (baz value) required by target `foo'. Stop.
check-defined-MY_FLAG special target
Personally I would use the simple and straightforward solution above. However, for example, this answer suggests using a special target to perform the actual check. One could try to generalize that and define the target as an implicit pattern rule:
# Check that a variable specified through the stem is defined and has
# a non-empty value, die with an error otherwise.
#
# %: The name of the variable to test.
#
check-defined-% : __check_defined_FORCE
#:$(call check_defined, $*, target-specific)
# Since pattern rules can't be listed as prerequisites of .PHONY,
# we use the old-school and hackish FORCE workaround.
# You could go without this, but otherwise a check can be missed
# in case a file named like `check-defined-...` exists in the root
# directory, e.g. left by an accidental `make -t` invocation.
.PHONY : __check_defined_FORCE
__check_defined_FORCE :
Usage:
foo :|check-defined-BAR
Notice that the check-defined-BAR is listed as the order-only (|...) prerequisite.
Pros:
(arguably) a more clean syntax
Cons:
One can't specify a custom error message
Running make -t (see Instead of Executing Recipes) will pollute your root directory with lots of check-defined-... files. This is a sad drawback of the fact that pattern rules can't be declared .PHONY.
I believe, these limitations can be overcome using some eval magic and secondary expansion hacks, although I'm not sure it's worth it.
Use the shell function test:
foo:
test $(something)
Usage:
$ make foo
test
Makefile:2: recipe for target 'foo' failed
make: *** [foo] Error 1
$ make foo something=x
test x
You can use an IF to test:
check:
#[ "${var}" ] || ( echo ">> var is not set"; exit 1 )
Result:
$ make check
>> var is not set
Makefile:2: recipe for target 'check' failed
make: *** [check] Error 1
For simplicity and brevity:
$ cat Makefile
check-%:
#: $(if $(value $*),,$(error $* is undefined))
bar:| check-foo
echo "foo is $$foo"
With outputs:
$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
Use the shell error handling for unset variables (note the double $):
$ cat Makefile
foo:
echo "something is set to $${something:?}"
$ make foo
echo "something is set to ${something:?}"
/bin/sh: something: parameter null or not set
make: *** [foo] Error 127
$ make foo something=x
echo "something is set to ${something:?}"
something is set to x
If you need a custom error message, add it after the ?:
$ cat Makefile
hello:
echo "hello $${name:?please tell me who you are via \$$name}"
$ make hello
echo "hello ${name:?please tell me who you are via \$name}"
/bin/sh: name: please tell me who you are via $name
make: *** [hello] Error 127
$ make hello name=jesus
echo "hello ${name:?please tell me who you are via \$name}"
hello jesus
Another option:
MY_FLAG = $(error Please set this flag)
Attempting to use this variable anywhere will cause an error, unless it's overriden from the command line.
To accept environment variables as well, use ?=:
MY_FLAG ?= $(error Please set this flag)

Resources