Compare integral numbers in a makefile? - shell

I have the following in my GNU makefile:
# Undefined Behavior Sanitizer (Clang 3.2 and GCC 4.8 and above)
UBSAN = 0
ifeq ($(findstring ubsan,$(MAKECMDGOALS)),ubsan)
UBSAN = 1
CXXFLAGS += -fsanitize=undefined
endif # UBsan
# Address Sanitizer (Clang 3.2 and GCC 4.8 and above)
ASAN = 0
ifeq ($(findstring asan,$(MAKECMDGOALS)),asan)
ASAN = 1
CXXFLAGS += -fsanitize=address
endif # Asan
# Effectively a NOR
ifneq ($(shell echo $$($(ASAN) * $(UBSAN))),0)
$(error Asan and UBsan are mutually exclusive. Specify only one of them)
endif
The idea is to detect make ubsan asan (or user overrides to CFLAGS and CXXFLAGS), and error out if both are specified.
Unfortunately, its firing with no command targets:
$ make
/bin/sh: 1: 0: not found
GNUmakefile:64: *** Asan and UBsan are mutually exclusive. Specify only one of them. Stop.
I also tried quoting the "0" with the same result: ifneq ($(shell echo $$($(ASAN) * $(UBSAN))),"0").
How do I compare two integral values in a makefile?

Your problem is a simple typo.
You left out one (/) in the arithmetic expansion in the $(shell) command.
Your shell command is echo $(0 * 0) which the shell sees as a command substitution not arithmetic expansion and then tries to run 0 <expansion of all files in the current directory> 0. Which is why you get the /bin/sh: 1: 0: not found error message.
Add the missing (/) and your problem goes away.
ifneq ($(shell echo $$(($(ASAN) * $(UBSAN)))),0)
That being said I wouldn't bother using the shell for this at all (it is expensive).
All you are trying to test, in this case, is that $(ASAN) and $(UBSAN) are not both 1. So just assert that.
ifeq ($(ASAN)$(UBSAN),11)
$(error Asan and UBsan are mutually exclusive. Specify only one of them)
endif
If you want to be safer about manual assignment of some other value (e.g. make ASAN=11 or something) then you could do something more like:
ifeq ($(filter-out 1,$(ASAN))$(filter-out 1,$(UBSAN)),)
$(error Asan and UBsan are mutually exclusive. Specify only one of them)
endif

Related

Conditional inclusion of sub-Makefiles based on ifeq test

Is there a way to conditionally include a sub-Makefile based on the result of a an ifeq test as suggested below
CC = g++
CENTOS_VERSION := $(shell rpm -E %{rhel})
TARGET = main
$(TARGET): $(TARGET).cpp
ifeq ($(CENTOS_VERSION),6)
#echo "Building on CentOS 6"
include(CentOS6_Makefile.mk)
else
#echo "Building on CentOS 7"
include(CentOS7_Makefile.mk)
endif
$(CC) $(CFLAGS) -o $(TARGET) $(TARGET).cpp
This does not work and generates the error message
Building on CentOS 6
include(CentOS6_Makefile.mk)
/bin/sh: -c: line 0: syntax error near unexpected token `CentOS6_Makefile.mk'
/bin/sh: -c: line 0: `include(CentOS6_Makefile.mk)'
make: *** [main] Error 1
Based on the answer by #MadScientist the solution to my problem boils down to just 2 lines
CENTOS_VERSION := $(shell rpm -E %{rhel})
include CentOS$(CENTOS_VERSION)_Makefile.mk
Your problem is not related to ifeq; if you remove the ifeq and always include one or the other you'll see the same problem.
First, your syntax for including files is wrong. There are no parentheses around filenames in make's include directive. It should just be:
include CentOS6_Makefile.mk
Second, you cannot use makefile processor commands like include as part of a recipe (that is, indented by a TAB). In a make recipe ALL lines that indented by TAB are passed to the shell as commands to run to build the target, they are not interpreted by make (except to expand macros). Also, you cannot include some other makefile in the middle of a recipe: once make starts to include a new makefile that's the end of any recipe that is currently being defined.
You can do this:
CENTOS_VERSION := $(shell rpm -E %{rhel})
ifneq ($(CENTOS_VERSION),6)
CENTOS_VERSION := 7
endif
include CentOS$(CENTOS_VERSION)_Makefile.mk
$(TARGET): $(TARGET).cpp
#echo "Building on CentOS $(CENTOS_VERSION)"
$(CC) $(CFLAGS) -o $(TARGET) $(TARGET).cpp

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?

