In my build script, I want to conditionally add -fstack-protector-strong to my CFLAGS if the gcc supports it (my project builds using multiple versions of gcc).
I want my build script (written in bash) to parse the output of gcc --version and automatically add this flag if the gcc supports it.
Which version of gcc is the earliest to include the -fstack-protector-strong flag?
I want my build script (written in bash) to parse the output of gcc --version and automatically add this flag if the gcc supports it.
Instead of doing that, just call gcc -fstack-protector-strong with a valid source file and see if it compiles. Such method works universally for any compile option, which will save you time for searching which version has which options. You may take inspiration from CMake check_c_compile_flag function.
Taking inspiration from #KamilCuk's answer but bashifying it:
add_supported_cc_flag() {
CC=$1
CFLAG_TO_CHECK=$2
C_FILE=/tmp/test.$$.c
O_FILE=${C_FILE/.c/.o}
rm -f ${C_FILE}
cat > ${C_FILE} <<EOF
int main() { return 0; }
EOF
if $CC ${CFLAG_TO_CHECK} -c ${C_FILE} -o ${O_FILE} >/dev/null 2>&1
then
echo ${CFLAG_TO_CHECK}
fi
rm -f ${C_FILE} ${O_FILE}
}
CFLAGS="${CFLAGS} $(add_supported_cc_flag gcc -fstack-protector-strong)"
It appears that -fstack-protector-strong was first available in gcc 4.9.0.
The option doesn't show up in https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Optimize-Options.html (4.8.5 was the last 4.8.x gcc version), but the option does show up in https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gcc/Optimize-Options.html.
Note that -fstack-protector-all and -fstack-protector are available prior to gcc 4.9.0.
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;
}
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
I routinely work on several different computers and several different operating systems, which are Mac OS X, Linux, or Solaris. For the project I'm working on, I pull my code from a remote git repository.
I like to be able to work on my projects regardless of which terminal I'm at. So far, I've found ways to get around the OS changes by changing the makefile every time I switch computers. However, this is tedious and causes a bunch of headaches.
How can I modify my makefile so that it detects which OS I'm using and modifies syntax accordingly?
Here is the makefile:
cc = gcc -g
CC = g++ -g
yacc=$(YACC)
lex=$(FLEX)
all: assembler
assembler: y.tab.o lex.yy.o
$(CC) -o assembler y.tab.o lex.yy.o -ll -l y
assembler.o: assembler.c
$(cc) -o assembler.o assembler.c
y.tab.o: assem.y
$(yacc) -d assem.y
$(CC) -c y.tab.c
lex.yy.o: assem.l
$(lex) assem.l
$(cc) -c lex.yy.c
clean:
rm -f lex.yy.c y.tab.c y.tab.h assembler *.o *.tmp *.debug *.acts
There are many good answers here already, but I wanted to share a more complete example that both:
doesn't assume uname exists on Windows
also detects the processor
The CCFLAGS defined here aren't necessarily recommended or ideal; they're just what the project to which I was adding OS/CPU auto-detection happened to be using.
ifeq ($(OS),Windows_NT)
CCFLAGS += -D WIN32
ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
CCFLAGS += -D AMD64
else
ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
CCFLAGS += -D AMD64
endif
ifeq ($(PROCESSOR_ARCHITECTURE),x86)
CCFLAGS += -D IA32
endif
endif
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
CCFLAGS += -D LINUX
endif
ifeq ($(UNAME_S),Darwin)
CCFLAGS += -D OSX
endif
UNAME_P := $(shell uname -p)
ifeq ($(UNAME_P),x86_64)
CCFLAGS += -D AMD64
endif
ifneq ($(filter %86,$(UNAME_P)),)
CCFLAGS += -D IA32
endif
ifneq ($(filter arm%,$(UNAME_P)),)
CCFLAGS += -D ARM
endif
endif
The uname command (http://developer.apple.com/documentation/Darwin/Reference/ManPages/man1/uname.1.html) with no parameters should tell you the operating system name. I'd use that, then make conditionals based on the return value.
Example
UNAME := $(shell uname)
ifeq ($(UNAME), Linux)
# do something Linux-y
endif
ifeq ($(UNAME), Solaris)
# do something Solaris-y
endif
Detect the operating system using two simple tricks:
First the environment variable OS
Then the uname command
ifeq ($(OS),Windows_NT) # is Windows_NT on XP, 2000, 7, Vista, 10...
detected_OS := Windows
else
detected_OS := $(shell uname) # same as "uname -s"
endif
Or a more safe way, if not on Windows and uname unavailable:
ifeq ($(OS),Windows_NT)
detected_OS := Windows
else
detected_OS := $(shell sh -c 'uname 2>/dev/null || echo Unknown')
endif
Ken Jackson proposes an interesting alternative if you want to distinguish Cygwin/MinGW/MSYS/Windows. See his answer that looks like that:
ifeq '$(findstring ;,$(PATH))' ';'
detected_OS := Windows
else
detected_OS := $(shell uname 2>/dev/null || echo Unknown)
detected_OS := $(patsubst CYGWIN%,Cygwin,$(detected_OS))
detected_OS := $(patsubst MSYS%,MSYS,$(detected_OS))
detected_OS := $(patsubst MINGW%,MSYS,$(detected_OS))
endif
Then you can select the relevant stuff depending on detected_OS:
ifeq ($(detected_OS),Windows)
CFLAGS += -D WIN32
endif
ifeq ($(detected_OS),Darwin) # Mac OS X
CFLAGS += -D OSX
endif
ifeq ($(detected_OS),Linux)
CFLAGS += -D LINUX
endif
ifeq ($(detected_OS),GNU) # Debian GNU Hurd
CFLAGS += -D GNU_HURD
endif
ifeq ($(detected_OS),GNU/kFreeBSD) # Debian kFreeBSD
CFLAGS += -D GNU_kFreeBSD
endif
ifeq ($(detected_OS),FreeBSD)
CFLAGS += -D FreeBSD
endif
ifeq ($(detected_OS),NetBSD)
CFLAGS += -D NetBSD
endif
ifeq ($(detected_OS),DragonFly)
CFLAGS += -D DragonFly
endif
ifeq ($(detected_OS),Haiku)
CFLAGS += -D Haiku
endif
Notes:
Command uname is same as uname -s because option -s (--kernel-name) is the default. See why uname -s is better than uname -o.
The use of OS (instead of uname) simplifies the identification algorithm. You can still use solely uname, but you have to deal with if/else blocks to check all MinGW, Cygwin, etc. variations.
The environment variable OS is always set to "Windows_NT" on different Windows versions (see %OS% environment variable on Wikipedia).
An alternative of OS is the environment variable MSVC (it checks the presence of MS Visual Studio, see example using Visual C++).
Below I provide a complete example using make and gcc to build a shared library: *.so or *.dll depending on the platform. The example is as simplest as possible to be more understandable.
To install make and gcc on Windows see Cygwin or MinGW.
My example is based on five files
├── lib
│ └── Makefile
│ └── hello.h
│ └── hello.c
└── app
└── Makefile
└── main.c
Reminder: Makefile is indented using tabulation. Caution when copy-pasting below sample files.
The two Makefile files
1. lib/Makefile
ifeq ($(OS),Windows_NT)
uname_S := Windows
else
uname_S := $(shell uname -s)
endif
ifeq ($(uname_S), Windows)
target = hello.dll
endif
ifeq ($(uname_S), Linux)
target = libhello.so
endif
#ifeq ($(uname_S), .....) #See https://stackoverflow.com/a/27776822/938111
# target = .....
#endif
%.o: %.c
gcc -c $< -fPIC -o $#
# -c $< => $< is first file after ':' => Compile hello.c
# -fPIC => Position-Independent Code (required for shared lib)
# -o $# => $# is the target => Output file (-o) is hello.o
$(target): hello.o
gcc $^ -shared -o $#
# $^ => $^ expand to all prerequisites (after ':') => hello.o
# -shared => Generate shared library
# -o $# => Output file (-o) is $# (libhello.so or hello.dll)
2. app/Makefile
ifeq ($(OS),Windows_NT)
uname_S := Windows
else
uname_S := $(shell uname -s)
endif
ifeq ($(uname_S), Windows)
target = app.exe
endif
ifeq ($(uname_S), Linux)
target = app
endif
#ifeq ($(uname_S), .....) #See https://stackoverflow.com/a/27776822/938111
# target = .....
#endif
%.o: %.c
gcc -c $< -I ../lib -o $#
# -c $< => compile (-c) $< (first file after :) = main.c
# -I ../lib => search headers (*.h) in directory ../lib
# -o $# => output file (-o) is $# (target) = main.o
$(target): main.o
gcc $^ -L../lib -lhello -o $#
# $^ => $^ (all files after the :) = main.o (here only one file)
# -L../lib => look for libraries in directory ../lib
# -lhello => use shared library hello (libhello.so or hello.dll)
# -o $# => output file (-o) is $# (target) = "app.exe" or "app"
To learn more, read Automatic Variables documentation as pointed out by cfi.
The source code
- lib/hello.h
#ifndef HELLO_H_
#define HELLO_H_
const char* hello();
#endif
- lib/hello.c
#include "hello.h"
const char* hello()
{
return "hello";
}
- app/main.c
#include "hello.h" //hello()
#include <stdio.h> //puts()
int main()
{
const char* str = hello();
puts(str);
}
The build
Fix the copy-paste of Makefile (replace leading spaces by one tabulation).
> sed 's/^ */\t/' -i */Makefile
The make command is the same on both platforms. The given output is on Unix-like OSes:
> make -C lib
make: Entering directory '/tmp/lib'
gcc -c hello.c -fPIC -o hello.o
# -c hello.c => hello.c is first file after ':' => Compile hello.c
# -fPIC => Position-Independent Code (required for shared lib)
# -o hello.o => hello.o is the target => Output file (-o) is hello.o
gcc hello.o -shared -o libhello.so
# hello.o => hello.o is the first after ':' => Link hello.o
# -shared => Generate shared library
# -o libhello.so => Output file (-o) is libhello.so (libhello.so or hello.dll)
make: Leaving directory '/tmp/lib'
> make -C app
make: Entering directory '/tmp/app'
gcc -c main.c -I ../lib -o main.o
# -c main.c => compile (-c) main.c (first file after :) = main.cpp
# -I ../lib => search headers (*.h) in directory ../lib
# -o main.o => output file (-o) is main.o (target) = main.o
gcc main.o -L../lib -lhello -o app
# main.o => main.o (all files after the :) = main.o (here only one file)
# -L../lib => look for libraries in directory ../lib
# -lhello => use shared library hello (libhello.so or hello.dll)
# -o app => output file (-o) is app.exe (target) = "app.exe" or "app"
make: Leaving directory '/tmp/app'
The run
The application requires to know where is the shared library.
On Windows, a simple solution is to copy the library where the application is:
> cp -v lib/hello.dll app
`lib/hello.dll' -> `app/hello.dll'
On Unix-like OSes, you can use the LD_LIBRARY_PATH environment variable:
> export LD_LIBRARY_PATH=lib
Run the command on Windows:
> app/app.exe
hello
Run the command on Unix-like OSes:
> app/app
hello
I was recently experimenting in order to answer this question I was asking myself. Here are my conclusions:
Since in Windows, you can't be sure that the uname command is available, you can use gcc -dumpmachine. This will display the compiler target.
There may be also a problem when using uname if you want to do some cross-compilation.
Here's a example list of possible output of gcc -dumpmachine:
mingw32
i686-pc-cygwin
x86_64-redhat-linux
You can check the result in the makefile like this:
SYS := $(shell gcc -dumpmachine)
ifneq (, $(findstring linux, $(SYS)))
# Do Linux things
else ifneq(, $(findstring mingw, $(SYS)))
# Do MinGW things
else ifneq(, $(findstring cygwin, $(SYS)))
# Do Cygwin things
else
# Do things for others
endif
It worked well for me, but I'm not sure it's a reliable way of getting the system type. At least it's reliable about MinGW and that's all I need since it does not require to have the uname command or MSYS package in Windows.
To sum up, uname gives you the system on which you're compiling, and gcc -dumpmachine gives you the system for which you are compiling.
The git makefile contains numerous examples of how to manage without autoconf/automake, yet still work on a multitude of unixy platforms.
Update: I now consider this answer to be obsolete. I posted a new perfect solution further down.
If your makefile may be running on non-Cygwin Windows, uname may not be available. That's awkward, but this is a potential solution. You have to check for Cygwin first to rule it out, because it has WINDOWS in its PATH environment variable too.
ifneq (,$(findstring /cygdrive/,$(PATH)))
UNAME := Cygwin
else
ifneq (,$(findstring WINDOWS,$(PATH)))
UNAME := Windows
else
UNAME := $(shell uname -s)
endif
endif
That's the job that GNU's automake/autoconf are designed to solve. You might want to investigate them.
Alternatively you can set environment variables on your different platforms and make you Makefile conditional against them.
I ran into this problem today and I needed it on Solaris so here is a POSIX standard way to do (something very close to) this.
#Detect OS
UNAME = `uname`
# Build based on OS name
DetectOS:
-#make $(UNAME)
# OS is Linux, use GCC
Linux: program.c
#SHELL_VARIABLE="-D_LINUX_STUFF_HERE_"
rm -f program
gcc $(SHELL_VARIABLE) -o program program.c
# OS is Solaris, use c99
SunOS: program.c
#SHELL_VARIABLE="-D_SOLARIS_STUFF_HERE_"
rm -f program
c99 $(SHELL_VARIABLE) -o program program.c
I finally found the perfect solution that solves this problem for me.
ifeq '$(findstring ;,$(PATH))' ';'
UNAME := Windows
else
UNAME := $(shell uname 2>/dev/null || echo Unknown)
UNAME := $(patsubst CYGWIN%,Cygwin,$(UNAME))
UNAME := $(patsubst MSYS%,MSYS,$(UNAME))
UNAME := $(patsubst MINGW%,MSYS,$(UNAME))
endif
The UNAME variable is set to Linux, Cygwin, MSYS, Windows, FreeBSD, NetBSD (or presumably Solaris, Darwin, OpenBSD, AIX, HP-UX), or Unknown. It can then be compared throughout the remainder of the Makefile to separate any OS-sensitive variables and commands.
The key is that Windows uses semicolons to separate paths in the PATH variable whereas everyone else uses colons. (It's possible to make a Linux directory with a ';' in the name and add it to PATH, which would break this, but who would do such a thing?) This seems to be the least risky method to detect native Windows because it doesn't need a shell call. The Cygwin and MSYS PATH use colons so uname is called for them.
Note that the OS environment variable can be used to detect Windows, but not to distinguish between Cygwin and native Windows. Testing for the echoing of quotes works, but it requires a shell call.
Unfortunately, Cygwin adds some version information to the output of uname, so I added the 'patsubst' calls to change it to just 'Cygwin'. Also, uname for MSYS actually has three possible outputs starting with MSYS or MINGW, but I use also patsubst to transform all to just 'MSYS'.
If it's important to distinguish between native Windows systems with and without some uname.exe on the path, this line can be used instead of the simple assignment:
UNAME := $(shell uname 2>NUL || echo Windows)
Of course in all cases GNU make is required, or another make which supports the functions used.
Here's a simple solution that checks if you are in a Windows or posix-like (Linux/Unix/Cygwin/Mac) environment:
ifeq ($(shell echo "check_quotes"),"check_quotes")
WINDOWS := yes
else
WINDOWS := no
endif
It takes advantage of the fact that echo exists on both posix-like and Windows environments, and that in Windows the shell does not filter the quotes.
Note that Makefiles are extremely sensitive to spacing. Here's an example of a Makefile that runs an extra command on OS X and which works on OS X and Linux. Overall, though, autoconf/automake is the way to go for anything at all non-trivial.
UNAME := $(shell uname -s)
CPP = g++
CPPFLAGS = -pthread -ansi -Wall -Werror -pedantic -O0 -g3 -I /nexopia/include
LDFLAGS = -pthread -L/nexopia/lib -lboost_system
HEADERS = data_structures.h http_client.h load.h lock.h search.h server.h thread.h utility.h
OBJECTS = http_client.o load.o lock.o search.o server.o thread.o utility.o vor.o
all: vor
clean:
rm -f $(OBJECTS) vor
vor: $(OBJECTS)
$(CPP) $(LDFLAGS) -o vor $(OBJECTS)
ifeq ($(UNAME),Darwin)
# Set the Boost library location
install_name_tool -change libboost_system.dylib /nexopia/lib/libboost_system.dylib vor
endif
%.o: %.cpp $(HEADERS) Makefile
$(CPP) $(CPPFLAGS) -c $
Another way to do this is by using a "configure" script. If you are already using one with your makefile, you can use a combination of uname and sed to get things to work out. First, in your script, do:
UNAME=uname
Then, in order to put this in your Makefile, start out with Makefile.in which should have something like
UNAME=##UNAME##
in it.
Use the following sed command in your configure script after the UNAME=uname bit.
sed -e "s|##UNAME##|$UNAME|" < Makefile.in > Makefile
Now your makefile should have UNAME defined as desired. If/elif/else statements are all that's left!
I had a case where I had to detect the difference between two versions of Fedora, to tweak the command-line options for inkscape:
- in Fedora 31, the default inkscape is 1.0beta which uses --export-file
- in Fedora < 31, the default inkscape is 0.92 which uses --export-pdf
My Makefile contains the following
# set VERSION_ID from /etc/os-release
$(eval $(shell grep VERSION_ID /etc/os-release))
# select the inkscape export syntax
ifeq ($(VERSION_ID),31)
EXPORT = export-file
else
EXPORT = export-pdf
endif
# rule to convert inkscape SVG (drawing) to PDF
%.pdf : %.svg
inkscape --export-area-drawing $< --$(EXPORT)=$#
This works because /etc/os-release contains a line
VERSION_ID=<value>
so the shell command in the Makefile returns the string VERSION_ID=<value>, then the eval command acts on this to set the Makefile variable VERSION_ID.
This can obviously be tweaked for other OS's depending how the metadata is stored. Note that in Fedora there is not a default environment variable that gives the OS version, otherwise I would have used that!
An alternate way that I have not seen anyone talking about is using the built-in variable SHELL. The program used as the shell is taken from the variable SHELL. On MS-Windows systems, it is most likely to be an executable file with .exe extension (like sh.exe).
In that case, the following conditional test:
ifeq ($(suffix $(SHELL)),.exe)
# Windows system
else
# Non-Windows system
endif
Would have the same result as using the environment variable OS:
ifeq ($(OS),Windows_NT)
# Windows system
else
# Non-Windows system
endif
However, it seems the latter is the most popular solution, so I would recommend you stick with it.