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 := Windowselse 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 := Windowselse 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 := Windowselse 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 WIN32endififeq ($(detected_OS),Darwin) # Mac OS X CFLAGS += -D OSXendififeq ($(detected_OS),Linux) CFLAGS += -D LINUXendififeq ($(detected_OS),GNU) # Debian GNU Hurd CFLAGS += -D GNU_HURDendififeq ($(detected_OS),GNU/kFreeBSD) # Debian kFreeBSD CFLAGS += -D GNU_kFreeBSDendififeq ($(detected_OS),FreeBSD) CFLAGS += -D FreeBSDendififeq ($(detected_OS),NetBSD) CFLAGS += -D NetBSDendififeq ($(detected_OS),DragonFly) CFLAGS += -D DragonFlyendififeq ($(detected_OS),Haiku) CFLAGS += -D Haikuendif
Notes:
Command
uname
is same asuname -s
because option-s
(--kernel-name
) is the default. See whyuname -s
is better thanuname -o
.The use of
OS
(instead ofuname
) simplifies the identification algorithm. You can still use solelyuname
, but you have to deal withif/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 variableMSVC
(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 := Windowselse uname_S := $(shell uname -s)endififeq ($(uname_S), Windows) target = hello.dllendififeq ($(uname_S), Linux) target = libhello.soendif#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 := Windowselse uname_S := $(shell uname -s)endififeq ($(uname_S), Windows) target = app.exeendififeq ($(uname_S), Linux) target = appendif#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 libmake: 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.ogcc 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 appmake: 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.ogcc 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.exehello
Run the command on Unix-like OSes:
> app/apphello