How to detect shell used in GNU make?

How can I detect shell that will be used by gnu make? I want my make to be running on following platforms:
linux
windows
windows with cygwin (Note that on windows with cygwin the make uses cygwin shell)
Then I would like to detect if gcc is present on the system independently on OS.
So far I come up with this solution:
# detect what shell is used
ifeq ($(findstring cmd.exe,$(SHELL)),cmd.exe)
$(info "shell Windows cmd.exe")
DEVNUL := NUL
WHICH := where
else
$(info "shell Bash")
DEVNUL := /dev/null
WHICH := which
endif
# detect platform independently if gcc is installed
ifeq ($(shell ${WHICH} gcc 2>${DEVNUL}),)
$(error "gcc is not in your system PATH")
else
$(info "gcc found")
endif
optionally when I need to detect more tools I can use:
EXECUTABLES = ls dd
K := $(foreach myTestCommand,$(EXECUTABLES),\
$(if $(shell ${WHICH} $(myTestCommand) 2>${DEVNUL} ),\
$(myTestCommand) found,\
$(error "No $(myTestCommand) in PATH)))
$(info ${K})

No rule to make target 'openmp'?

The following is defined in the makefile:
CXX = g++
CXXFLAGS = -std=c++11
I would like to compile my code with OpenMP directives without changing the original makefile (which runs perfectly). The manual suggests a way to do this, is by changing it on the command line. However, when I run,
make CXXFLAGS=-std=c++11 -fopenmp
the error mentioned before pops up. Can someone help me understand what I'm doing wrong?
The problem here is the space between -std=c++11 and -fopenmp. It splits these two arguments up and the -fopenmp is interpreted as the option -f then openmp is interpreted as a makefile that make attempts to build because it can't find it in the current directory.
You need to execute make with quotes (") like
$ make CXXFLAGS="-std=c++11 -fopenmp"
this will pass CXXFLAGS= -std=c++11 -fopenmp to make. We can see this behaviour with the following simple makefile.
CXX = g++
CXXFLAGS = -std=c++11
all:
#echo $(CXXFLAGS)
Running make will produce the output
$ make
-std=c++11
Running make CXXFLAGS="-std=c++11 -fopenmp" will produce
$ make
-std=c++11 -fopenmp
the correct output and what we were hoping to achieve.
As an aside the command, make CXXFLAGS= -std=c++11 -fopenmp, in your question should produce more errors than just
make: openmp: No such file or directory
make: *** No rule to make target 'openmp'. Stop.
because of the space between CXXFLAGS= and -std=c++11. I get
$ make CXXFLAGS= -std=c++11 -fopenmp
make: invalid option -- =
make: invalid option -- c
make: invalid option -- +
make: invalid option -- +
make: invalid option -- 1
make: invalid option -- 1
If for instance you want to compile with -std=c++14 instead of -std=c++11 you would need to execute make with
$ make CXXFLAGS=-std=c++14
note: no space, or equivalently with
$ make CXXFLAGS="-std=c++14"
again without a space.
Space - The Final Frontier. You need quotes as in
make CXXFLAGS="-std=c++11 -fopenmp"
The shell splits command line words at word boundaries delimited by white space. Quoting is used to avoid word-splitting.

How to specify --no-print-directory within the Makefile itself

I'd like to run my makefile without the -w flag turned on by the recursive make calls.
The flag to do that is --no-print-directory on the make command line.
Is it possible to specify that flag within the makefile itself?
I plan to make this flag dependent on a VERBOSE mode, perhaps something like
$(if $(VERBOSE),,MAKEFLAGS += no-print-directory))
Thanks,
Dan
Yes, just appending --no-print-directory to MAKEFLAGS should be enough, but you have to do that with conditional directives, not with conditional functions:
ifndef VERBOSE
MAKEFLAGS += --no-print-directory
endif
You can include the .SILENT: special target in the calling makefile. For example, here's your toplevel makefile:
all:
$(MAKE) -f sub.mk foo
.SILENT:
and the submake makefile, sub.mk:
foo:
#echo done
Note that .SILENT is considered obsolete, so it may not be around forever, and also note that including that in your makefile also has the effect of suppressing command echo, just as if you had put # before every command in the makefile.

Resources