Evaluation of GNU make conditionals - makefile

The following snippet
ifeq (1,1)
a = 1
$(info true)
endif
ifeq (1,0)
a = 0
$(info false)
endif
$(info $(a))
prints
true
1
And I get it. If I were to put the same thing inside a define and then evaluate
define foo
ifeq (1,1)
a = 1
$(info true)
endif
ifeq (1,0)
a = 0
$(info false)
endif
endef
$(eval $(call foo))
$(info $(a))
It prints
true
false
1
The ifeq (1,0) doesn't evaluates to true (as the value of $(a) is 1 finally). But then why did it print false?

Because before make parses the results of the evaluation, it first expands the string to be evaluated.
The info function is being expanded when the argument to eval is being expanded, before make parses the code. You need to defer the info until eval examines it by escaping the $:
define foo
ifeq (1,1)
a = 1
$$(info true)
endif
ifeq (1,0)
a = 0
$$(info false)
endif
endef

Related

Succinctly adding verbose output to GNU make

I would like to add verbose output to a gnu makefile if V=1 is passed on the command line.
I can already do it in three lines as follows:
ifeq ($(V),1)
$(info SRC_FILES=$(SRC_FILES))
endif
Is there some more terse idiom, e.g., that appears all on one line? Ideally I'd like something like:
$(verbose SRC_FILES=$(SRC_FILES))
which may not be possible, or at least a one-liner like:
$(if $(V) $(info SRC_FILES=$(SRC_FILES)))
$(info $(if $(V),SRC_FILES=$(SRC_FILES))) almost does what you want. Its only drawback is that it outputs an empty line when V is undefined.
EDIT: from MadScientist's remark, and as initially suggested by BeeOnRope, $(if $(V),$(info SRC_FILES=$(SRC_FILES))) works exactly as expected, without the empty line when V is undefined.
You could also define a macro that prints an info message about a variable if and only if V is defined:
define verbose
$(if $(V),$(info $(1) = $($(1))))
endef
$(call verbose,SRC_FILES)
Of course, if you want a more generic macro, you can pass it the text to print:
define verbose
$(if $(V),$(info $(1)))
endef
$(call verbose,SRC_FILES = $(SRC_FILES))
Note that there are other types of information that you may want to control with a verbosity level variable. For the commands echoing, the commands outputs and the quiet command options I frequently use the following:
# Verbosity
ifeq ($(V),)
Q := #
MQ := --quiet
ECHO := echo
OUT := &> /dev/null
else ifeq ($(V),1)
Q :=
MQ :=
ECHO := echo
OUT :=
else
$(error V: invalid value ($(V)))
endif
foo:
$(Q)$(ECHO) 'making $#' && \
some-command $# $(OUT)
bar: cuz
$(Q)$(MAKE) $(MQ) $#
...
Maybe this will be practical:
verbose = $(if $(VERBOSITY),$(info $(-verbose)))
-verbose = $(if $(findstring 0,$(VERBOSITY)),$1 )$(if $(findstring 1,$(VERBOSITY)),$2 )$(if $(findstring 2,$(VERBOSITY)),$3 )$(if $(findstring 3,$(VERBOSITY)),$4 )$(if $(findstring 4,$(VERBOSITY)),$5 )$(if $(findstring 5,$(VERBOSITY)),$6 )$(if $(findstring 6,$(VERBOSITY)),$7 )$(if $(findstring 7,$(VERBOSITY)),$8 )$(if $(findstring 8,$(VERBOSITY)),$9 )$(if $(findstring 9,$(VERBOSITY)),$(10) )
VERBOSITY = 0
$(call verbose,warninglevel 1,warninglevel 2,warninglevel 3,info 1,info 2,info 3)
VERBOSITY = 2
$(call verbose,warninglevel 1,warninglevel 2,warninglevel 3,info 1,info 2,info 3)
VERBOSITY = 012345
$(call verbose,warninglevel 1,warninglevel 2,warninglevel 3,info 1,info 2,info 3)
Output:
warninglevel 1
warninglevel 3
warninglevel 1 warninglevel 2 warninglevel 3 info 1 info 2 info 3
I have added it to gmtt.

conditional execution in makefile

