How do I perform arithmetic in a makefile? - makefile

Is it possible to perform some operations on variables in a makefile?
For instance, defining
JPI=4
JPJ=2
is it possible to define in the same makefile a variable JPIJ equal to the expanded value of $(JPI)*$(JPJ)?

Using Bash arithmetic expansion:
SHELL=/bin/bash
JPI=4
JPJ=2
all:
echo $$(( $(JPI) * $(JPJ) ))
The first line is to choose the Bash shell instead of the default (sh). Typically, sh doesn't support arithmetic expansion. However in Ubuntu, /bin/sh is provided by Dash, which supports this feature. So that line could be skipped.
The double dollar sign is because we want the expansion to be done by the shell. Note: the JPI and JPJ variables are expanded by make first, then the expression is passed to bash like this:
$(( 4 * 2 ))

Answer from #mrkj is great but as #Daniel mentions, not all systems have bc (for example, I don't have it on MSys).
I have found the two following methods, both using shell: $$(( ... )) and expr ...
JPI=4
JPJ=2
#With Double-dollar
JPIJ_1 = $(shell echo $$(( $(JPI) + $(JPJ) )))
#With 'expr'
JPIJ_2 = $(shell expr $(JPI) + $(JPJ) )
$(info Sum with Double-$$: $(JPIJ_1))
$(info Sum with 'expr': $(JPIJ_2))
Note that when using expr, you shall put spaces around the + or it will return 4+2. This is not required when using $$.
.
When you have bc available, you might definitely go with it. I found the following page very interesting: http://www.humbug.in/2010/makefile-tricks-arithmetic-addition-subtraction-multiplication-division-modulo-comparison/

If you're using GNU make and have bc installed on your system, you can use something like this:
JPI=4
JPJ=2
FOO=$(shell echo $(JPI)\*$(JPJ) | bc)
all:
echo $(FOO)

It's clumsy (or brilliant, depending on your perspective), but you can do arithmetic directly in GNU make. See Learning GNU Make Functions with Arithmetic. Be aware though that this method doesn't scale well. It will work wonderfully for small numbers as you have shown in your question, but it doesn't do well when you're working with numbers with a large magnitude (greater than 10,000,000).

In GNU Make with Guile support (i.e. since version 4.0) it is easy to use call to Scheme language for arithmetic or other calculations. It is done without creating any subshell or child process.
An example
JP-I := 4
JP-J := 2
JP-IJ := $(guile (* $(JP-I) $(JP-J) ))
$(info JP-IJ = $(JP-IJ) )
# prints: JP-IJ = 8
See also the manual for Guile Arithmetic Functions.
A possible check for Guile:
ifeq (,$(filter guile,$(.FEATURES)))
$(error Your Make version $(MAKE_VERSION) is not built with support for Guile)
endif

The GNU Make Standard Library provides integer arithmetic functions.
include gmsl
JPI = 4
JPJ = 2
JPIJ = $(call plus,$(JPI),$(JPJ))

With makepp it's much easier. You get direct access to the underlying Perl interpreter. In this case the makeperl function does variable expansion before evaluating as Perl, the perl function OTOH would only evaluate:
JPI=4
JPJ=2
JPIJ = $(makeperl $(JPI)*$(JPJ))
&echo result: $(JPIJ)
You can use the builtin &echo command outside of a rule as a statement.

To add a late answer to the pool: The GNUmake table toolkit features many arithmetic functions. You can add, subtract, multiply, divide, take the modulus in base 8,10 and 16. Also there are the usual binary operations and, or, xor and not. Numbers can be around 60 digits but you can adapt this, if you need more. The code is pure GNUmake syntax and therefore portable between Windows and Unix, contrary to shell scripts - in case you want to number crunch, there may be better solutions ;) of course.
Here is an example:
include gmtt/gmtt.mk
NUMBER_A := -12392834798732429827442389
NUMBER_B := 984398723982791273498234
$(info $(call add,$(NUMBER_A),$(NUMBER_B)))
$(info $(call sub,$(NUMBER_A),$(NUMBER_B)))
$(info $(call mul,$(NUMBER_A),$(NUMBER_B)))
$(info $(call div,$(NUMBER_A),$(NUMBER_B)))
$(info $(call mod,$(NUMBER_A),$(NUMBER_B)))
Output:
$ make
-11408436074749638553944155
-13377233522715221100940623
-12199490762401735834920873237276176262117128241026
-12
-580050110938934545463581

