uname) $(shell OS detecting makefile

6 Answers

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.


UNAME := $(shell uname)

ifeq ($(UNAME), Linux)
# do something Linux-y
ifeq ($(UNAME), Solaris)
# do something Solaris-y
$(os) makefile

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

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

        rm -f lex.yy.c y.tab.c y.tab.h assembler *.o *.tmp *.debug *.acts

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
 # Do things for others

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.

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
ifneq (,$(findstring WINDOWS,$(PATH)))
    UNAME := Windows
    UNAME := $(shell uname -s)

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
    -@make $(UNAME)

# OS is Linux, use GCC
Linux: program.c
    rm -f program
    gcc $(SHELL_VARIABLE) -o program program.c

# OS is Solaris, use c99
SunOS: program.c
    rm -f program
    c99 $(SHELL_VARIABLE) -o program program.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:


Then, in order to put this in your Makefile, start out with Makefile.in which should have something like


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 finally found the perfect solution that solves this problem for me.

ifeq '$(findstring ;,$(PATH))' ';'
    UNAME := Windows
    UNAME := $(shell uname 2>/dev/null || echo Unknown)
    UNAME := $(patsubst CYGWIN%,Cygwin,$(UNAME))
    UNAME := $(patsubst MSYS%,MSYS,$(UNAME))
    UNAME := $(patsubst MINGW%,MSYS,$(UNAME))

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.