Using make for cross platform compilation

As somebody who has used both autotools and CMake, I would recommend using CMake over rolling your own Makefiles and using autotools. CMake has so many useful, easy to use benefits, even if it is a simple project. For example, CMake will create an NSIS installer, manage production vs. debug compilation and has a nice testing framework. The one knock I had was that it was kind of hard to find real examples of how to use it. So much open source software uses autotools that realworld examples for it are easy to find. However, if you download the CMake source, there are lots of examples in the Example directory and Test directory.

In other words, the Juice is worth the squeeze.


Use a single make file and put the platform-specifics in conditionals, eg

ifeq ($(OS),Windows_NT)
    DLLEXT := .dll
else
    DLLEXT := .so
endif

DLL := libfoo$(DLLEXT)

lib : $(DLL)

I use UNAME := $(shell uname) within my Makefile to detect the platform (Linux or MS-Windows).

I provide below a complete example based on make and gcc to build a shared library: *.so or *.dll depending on the platform.

The example is basic/simple/stupid to be more understandable :-)

To use make and gcc on MS-Windows, Cygwin or MinGW can be installed.

The example uses five files:

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

The Makefiles

app/Makefile

app.exe: main.o
        gcc -o $@ $^ -L../lib -lhello
        # '-o $@'    => output file => $@ = the target file (app.exe)
        # '   $^'    => no options => Link all depended files 
        #            => $^ = main.o and other if any
        # '-L../lib' => look for libraries in directory ../lib
        # '-lhello   => use shared library hello (libhello.so or hello.dll)

%.o: %.c
        gcc -o $@ -c $< -I ../lib
        # '-o $@'     => output file => $@ = the target file (main.o)
        # '-c $<'     => COMPILE the first depended file (main.cpp)
        # '-I ../lib' => look for headers (*.h) in directory ../lib

clean:
        rm -f *.o *.so *.dll *.exe

lib/Makefile

UNAME := $(shell uname)

ifeq ($(UNAME), Linux)
TARGET = libhello.so
else
TARGET = hello.dll
endif

$(TARGET): hello.o
        gcc  -o $@  $^  -shared
        # '-o $@'    => output file => $@ = libhello.so or hello.dll
        # '   $^'    => no options => Link all depended files => $^ = hello.o
        # '-shared'  => generate shared library

%.o: %.c
        gcc  -o $@  -c $<  -fPIC
        # '-o $@' => output file => $@ = the target file (main.o)
        # '-c $<' => compile the first depended file (main.cpp)
        # '-fPIC' => Position-Independent Code (required for shared lib)

clean:
        rm -f *.o *.so *.dll *.exe

The source code

app/main.c

#include "hello.h" //hello()
#include <stdio.h> //puts()

int main()
{
    const char* str = hello();
    puts(str);
}

lib/hello.h

#ifndef __HELLO_H__
#define __HELLO_H__

const char* hello();

#endif

lib/hello.c

#include "hello.h"

const char* hello()
{
    return "hello";
}

The build

Fix the copy-paste of Makefiles (replace leading spaces by tabulation).

> sed  -i  's/^  */\t/'  */Makefile

The make command is the same on both platforms. The given output is for MS-Windows (unnecessary lines removed).

> cd lib
> make clean
> make
gcc  -o hello.o  -c hello.c  -fPIC
gcc  -o hello.dll  hello.o  -shared
> cd ../app
> make clean
> make
gcc -o main.o -c main.c -I ../lib
gcc -o app.exe main.o -L../lib -lhello

The run

The application requires to know where is the shared library.

On MS-Windows, the simple/basic/stupid way is to copy the library where the application is:

> cp -v lib/hello.dll app
`lib/hello.dll' -> `app/hello.dll'

On Linux, use the LD_LIBRARY_PATH environment variable:

> export LD_LIBRARY_PATH=lib

The run command line and output are the same on both platforms:

> app/app.exe
hello