After an hour of hair pulling, I ended up on this (assuming you got hexadecimal and decimal numbers mixed in like my use case):
JPI = 0x123
JPJ = 2
MUL = $(shell python3 -c "print($(JPI)*$(JPJ))")

Related

Escaping # in gmake $(shell) function

I have an environment variable GITHUB_REFS that I want to perform some bashism on and capture the result in another variable GITHUB_BRANCH from a GNU makefile. My naive approach looks like this:
SHELL:=/bin/bash
GITHUB_BRANCH:=$(shell echo "${GITHUB_REF#refs/heads/}")
If I run the bashism by itself, it works fine however when running the makefile above it fails with:
Makefile:2: *** unterminated call to function 'shell': missing ')'. Stop.
I tried escaping the # as \#, since it is a plausible culprit, and indeed then the Makefile works however the bashism does not. Double escaping it gives the same error again.
So how can I pull this off?
You also need to double the dollar sign to pass it through to the shell.
GITHUB_BRANCH:=$(shell echo "$${GITHUB_REF\#refs/heads/}")
For what it's worth, this simple parameter expansion is portable to any reasonably modern sh, so not at all an exclusive Bash feature.
Of course, make is perfectly capable of performing the same substitution, without invoking an external process.
GITHUB_BRANCH := $(patsubst refs/heads/%,%,${GITHUB_REF})
The # can be escaped using \, but you also forgot to escape the $.
This Makefile works:
SHELL:=/bin/bash
foo := $(shell echo "$${SHELL\#/bin/}")
all:
echo $(foo)
Just to note that this (the need to escape the #) is a bug, and will be fixed in an upcoming version of GNU make. If you want to allow your makefile to be portable before/after the bug is fixed, you should hide it in a variable like this:
HASH := \#
foo := $(shell echo "${GITHUB_REF$(HASH)refs/heads/}")
This will work in all versions of GNU make.

How to conditionally set Makefile variable to something if it is empty?

I want to set a variable if it is empty. I tried in this way:
....
TEST := $(something)
...
TEST ?= $(something else)
The first $(something) may return an empty string, however the conditional assignment ?= works only if the previous variable is not set, not if empty.
Any elegant solution to set the variable if empty?
EDIT
I found this solution:
....
TEST := $(something)
...
TEST += $(something else)
TEST := $(word 1, $(TEST))
but I think that there will be one more elegant.
Any elegant solution to set the variable if empty?
GNU make is hardly known for elegant solutions. Unless you find trapdoors and minefields to be elegant. I know only of the two ways to accomplish what you want:
The standard ifeq/endif solution:
ifeq ($(TEST),)
TEST := $(something else)
endif
Use the $(if) function:
TEST := $(if $(TEST),$(TEST),$(something else))
One can try to package that construct into a function too, but that is inadvisable. The function would have the hidden pitfall of occasionally breaking the $(something else) if it contains the , (for which there are only wayward workarounds). (The built-in functions like $(if) are immune to the , bug.)
Elegance test is up to you.
Here's another alternative that I personally find quite elegant, because it's a one-liner and doesn't need the redundant else-branch:
TEST := $(or $(TEST),$(something else))
From GNU make, chapter 7.2, Syntax of Conditionals:
"Often you want to test if a variable has a non-empty value. When the value results from complex expansions of variables and functions, expansions you would consider empty may actually contain whitespace characters and thus are not seen as empty. However, you can use the strip function to avoid interpreting whitespace as a non-empty value. For example:
ifeq ($(strip $(foo)),)
text-if-empty
endif
will evaluate text-if-empty even if the expansion of $(foo) contains whitespace characters."
Folks, I think there's a simpler solution
KDIR ?= "foo"
From: What is ?= in Makefile
Just in case anyone stumbled upon putting the condition in the rule itself. below how I did it, thought it might help others.
In Makefile, suppose we have the following rule with check target and we need to check whether var was passed.
check:
#[ "${var}" ] && echo "all good" || ( echo "var is not set"; exit 1 )
To test this out, run the following commands
$ make check
var is not set
make: *** [check] Error 1
$ make check var=test
all good
So, Now we can pass the variable value or a default value in case it was not passed to a bash script that will be responsible to do the logic. something like the following:
#[ "${var}" ] && ./b.sh ${var} || ./b.sh 'ss'
Here's below what b.sh might look like, though you can add more logic to it.
#!/bin/sh
echo $1
In case you need to distinguish if a variable is undefined or just has an empty value, use $(origin VARNAME) function:
ifeq ($(origin VARNAME),undefined)
VARNAME := "now it's finally defined"
endif
Note that VARNAME is not surrounded by $() - you literally give the name of the variable.
Setting value to variable in Makefile if value defined
ifdef RELEASE_BRANCH
GIT_TAG=$(shell cat projects/${RELEASE_BRANCH}/GIT_TAG)
else
GIT_TAG=$(shell cat release/DEFAULT_GIT_TAG)
endif

make rule that invokes another rule several times with different values for a variable

I have a rule something, that works on the variable VAR. I also have another rule something-all, that needs to run something, with VAR set to each value in vars.
vars = hello world
something:
echo $(VAR)
something-all:
$(foreach VAR,$(vars),something)
This doesn't quite work, I get
noob#work:~/Desktop$ make something-all
something something
make: something: No such file or directory
make: *** [something-all] Error 1
It should probably print hello\nworld.
I used to do this with wildcard rules by retrieving VAR from %, but got the feeling that was the wrong way to do it. This looked like this:
vars = hello world
all: $(foreach VAR,$(vars),something-$(VAR))
something-%:
echo $*
The below should fix your problem
Using foreach (Tried on GNU Make 3.80 on sparc-solaris 2.8 and windows)
vars = hello world
something:
echo $(VAR)
something-all:
$(foreach i, $(vars), $(MAKE) something VAR=$i || exit 1;)
Using shell for-loop (Tried on GNU Make 3.80 and cc make on sparc-solaris 2.8)
vars = hello world
something:
echo $(VAR)
something-all:
for i in $(vars); do $(MAKE) something VAR=$$i || exit 1; done
TL;DR: If you want to program make, drop GNU Make in favor of BSD Make.
This is a personal recommendation. While BSD Make seems more limited than GNU Make, as it offers less programming facilities, it is much easier to program and has a few unique killer features. This is why I propose a solution with GNU Make and another solution for BSD Make:
Doing it in GNU Make
Using GNU Make, you can write a macro to define a target. The canonical way to define a sequence in a Makefile is to add the steps of the sequence as dependencies to a target, as reflected by the snippet below:
vars= hello world
define something_t =
something: something-$(1)
something-$(1):
#echo $(1)
endef
$(foreach _,$(vars),$(eval $(call something_t,$_)))
It is recommended to use this organisation (rather than defining just one target), because you can work on it to make the task easily resumable if you interrupt the sequence. A Makefile describes a job whose advancement is entirely described by the state of the file system. A task is then easily resumable, if each step is associated to a file, usually a compilation object but sometimes also an empty file which is touch'ed to indicate that important checkpoints have been passed.
Using an auxiliary macro is a flexible solution that can be adapted to more complicated tasks than just echoing a name. Note that this does work with newest versions of GNU Make (4.1). On GNU Make 3.81, you should remove the equal sign from the macro definition.
Adapting your example for BSD Make
If this is an option for you, I recommand dropping the use of GNU Make and replace it by BSD Make, which is way easier to program: it has a short and to the point documentation, while the documentation of GNU Make is very verbose and somewhat unclear, BSD Make has industrial-strength examples of complex rulesets (FreeBSD Build system or BSD Owl), and it has a simple and predictable macro language.
vars= hello world
something:
.for _var in ${vars}
echo ${_var}
.endfor
This can evolve to support more complicated tasks, just by replacing the echo by the adapted commands, or using intermediary steps.
Allow the user to override some tasks, also in BSD Make
In this slightly more advanced variation, we allow the user to override our own recipes for building targets something-hello and something-world.
For each item in our list, a target something-* is created it if it does not already exist, and added to the dependencies of something. The whole operation of defining these targets only happens if something has been left undefined. Therefore, users of these macros can:
Override the recipes for something-hello and something-world
Override the full procedure bound to something.
Implementing such customisation possibilities is mandatory if we want to write useful, reusable, macros for Make. Unluckily, customisation of this sort is nearly impossible in GNU Make.
vars = hello world
.if!target(depend)
.for _var in ${vars}
.if!target(something-${_var})
something-${_var}:
echo ${_var}
.endif
something: something-${_var}
.endfor
.endif
Here's one way to do it:
VARS := hello world
THINGS := $(addprefix something-, $(VARS))
allthings: $(THINGS)
something-%:
echo $*
It should be no surprise that
vars := hello world
something-all:
$(foreach VAR,$(vars),something)
tries to run something something. That's exactly what the foreach expands to, since you don't reference VAR in the third expression.
All you need to do is reference VAR and use a command such as echo:
vars := hello world
something-all:
$(foreach VAR,$(vars),echo $(VAR);)
$ make
echo hello; echo world;
hello
world
Note how chaining the commands with a semicolon avoids forking several shells or -- GASP! -- recursive make invocations. It doesn't get more performant than that.
Alternatively, if your command accepts several somethings as arguments,
vars := hello world
something-all:
echo $(foreach VAR,$(vars),$(VAR))
$ make
echo hello world
hello world
But that is equivalent to the super simple echo $(vars). So it might pay off to think outside the box trying to change your requirements to make this simple solution work.

Arithmetic operation on String in makefile without Shell utility

I want to perform Arithmetic operation on string variable, I don't have shell utility in makefile system platform for i386-pc-mingw32 (windows).
Can anybody help me how to perform Arithmetic operation( substraction , comparsion ) on string variable by any means ??
To add a late answer: The GNUmake table toolkit features (in spite of its name) many arithmetic functions. You can add, subtract, multiply, divide, take the modulus in base 8,10 and 16. Also there are the usual binary operations and, or, xor and not. Numbers can be around 60 digits but you can adapt this, if you need more. The code is pure GNUmake syntax and therefore portable between Windows and Unix, contrary to shell scripts - in case you want to number crunch, there may be better solutions ;) of course.
Here is an example:
include gmtt/gmtt.mk
NUMBER_A := -12392834798732429827442389
NUMBER_B := 984398723982791273498234
$(info $(call add,$(NUMBER_A),$(NUMBER_B)))
$(info $(call sub,$(NUMBER_A),$(NUMBER_B)))
$(info $(call mul,$(NUMBER_A),$(NUMBER_B)))
$(info $(call div,$(NUMBER_A),$(NUMBER_B)))
$(info $(call mod,$(NUMBER_A),$(NUMBER_B)))
Output:
$ make
-11408436074749638553944155
-13377233522715221100940623
-12199490762401735834920873237276176262117128241026
-12
-580050110938934545463581

Computing Makefile variable on assignment

In a Makefile, I'm trying to assign the result of a shell command to a variable:
TMP=`mktemp -d /tmp/.XXXXX`
all:
echo $(TMP)
echo $(TMP)
but
$ make Makefile all
is echoing 2 different values, eg:
/tmp/.gLpm1T
/tmp/.aR4cDi
What is the syntax for mktemp to be computed on variable assignment?
Thank you.
It depends on the flavour of make. With GNU Make, you can use := instead of = as in
TMP:=$(shell mktemp -d /tmp/.XXXXX)
Edit As pointed out by Novelocrat, the = assignment differs from := assignment in that values assigned using = will be evaluated during substitution (and thus, each time, the variable is used), whereas := assigned variables will have their values evaluated only once (during assignment), and hence, the values are fixed after that. See the documentation of GNU Make for a more detailed explanation.
In order for the value to be truly constant after assignment, though, it should not contain any parts, which might be special to the shell (which make calls in order to actually run the update rules, etc.) In particular, backticks are best avoided. Instead, use GNU make's built-in shell function and similar to achieve your goals.
If you’re using GNU Make, instead of using backticks, use $(shell ...). For example,
TMP=$(shell mktemp -d /tmp/.XXXXX)

Resources