331

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
samoz
  • 56,849
  • 55
  • 141
  • 195

14 Answers14

367

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
Somu
  • 3,593
  • 6
  • 34
  • 44
Trevor Robinson
  • 15,694
  • 5
  • 73
  • 72
  • 10
    Sadly the `PROCESSOR_ARCHITECTURE` envvar seems to be virtualized depending on whether the process is 32-bit or 64-bit. So if your `make` is 32-bit and you're trying to build a 64-bit application, it will fail. Using it in combination with `PROCESSOR_ARCHITEW6432` worked for me (see [this](http://stackoverflow.com/questions/1738985/why-processor-architecture-always-returns-x86-instead-of-amd64), and [that](http://blogs.msdn.com/b/david.wang/archive/2006/03/26/howto-detect-process-bitness.aspx)) – Thomas Nov 29 '13 at 12:59
  • 5
    Would be nice if the `make` team add a couple of magic variables with os and arch, probably too much trouble. – Alex Aug 25 '14 at 15:00
  • this breaks on `FreeBSD`, as `$OS` is not set. `$OSTYPE` can be `freebsd10.1` though. – Janus Troelsen Oct 02 '15 at 11:56
  • 7
    @JanusTroelsen: it shouldn't matter whether `OS` is set on non-Windows systems. Make treats unset the same as empty, which will cause a jump to the `uname`-based block. You just need to add a FreeBSD check there. – Trevor Robinson Oct 02 '15 at 16:53
  • 4
    this breaks on osx too. `/bin/sh: -c: line 0: syntax error near unexpected token `,Windows_NT' /bin/sh: -c: line 0: `ifeq (,Windows_NT)' make: *** [os] Error 2` – k107 Dec 02 '16 at 07:58
  • 1
    @kristi It sounds like you ran this as shell commands and not in the makefile directives context. – Phil Hord Mar 24 '17 at 17:17
  • I get `process_begin: CreateProcess(NULL, ifeq (Windows_NT,Windows_NT), ...) failed.`... – Post Self Jul 01 '17 at 20:00
  • uname -s on Cygwin returns with an OS version number. uname -o returns 'Cygwin', and works. uname -o on Linux is 'GNU/Linux', and non-standard. So I am just using '-o' for a cygwin test. – rickfoosusa Jul 06 '17 at 21:53
  • 1
    I have a Mac running El Capitan and `uname -p` outputs "**i386**" whereas `uname -m` outputs "**x86_64**" ([as documented by others](https://apple.stackexchange.com/questions/140651/why-does-arch-output-i386)) So perhaps `uname -m` is more reliable. – drwatsoncode Sep 20 '17 at 02:07
  • 6
    `if`,`else` and `endif` *MUST NOT* be indented (in my experiments). I also only got this to work *outside* of the target blocks – Adam Straughan May 15 '18 at 07:52
  • I can not agree with the "don't assume ```uname``` exists" advice. To the best of my knowledge, there is no method to install GNU Make without also installing ```uname```. – burito Jul 12 '18 at 14:52
  • strikingly similar with this one https://gist.github.com/sighingnow/deee806603ec9274fd47 – mihaipopescu Sep 11 '18 at 13:16
  • The OS variable remains set for Cygwin and MSYS, so you can't use it to distinguish Windows, Cygwin and MSYS. But there is an excellent solution. See it at the bottom of this page. – Ken Jackson Sep 11 '18 at 19:23
135

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
Somu
  • 3,593
  • 6
  • 34
  • 44
dbrown0708
  • 4,544
  • 4
  • 25
  • 22
  • Just to be explicit, that line goes in your Makefile. I just tried that construct in Makefiles in Cygwin and OSX, and it worked as expected. Something to try: Type uname on your command line. That'll tell you the value for that OS. OSX will likely be "Darwin". – dbrown0708 Apr 03 '09 at 18:10
  • The GnuWin32 project has both uname and Gnu make available as native Windows applications, making this technique portable to MingW at a command prompt as well as Cygwin on Windows. – RBerteig Apr 04 '09 at 01:52
  • It fails on my Solaris machine when it's inside the makefile. The uname command is available on that system. – samoz Apr 04 '09 at 21:20
  • 1
    Did it work in OSX or Linux? Is it possible you're not running GNU make but some other variant? If you aren't using GNU, it might be worth installing that version on your Solaris box since OSX and Linux will be running GNU. – dbrown0708 Apr 06 '09 at 03:01
  • FYI, uname on Solaris (tested on 9 and 10) reports SunOS – basszero Jul 22 '09 at 18:50
  • And you can grep the return value from uname if you're only interested in part of the string. See: https://github.com/duncan-bayne/myfitnessdata/blob/master/Makefile – Duncan Bayne Feb 16 '11 at 09:27
  • 6
    Note that if you put this inside a Maketarget it must not be indented. – nylund Feb 05 '12 at 15:28
  • 4
    Isn't the ":=" syntax specific to GNU Make? – Ankur Sethi Apr 15 '12 at 15:06
86

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
oHo
  • 51,447
  • 27
  • 165
  • 200
  • I appreciate your effort, but the main question was to detect the operating system. Your example only detects Linux and otherwise outright assumes Windows. – Shahbaz Feb 09 '13 at 16:58
  • Hi @Shahbaz. You are right, my answer does not give a different approach than the other answers. Moreover, my script assumes the platform is Windows when `uname` is not Linux. I give just an example you may not need, but this may help someone searching (on the web) a way to implement a `Makefile` for both platforms ;-) What should I change in my answer? Cheers – oHo Feb 11 '13 at 08:59
  • try to come up with ways to correctly identify other operating systems too! The goal is to find a method that is not too complicated, but more importantly is bullet proof. That is, it wouldn't make mistake no matter what. – Shahbaz Feb 11 '13 at 09:52
  • Hi @Shahbaz. When I will find a more safe way to detect the platform, I will update this answer... for the moment I do not have enough time to investigate. Have a fine week end, Cheers ;-) – oHo Feb 22 '13 at 16:04
  • Hi @Shahbaz. After more than two years, I finally reworked my answer. Hope the proposed ***operating system identification*** is enough *bullet proof*. What do you think? Cheers – oHo Oct 08 '15 at 09:54
  • Typo? `$^ => $^ is the first after ':' => Link hello.o` - The variable `$^` is the list of _all_ prerequisites, not just the first. As in your previous example, `$<` is just the first.[Docs here](http://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html#Automatic-Variables) – cfi Nov 03 '15 at 07:58
  • Thank you very much @cfi :-) I have also added your link ;-) Cheers – oHo Nov 03 '15 at 09:35
  • 1
    @olibre Thanks for the detailed example, much appreciated and helps get me started quickly. In the `lib/Makefile` example, `target` is used for `.so` vs `.dll`. A parallel example for the `app/makefile` would be useful for the `empty string` vs `.exe` comparison for app filename. For instance, I don't usually see `app.exe` on Unix-like OSes. ;-) –  May 23 '17 at 06:09
  • Thank you @user314159 for your feedback ;-) I have just edited my answer. I think the answer is longer than before (e.g. the `app/Makefile` is double) but the answer is more consistent. Cheers ;-) – oHo May 23 '17 at 12:43
  • 1
    LSF? LFS? Typo? – Franklin Yu Feb 11 '18 at 23:18
  • The problem with the OS environment variable is that it remains set in the Cygwin environment. You can't use it to distinguish Cygwin from native Windows. – Ken Jackson Aug 28 '18 at 14:37
  • Hi @KenJackson Thanks for your feedback. My answer assumes we want `detected_OS=Windows` when we run Cygwin or Mingw over Windows. Do you need to distinguish Cygwin/Mingw/...? – oHo Sep 07 '18 at 09:33
  • Cygwin and MSYS behave more like Linux than Windows, so it's important to distinguish them, if you work with them. See my perfect solution at the bottom of the page. – Ken Jackson Sep 11 '18 at 19:25
  • Hi Ken. Thank you for having posted a second answer. Your [ultimate answer](https://stackoverflow.com/a/52062069/938111) is awesome => +1 I have just added a link in my answer to invite people to read [your answer](https://stackoverflow.com/a/52062069/938111). Is it OK for you? Cheers, have fun ;-) – oHo Oct 07 '18 at 20:24
  • The $(OS) env var doesn't seem to exist on macOS. – Caleb Stanford Nov 18 '22 at 20:03
21

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
phsym
  • 1,364
  • 10
  • 20
  • This is a good point. However, doesn't `uname` come with `MinGW` anyway? Nevertheless, the extra note regarding cross-compilation is great. – Shahbaz Sep 11 '12 at 14:48
  • 2
    @Shahbaz MinGW setup can install MSYS (which contains uname) but it's optionnal. It's still possible to find systems with only MinGW gcc tools – phsym Sep 11 '12 at 16:05
  • 1
    That doesn't work anywhere Clang is the default compiler, like OS X and FreeBSD. – MarcusJ Jun 20 '16 at 07:56
  • @SebastianGodelet @MarcusJ An easy fix is `$(shell $(CC) -dumpmachine)`. As of OS X Sierra, the -dumpmachine command works on Clang. – Vortico Jun 15 '17 at 09:09
  • On OS X 10.12.5 it's `x86_64-apple-darwin16.6.0` and that works wether you call it `gcc`, `cc`, or `clang`, but not `cl` – MarcusJ Jun 25 '17 at 01:59
19

The git makefile contains numerous examples of how to manage without autoconf/automake, yet still work on a multitude of unixy platforms.

JesperE
  • 63,317
  • 21
  • 138
  • 197
12

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
Ken Jackson
  • 479
  • 4
  • 5
  • This is good to now! Can you tell me one thing, though? I don't use Cygwin, but I have MinGW installed with it's bin path in PATH. If I issue `uname` from a normal cmd terminal it gives me MINGW. What I mean is, I still have `uname` without using Cygwin. I also have git bash, but I haven't tried uname on it (right now I'm in Linux). Can you tell me how these two can be incorporated in your code? – Shahbaz May 16 '12 at 11:56
  • If you are sure that uname is available, that's the best solution. But in my environment, everyone is using Windows and few people have either [cygwin](http://cygwin.com) or [mingw](http://mingw.org) installed, so I have no guarantee that anything even as standard as uname will work. I'm currently having some difficulty with the above code running make.exe in a cmd shell. Windows is a very frustrating platform to work with. – Ken Jackson May 17 '12 at 21:34
  • What I mean is, before testing for existence of WINDOWS in PATH, you make sure you are not dealing with cygwin, how can you make sure you are not dealing with MinGW? For example, is it possible in Makefile to test whether a command can be run, and if `uname` couldn't be run we would understand we are in Windows? – Shahbaz May 17 '12 at 22:49
  • I'm struggling to find a clean solution to this Mingw/cygwin/shell-or-cmd/Linux as well. At the end of the day, something like premake or cmake seems like the best idea. – Isaac Nequittepas Apr 22 '13 at 17:45
  • This is no longer the best solution. The new solution I posted distinguishes native Windows by looking for ';' in the PATH variable, without a shell call. – Ken Jackson Aug 28 '18 at 16:04
9

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
Huckle
  • 1,810
  • 3
  • 26
  • 40
  • 1
    Getting error on OSX: "Makefile:22: *** missing separator. Stop.". On this line: "-@make $(UNAME_S)". – Czarek Tomczak Feb 13 '14 at 11:55
  • OSX likely isn't compliant so try these in order. (1) Make sure you are using a TAB as the first character on the line (2) Remove the "-@" in front of the make (2a) If 2 worked, try one character and then the other (3) Ensure that UNAME_S is defined, try echo $(UNAME_S) instead of -@make $(UNAME_S) – Huckle Feb 16 '14 at 03:41
9

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.

Ken Jackson
  • 479
  • 4
  • 5
9

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.

Douglas Leeder
  • 52,368
  • 9
  • 94
  • 137
  • 15
    I strongly advise against using automake/autoconf. They are tedious to use, add a lot of overhead to your files, to your build time. They simply add complexity for usually very little effect (still no portability between systems). – Johannes Overmann Nov 04 '13 at 15:48
  • 3
    I just spent a couple of days learning to get `make` to do what I want. Do I now want to get into automake/autoconf as well? - NO. What _can_ be done in the makefile, certainly should be done in the makefile, if only so that I don't have several stop-off points every time I want to amend compile & link. – Engineer Jul 04 '15 at 18:32
  • How many platforms do your makefiles support? automake and autoconf really come into their own when you want portability to many platforms. – Douglas Leeder Jul 06 '15 at 18:43
  • 2
    I'm not gonna require useless dependencies, and change my whole build system just to find out what OS it's being compiled for. – MarcusJ Jun 20 '16 at 08:08
8

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.

Samuel
  • 8,063
  • 8
  • 45
  • 41
  • 1
    Quite unsafe since `$PATH` might refer to another `echo` (Mine does...) – yyny Apr 16 '16 at 01:33
  • @YoYoYonnY Why does your path refer to another echo? Seems like a very unlikely situation. – Samuel Apr 20 '16 at 13:13
  • 1
    not really, git does it, mingw does it, cygwin does it... And I personally put C:\Windows\System32 at the bottom of my path. – yyny Apr 20 '16 at 13:22
  • @YoYoYonnY Of course Cygwin does it, and this solution works for Cygwin. Note that my answer is for detecting a posix-like (POSIX, as in Linux/Unix/Mac-like) environment, which Cygwin is. The same is probably true for your git (bash I'm assuming) environment. – Samuel Apr 21 '16 at 14:06
  • 2
    This "solution" "works" for all environments, but my point is that this definitly doesn't detect windows safely. If I want to set a `-mwindows` flag or chose between a `.dll` or `.so`, this would fail. – yyny Apr 21 '16 at 14:18
  • 1
    @YoYoYonnY Thanks for clarifying. In my situation I cared only if I was in a Cygwin or Windows or Linux environment rather than what OS I was in, so this was helpful for me. Sounds like your needs are different than what mine were. – Samuel Apr 22 '16 at 12:57
  • yes, $(OS) seems a little safer, although it is still just a environment variable. – yyny Apr 22 '16 at 14:25
  • I like this solution, Thanks – wukong Apr 18 '19 at 12:25
  • Many Windows development environments provide a unix-style echo, which defeats this test. The example I was burned by is MCUxpresso (NXP embedded development), but there are plenty of others. – Dave Nadler Jan 21 '23 at 23:18
4

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 $
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ChrisInEdmonton
  • 4,470
  • 5
  • 33
  • 48
3

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!

dandan78
  • 13,328
  • 13
  • 64
  • 78
Sean
  • 31
  • 1
1

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!

1

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.

LucasJ
  • 68
  • 1
  • 9