Checking the gcc version in a Makefile? - gcc

I would like to use some gcc warning switchs that aren't available in older gcc versions (eg. -Wtype-limits).
Is there an easy way to check the gcc version and only add those extra options if a recent gcc is used ?

I wouldn't say its easy, but you can use the shell function of GNU make to execute a shell command like gcc --version and then use the ifeq conditional expression to check the version number and set your CFLAGS variable appropriately.
Here's a quick example makefile:
CC = gcc
GCCVERSION = $(shell gcc --version | grep ^gcc | sed 's/^.* //g')
CFLAGS = -g
ifeq "$(GCCVERSION)" "4.4.3"
CFLAGS += -Wtype-limits
endif
all:
$(CC) $(CFLAGS) prog.c -o prog
Edit: There is no ifgt. However, you can use the shell expr command to do a greater than comparison. Here's an example
CC = gcc
GCCVERSIONGTEQ4 := $(shell expr `gcc -dumpversion | cut -f1 -d.` \>= 4)
CFLAGS = -g
ifeq "$(GCCVERSIONGTEQ4)" "1"
CFLAGS += -Wtype-limits
endif
all:
$(CC) $(CFLAGS) prog.c -o prog

To transform full 3-part gcc version (not only first digit) into numerical format, suitable for comparison (e.g. 40701) use
gcc -dumpfullversion -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$/&00/'
Which addresses the possibility of double-digit numbers in any of the version part, and possibility of missing 3-rd part of the version in output of gcc -dumpversion (which is the case in some earlier gcc versions).
So to test the version in makefile, use something like (note $$ inside last sed command)
GCC_GTEQ_472 := $(shell expr `gcc -dumpfullversion -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$/&00/'` \>= 40702)
ifeq "$(GCC_GTEQ_472)" "1"
...
endif

I just encountered this problem where I needed to test the first two digits of gcc and wanted a more readable option than the clever sed hackery above. I used bc to do the comparison since it supports floating point (expr treats non-integers as strings):
GCC_VER_GTE44 := $(shell echo `gcc -dumpversion | cut -f1-2 -d.` \>= 4.4 | bc )
ifeq ($(GCC_VER_GTE44),1)
...
endif
If they release gcc 4.10 after gcc 4.9, then a bit of sed hacking is necessary, but this is still pretty readable:
GCC_VER_GTE44 := $(shell echo `gcc -dumpversion | cut -f1-2 -d.` \>= 4.4 | sed -e 's/\./*100+/g' | bc )
ifeq ($(GCC_VER_GTE44),1)
...
endif

I found this and thought it was really clever. It implements >, >=, <, and <= with fewer shell calls:
GCC_VERSION := $(shell gcc -dumpversion)
VERSION := 7.4.0
ifeq ($(VERSION),$(firstword $(sort $(GCC_VERSION) $(VERSION))))
# stuff that requires GCC_VERSION >= VERSION
endif
This example shows >=. You can implement >, <=, or < using combinations of ifneq and $(lastword).
References:
https://lists.gnu.org/archive/html/help-make/2006-04/msg00065.html

Are you using something like autoconf?
It might be worth invoking a 'dummy' compile via gcc with the flag enabled and if that one fails because the compiler doesn't recognise the flag, you can fall back to the command line that doesn't use the newer warning options.

