OS detecting 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
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
    

    If you do not need sophisticated stuff as done by Git LFS, you can detect the Operating System just using two simple tricks:

  • environment variable OS
  • and then the command uname -s
  • ifeq ($(OS),Windows_NT)
        detected_OS := Windows
    else
        detected_OS := $(shell uname -s)
    endif
    

    Or a more safe way, if not Windows and command uname not available:

    ifeq ($(OS),Windows_NT)
        detected_OS := Windows
    else
        detected_OS := $(shell sh -c 'uname -s 2>/dev/null || echo not')
    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
    

    Please see also this detailed answer about importance of uname -s better than uname -o .

    The use of OS (instead of uname -s ) simplifies the identification algorithm. You can still use solely uname -s but you have to deal with if/else blocks to check all MinGW/Cygwin/... variations.

    Note: The environment variable OS is always set to "Windows_NT" on any Windows platform (see Windows Environment Variables on Wikipedia). An alternative of OS is the environment variable MSVC (it checks the presence of MS Visual Studio, see example using MSVC).


    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 5 files

     ├── lib
     │   └── Makefile
     │   └── hello.h
     │   └── hello.c
     └── app
         └── Makefile
         └── main.c
    

    Do not forget: files Makefile are indented using tabulations.

    The two files Makefile

    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
    
    链接地址: http://www.djcxy.com/p/61430.html

    上一篇: gcc的makefile错误:“没有规则,使目标...”

    下一篇: OS检测makefile