I have the following makefile:
C_FILE=""
cfg:
## C to CFG ####
# echo $(C_FILE)
ifndef C_FILE
$(error variable C_FILE not set)
endif
$(eval CFG_FILE := ./outputs/temp/$(shell basename $(C_FILE) .c).cfg)
gcc -fdump-tree-cfg=$(CFG_FILE) $(C_FILE)
When I run the command make cfg C_FILE="./inputs/Fib.c" it always
terminates saying variable C_FILE not set.
Lines beginning with a tab character (by default) aren't parsed by make (other than for variable expansion), they're sent directly to the shell, get rid of the indents on the lines with the make conditionals
C_FILE=""
cfg:
## C to CFG ####
# echo $(C_FILE)
ifndef C_FILE
$(error variable C_FILE not set)
endif
$(eval CFG_FILE := ./outputs/temp/$(shell basename $(C_FILE) .c).cfg)
gcc -fdump-tree-cfg=$(CFG_FILE) $(C_FILE)
I'd like to add some comments to user657267's answer.
ifndef C_FILE is always false. C_FILE is defined on the first line, or from command line. Consider using ifeq "" "$(C_FILE)".
Quote (") is normal character in makefile. Define empty variable this way:
C_FILE=
instead of using $(shell ) function, use makefile built-ins:
$(basename $(notdir $(C_FILE)))
avoid using $(eval ) if not really needed. Extract relevant code outside recipe.
My proposal is:
C_FILE=
ifeq "" "$(C_FILE)"
$(error variable C_FILE not set)
endif
CFG_FILE=./outputs/temp/$(basename $(notdir $(C_FILE))).cfg
cfg:
## C to CFG ####
# echo $(C_FILE)
gcc -fdump-tree-cfg=$(CFG_FILE) $(C_FILE)

Test if a list of variables are defined in a Makefile

My Makefile is based on multiple variables defined in a configuration file or ENV vars. My current solution is to test all of them manually:
NOGOAL = help clean distclean mrproper
ifeq ($(strip $(filter $(NOGOAL), $(MAKECMDGOALS))),)
VAR1 ?= $(error VAR1 undefined)
VAR2 ?= $(error VAR2 undefined)
VAR3 ?= $(error VAR3 undefined)
...
VARn ?= $(error VARn undefined)
endif
I would like to use a foreach loop instead:
ifeq ($(strip $(filter $(NOGOAL), $(MAKECMDGOALS))),)
TESTVAR = TEST1 TEST2 TEST3
$(foreach v, $(TESTVAR), $(eval $v ?= $$(warning Error: $v undefined)))
endif
Unfortunately eval doesn't work as I expected. Did I miss something?
Here a full test of my tests with 2 implementations of the tests. Even if TEST3 is not defined I don't get any error
TEST1 = 1
TEST2 = 1
#TEST3 = 1 # NOT DEFINED
TESTVAR := TEST1 TEST2 TEST3
# First implementation
$(foreach v, $(TESTVAR), $(eval $v ?= $$(warning Error: $v undefined)))
# Second implementation
$(foreach v, $(TESTVAR), $(eval $(call TESTER,$v)))
define TESTER
ifndef $1
$(warning $1 not defined)
endif
endef
# Dummy rule
all:
#echo Hello World
However, my first implementation works if I use $(TEST3) somewhere.
EDIT
Here I get no error but TEST3 is not defined:
~$ cat Makefile
TEST1 = 1
TEST2 = 1
#TEST3 = 1 # NOT DEFINED
TESTVAR := TEST1 TEST2 TEST3
# First implementation
$(foreach v, $(TESTVAR), $(eval $v ?= $$(warning Error: $v undefined)))
# Dummy rule
all:
#echo Hello World
~$ make
Hello World
Well, I guess I don't get it. Your original version, that you say works the way you want it, will not print any warnings unless you USE one of the variables which is not defined. Your first alternative with foreach works the same way: it will print a warning but only when you use the variable that's undefined.
If you want it that way, then testing for clean, etc. doesn't really make much sense since presumably those rules won't use the variables that are not defined so you won't get any errors (and if they did use the variables that weren't defined, presumably you'd want those rules to fail as well).
But in your second edit, you say that you want the make to fail immediately if the variables are not defined, regardless of whether or not they're used (in your last example you don't define TEST3, but you don't use TEST3 for anything either so no warning is printed). If that's what you want I don't see why you are assigning values to the variables with ?= at all, or using eval. Just write something like:
ifeq ($(strip $(filter $(NOGOAL), $(MAKECMDGOALS))),)
$(foreach v,$(TESTVAR),$(if $($v),,$(error Error: $v undefined))
endif
(In this version you do need to check MAKECMDGOALS since it fails immediately on an unset variable).

Makefile check existence of variable from list

I have a Makefile:
#Build Configurations
CONFIGS = Debug Release Profile
#Config flags
Debug_Flags=dasd
Release_Flags=
.SECONDEXPANSION:
#Debug_safety_check Release_safety_check Profile_safety_check targets
$(addsuffix _safety_check,$(CONFIGS)):
#Check existence of variable
ifeq '$(origin $(subst safety_check,FLAGS,$#))' 'undefined'
$(error $(subst safety_check,FLAGS,$#) variable undefined)
endif
#How to make our configurations (do corresponding safety_checks)
$(CONFIGS): $$#_safety_check
This line is incorrect:
ifeq '$(origin $(subst safety_check,FLAGS,$#))' 'undefined'
I think, It is because $# expansion occurs, when calling corresponding safety check. But ifeq expansion occurs "immediately", so, in fact, we get such line:
ifeq '$(origin ' ') 'undefined'
Does some method of check variables definition from list exist?
Ufff, gotcha.
#Build Configurations
CONFIGS = Debug Release Profile
#Config flags
Debug_Flags=dasd
Release_Flags=
#Profile_Flags=
#Adds _flags suffix for each variable,
#get it's origin ('undefined' for undefined variables).
#If $(findstring) a lookup for the word 'undefined' succeeds,
#adds to the result variable
undef_flags = $(foreach c, $(CONFIGS), \
$(if $(findstring undefined, $(origin $c_Flags)), $c))
#Count words for undefined configs, must be not '0'
ifneq ($(words $(undef_flags)),0)
#strip because if flag is undefined we'll get the trash spaces
$(error Flags ($(strip $(undef_flags))) must be defined)
endif

Makefile ifeq logical or

How do you perform a logical OR using make's ifeq operator?
e.g., I have (simplified):
ifeq ($(GCC_MINOR), 4)
CFLAGS += -fno-strict-overflow
endif
ifeq ($(GCC_MINOR), 5)
CFLAGS += -fno-strict-overflow
endif
but would like to consolidate these lines.
(yes, yes, autotools, configure, etc etc; too heavy-handed for the current situation, would like to keep everything within the Makefile here)
[logical opposite of this question: How to Use of Multiple condition in 'ifeq' statement ]
As found on the mailing list archive,
http://osdir.com/ml/gnu.make.windows/2004-03/msg00063.html
http://osdir.com/ml/gnu.make.general/2005-10/msg00064.html
one can use the filter function.
For example
ifeq ($(GCC_MINOR),$(filter $(GCC_MINOR),4 5))
filter X, A B will return those of A,B that are equal to X.
Note, while this is not relevant in the above example, this is a XOR operation. I.e. if you instead have something like:
ifeq (4, $(filter 4, $(VAR1) $(VAR2)))
And then do e.g. make VAR1=4 VAR2=4, the filter will return 4 4, which is not equal to 4.
A variation that performs an OR operation instead is:
ifneq (,$(filter $(GCC_MINOR),4 5))
where a negative comparison against an empty string is used instead (filter will return en empty string if GCC_MINOR doesn't match the arguments). Using the VAR1/VAR2 example it would look like this:
ifneq (, $(filter 4, $(VAR1) $(VAR2)))
The downside to those methods is that you have to be sure that these arguments will always be single words. For example, if VAR1 is 4 foo, the filter result is still 4, and the ifneq expression is still true. If VAR1 is 4 5, the filter result is 4 5 and the ifneq expression is true.
One easy alternative is to just put the same operation in both the ifeq and else ifeq branch, e.g. like this:
ifeq ($(GCC_MINOR),4)
#echo Supported version
else ifeq ($(GCC_MINOR),5)
#echo Supported version
else
#echo Unsupported version
endif
You can introduce another variable. It doesnt consolidate both checks, but it at least avoids having to put the body in twice:
do_it =
ifeq ($(GCC_MINOR), 4)
do_it = yes
endif
ifeq ($(GCC_MINOR), 5)
do_it = yes
endif
ifdef do_it
CFLAGS += -fno-strict-overflow
endif
I don't think there's a concise, sensible way to do that, but there are verbose, sensible ways (such as Foo Bah's) and concise, pathological ways, such as
ifneq (,$(findstring $(GCC_MINOR),4-5))
CFLAGS += -fno-strict-overflow
endif
(which will execute the command provided that the string $(GCC_MINOR) appears inside the string 4-5).
Here more flexible variant: it uses external shell, but allows to check for arbitrary conditions:
ifeq ($(shell test ".$(GCC_MINOR)" = .4 -o \
".$(GCC_MINOR)" = .5 -o \
".$(TODAY)" = .Friday && printf "true"), true)
CFLAGS += -fno-strict-overflow
endif
Note that ifeq ($(GCC_MINOR),$(filter $(GCC_MINOR),4 5)) will catch the case where GCC_MINOR is not defined at all.
If you want to catch GCC_MINOR==4 or GCC_MINOR==5 this will do trick:
ifneq ($(filter $(GCC_MINOR),4 5),)
echo "def"
endif
ifeq ($(GCC_MINOR), 4)
CFLAGS += -fno-strict-overflow
endif
ifeq ($(GCC_MINOR), 5)
CFLAGS += -fno-strict-overflow
endif
Another you can consider using in this case is:
GCC42_OR_LATER = $(shell $(CXX) -v 2>&1 | $(EGREP) -c "^gcc version (4.[2-9]|[5-9])")
# -Wstrict-overflow: http://www.airs.com/blog/archives/120
ifeq ($(GCC42_OR_LATER),1)
CFLAGS += -Wstrict-overflow
endif
I actually use the same in my code because I don't want to maintain a separate config or Configure.
But you have to use a portable, non-anemic make, like GNU make (gmake), and not Posix's make.
And it does not address the issue of logical AND and OR.
In the case that you are looking to logically "or" several boolean flags together, one practical hack can be to simply let strings concatenate: if the end result is an empty string, then none of the options were true, else non-empty then at least one of them was enabled:
# Use `docker build --pull` in case either `PULL=whatever` is set OR if the `CI` environment variable is present.
ifneq ($(PULL)$(CI),)
PULL_OR_NOT := --pull
endif
build:
docker build $(PULL_OR_NOT)

Resources