I've made a ready-to-use IF_GCC macro, based on the answers above:
MY_GCC_VERSION=$(if $(GCC_VERSION),$(GCC_VERSION),$(GCC_DEFAULT_VER))
MY_GCC_TOINT=$(shell echo $(1) | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$//')
MY_IF_GCC=$(if $(shell test $(call MY_GCC_TOINT, $(MY_GCC_VERSION)) -$(1) $(2) || echo 0),$(4),$(3))
GCC_DEFAULT_VER:=$(firstword $(shell cc -V 2>&1 | grep default | sed -r 's/( *)([0-9.]+),(.*)/\2/g'))
Usage: $(call MY_IF_GCC,ge,30305,-fan_option_for_gcc_ge_3.3.5)
As the second argument, you can use any operator of those supported by test(1): eq, gt, lt, ge, le etc.
If cc -V doesn't work for you, replace it with gcc -dumpversion or whatever suitable
Hope that helps.

Following Chris, but using awk
GCC_VER_GTE44 := $(shell expr $$(gcc -dumpversion | awk -F. '{print $$3+100*($$2+100*$$1)}') \>= 40400)
note $ needs to be escaped in Makefile with another $.

I think awk is a better tool for this purpose, as it can both split the the version string into $1 $2 and $3 and then do the comparison with >, <, >= etc. in one command. The whole line is omitted from awk output if the expression is false.
GCC_VERSION_GT_75 = $(shell gcc -dumpfullversion -dumpversion | awk -F. '$$1 > 7 && $$2 > 5')
ifneq ($(GCC_VERSION_GT_75),)
# stuff that requires gcc version > 7.5
endif

Related

Command working in bash but not in Makefile [duplicate]

I'm trying to do this in a makefile and it fails horribly:
M_ARCH := $(shell g++ -dumpmachine | awk '{split($1,a,"-");print a[1]}')
do you know why? I guess it has to do with escaping, but what and where?
It's the dollar sign, in makefiles you'll have to type $$ to get a single dollar sign:
M_ARCH := $(shell g++ -dumpmachine | awk '{split($$1,a,"-");print a[1]}')
Make is quite lispy when you get down to it. Here's a non-awk version that does the same thing:
space := $() #
M_ARCH := $(firstword $(subst -,$(space),$(shell g++ -dumpmachine)))
all:
$(info $(M_ARCH))

How to check the version of Binutils on GNU GAS assembly code at compile time?

I have some GAS assembly code, and I'm compiling it directly through GCC to use preprocessor features like #include:
gcc main.S
I came across a reported Binutils bug for a given Binutils version, and I would like to have two versions of the code, decided at compile time:
a workaround version for the buggy Binutils version
the main code version otherwise
For the version of GCC itself, I can use __GNUC__ and related macros as explained at: How do I test at compile time the current version of GCC?
Is there something like that for the version of Binutils?
I could modify my build system to check the as --version myself and pass a gcc -D define, but I wonder if that can be avoided.
There is a special symbol called .gasversion. (with a leading and trailing dot). You can use it this way:
.data
.if .gasversion. >= 22900
.ascii "binutils 2.29 or newer"
.endif
.if .gasversion. >= 22800
.ascii "binutils 2.28 or newer"
.endif
Note that this is not a preprocessor feature (as GCC does not know the GAS/BFD version and does not pass it to the preprocessor). So you have to use GAS constructs like .if and .macro to implement what you need.
Often, an alternative approach is used where the actual presence of the bug is tested in some configure script and the workaround is activated only if necessary. This means that the workaround is only used when it is absolutely required—version numbers do not reflect distribution backports which could have fixed the bug. Obviously, this only makes sense if the workaround is costly (because it introduces additional run-time overhead).
How to check the version of Binutils on GNU GAS assembly code at compile time?
Crypto++ had a a similar problem. They needed to know AS and LD versions to ensure instructions part of ISAs like SSE4 (-msse4.1), AES (-maes) and SHA (-msha) were available during a build (using Intel as an example).
In a GNUmakefile Crypto++ used to perform:
GCC_COMPILER := $(shell $(CXX) --version 2>/dev/null | $(GREP) -v -E '(llvm|clang)' | $(GREP) -i -c -E '(gcc|g\+\+)')
...
ifneq ($(GCC_COMPILER),0)
IS_GCC_29 := $(shell $(CXX) -v 2>&1 | $(GREP) -i -c -E gcc-9[0-9][0-9])
GCC42_OR_LATER := $(shell $(CXX) -v 2>&1 | $(GREP) -i -c -E "gcc version (4\.[2-9]|[5-9]\.)")
GCC46_OR_LATER := $(shell $(CXX) -v 2>&1 | $(GREP) -i -c -E "gcc version (4\.[6-9]|[5-9]\.)")
endif
ifneq ($(HAVE_GAS),0)
GAS210_OR_LATER := $(shell $(CXX) -xc -c /dev/null -Wa,-v -o/dev/null 2>&1 | $(GREP) -c -E "GNU assembler version (2\.[1-9][0-9]|[3-9])")
GAS217_OR_LATER := $(shell $(CXX) -xc -c /dev/null -Wa,-v -o/dev/null 2>&1 | $(GREP) -c -E "GNU assembler version (2\.1[7-9]|2\.[2-9]|[3-9])")
GAS218_OR_LATER := $(shell $(CXX) -xc -c /dev/null -Wa,-v -o/dev/null 2>&1 | $(GREP) -c -E "GNU assembler version (2\.1[8-9]|2\.[2-9]|[3-9])")
GAS219_OR_LATER := $(shell $(CXX) -xc -c /dev/null -Wa,-v -o/dev/null 2>&1 | $(GREP) -c -E "GNU assembler version (2\.19|2\.[2-9]|[3-9])")
GAS224_OR_LATER := $(shell $(CXX) -xc -c /dev/null -Wa,-v -o/dev/null 2>&1 | $(GREP) -c -E "GNU assembler version (2\.2[4-9]|2\.[3-9]|[3-9])")
endif
And later Crypto++ would do stuff like:
ifeq ($(HAVE_GAS)$(GAS224_OR_LATER),10)
CXXFLAGS += -DCRYPTOPP_DISABLE_SHA
endif
The 10 string is basically equivalent to the following. It is the GNU Makefile way of doing boolean expressions:
if HAVE_GAS==true && GAS224_OR_LATER==false
CXXFLAGS += -DCRYPTOPP_DISABLE_SHA
fi
By the way, the GAS version checking broke with Clang and the Integrated Assembler. Clang does not respond to -Wa,-v like AS does. LLVM Bug 24200 was filed because of it: Fail to fetch version string of assembler when using integrated assembler.
What Crypto++ found was, this did not scale well. It was OK 10 or 20 years ago (literally, when it was initially used). However it broke down when (1) new platforms use ancient toolchains, like modern BSD pinning to GPL2 toolchains (2) new compilers were installed on old platforms, like Clang 7.0 on a Power6 machine, and (3) Clang and its integrated assembler, which did not need AS to assemble higher ISAs.
ARM platforms was also very troublesome because the project could not reliably determine when to include <arm_neon.h> and <arm_acle.h> based on platforms and compiler versions. Sometimes the headers were available for a platform and compiler, sometimes they were not (even on the same platform with different versions of the same compiler). Preprocessor macros like __ARM_ACLE__ were completely missing (see ARM C Language Extensions (ACLE)). Android and iOS just does what the hell it wants breaking from what happens on armhf and friends or what is stated in the docs. And Microsoft found a new way to break it with their header <arm64_neon.h>.
Now Crypto++ performs a test compile through the GNU Makefile to see if a program can be compiled, assembled and linked. However, it is not braindead like Autotools or Cmake. Crypto++ looks for any diagnostic and fails the test for any diagnostic. This caught cases Autotools and Cmake were missing, like SunCC emitting like "illegal option: -xarch=sha". Autotools and Cmake would report success and later the build would fail. (Apparently Autotools and Cmake only check compiler return codes, and not diagnostic messages like "illegal option").
The Crypto++ tests now look like:
SUN_COMPILER := $(shell $(CXX) -V 2>&1 | $(GREP) -i -c -E 'CC: (Sun|Studio)')
...
ifeq ($(SUN_COMPILER),1)
SSE2_FLAG = -xarch=sse2
else
SSE2_FLAG = -msse2
endif
...
TPROG = TestPrograms/test_x86_sse2.cxx
TOPT = $(SSE2_FLAG)
HAVE_OPT = $(shell $(CXX) $(TCXXFLAGS) $(ZOPT) $(TOPT) $(TPROG) -o $(TOUT) 2>&1 | tr ' ' '\n' | wc -l)
ifeq ($(strip $(HAVE_OPT)),0)
CHACHA_FLAG = $(SSE2_FLAG)
SUN_LDFLAGS += $(SSE2_FLAG)
else
SSE2_FLAG =
endif
And test_x86_sse2.cxx, which is compiled at -O0 so the optimzer does not remove the code:
$ cat TestPrograms/test_x86_sse2.cxx
#include <emmintrin.h>
int main(int argc, char* argv[])
{
__m128i x = _mm_setzero_si128();
x=_mm_add_epi64(x,x);
return 0;
}

Boolean circuit using basic arithmetic?

According to How do I perform arithmetic in a makefile?, we can perform basic arithmetic through the Posix shell in a GNUmakefile (see Dominic's answer).
I got really excited because I have suffered lack of logical operators in the past. So I coded up the following test on OS X:
GCC42_OR_LATER = $(shell $(CXX) -v 2>&1 | $(EGREP) -c "^gcc version (4.[2-9]|[5-9])")
IS_DARWIN = $(shell uname -s | $(EGREP) -i -c "darwin")
CLANG_COMPILER = $(shell $(CXX) --version 2>&1 | $(EGREP) -i -c "clang")
# Below, we are building a boolean circuit that says "Darwin && (GCC 4.2 or above || Clang)"
SUPPORTS_MULTIARCH = $$($(IS_DARWIN) * $$($(GCC42_OR_LATER) + $(CLANG_COMPILER)))
ifneq ($(SUPPORTS_MULTIARCH),0)
CXXFLAGS += -arch x86_64 -arch i386
else
CXXFLAGS += -march=native
endif
A run on OS X showed -arch x86_64 -arch i386, and that was expected.
I then added a IS_DARWIN=0 before the math, which should have driven everything low. But it did not, and I got -arch x86_64 -arch i386 again.
What am I doing wrong in the above code?
As the comment says, to have $$(()) arithmetic work, you need to involve the shell and that shell needs to be bash. (EDIT: Apparently it doesn't, and any POSIX shell will work, even something as minimal as dash)
So first off, add this to the top of your `Makefile`:
SHELL=/bin/bash
Then, the line you were playing with should read:
# Below, we are building a boolean circuit that says "Darwin && (GCC 4.2 or above || Clang)"
SUPPORTS_MULTIARCH = $(shell echo $$(( $(IS_DARWIN) * ( $(GCC42_OR_LATER) + $(CLANG_COMPILER) ) )) )
I've added extra spaces in that line to show clearly what's matching with what. Feel free to delete them, though they do no harm.
Inside the double-paren-arithmetic group, you don't need $$(( to start an arithmetic subgroup; just use ordinary parentheses.

in Makefiles, how to test for gcc's --with-ld option?

We're trying to develop an as-portable-as-possible Makefile...
Neither uname nor uname -v is definitive in one suite of cases...
which ld is also unhelpful, as both linkers are present...
I imagine we could just parse output of gcc -v for '--with-ld=/usr/bin/ld', then test the features/version of that linker. But is the best way to do this?
What are 'Best Practices' here? Can gcc be queried more cleanly - from within a Makefile - for its linker options?
The first thing that comes to my mind (tested with GNU make and clearmake):
# redirect gcc -v to stdout && count the number of occurrences
GCC_WITH_LD := $(shell gcc -v 2>&1 | grep -c "\--with-ld")
ifeq ($(GCC_WITH_LD),0)
$(shell echo --with-ld NOT FOUND 1>&2) # print to stderr
# exit using error directive?
else
$(shell echo --with-ld FOUND 1>&2) # print to stderr
endif
mytarget:
#echo myjobs
The option you're looking for is -print-prog-name:
andy#Andrews-Mac-Pro ~ % gcc -print-prog-name=ld
/Library/Developer/CommandLineTools/usr/bin/ld
Then in the Makefile you can set LD with:
LD := $(shell gcc -print-prog-name=ld)
This also works on Solaris.

Escaping in makefile

I'm trying to do this in a makefile and it fails horribly:
M_ARCH := $(shell g++ -dumpmachine | awk '{split($1,a,"-");print a[1]}')
do you know why? I guess it has to do with escaping, but what and where?
It's the dollar sign, in makefiles you'll have to type $$ to get a single dollar sign:
M_ARCH := $(shell g++ -dumpmachine | awk '{split($$1,a,"-");print a[1]}')
Make is quite lispy when you get down to it. Here's a non-awk version that does the same thing:
space := $() #
M_ARCH := $(firstword $(subst -,$(space),$(shell g++ -dumpmachine)))
all:
$(info $(M_ARCH))

Resources