Tuesday, June 20, 2006

A Cool Makefile

Recently I wanted to make a makefile that could detect what OS you are
using and weither it should compile for debug or release mode. After
hours of playing around and scouting the net for answers, I actually
managed to code one very close to what I wanted.
For those who doesn't know what a makefile is, well... ever heard of MingW C/C++ or GNU C/C++ ?

They are both the same except MingW is for Windows while GNU's one is
for Linux. Its a script file that prepares and calls the compiler to
compile your code.
Why would you need one? For various reasons. Mostly for automated
tasks, for instance, lets say you want all your compiled .o files in a
"output" directory in the current path, then you can instruct 'make'
(the interpreter that reads your Makefile) to create that directory and
to tell the compiler to place all those object files into that
directory.
You might wonder why you don't use BATCH scripting for windows or BASH
scripting for Linux instead of Makefiles, well... Makefiles are more
programming oriented, it has functionality specifically for compiling
purposes that BASH or BATCH scripting doesn't have. Well, you might
still accompilsh the same with BASH but not entirely, I believe.
How would you invoke a Makefile? Easy, make sure that you have a file
called Makefile in your
source directory and type : make
You can add parameters to 'make' according to what the Makefile
expects, so its a good idea to either read the Makefile itself or any
READMEs found with the source.
I'm not going through all the basics about Makefiles, but I will
explain how I managed to complete one mentioned above.


#
# NOTES:
# Please use GNU's make application for this makefile.
# Also, this is made for Windows & Linux compilations only!
# This creates an output folder where all the object
# files are going to be stored. It'll compile all .c files
# found in the current directory where the makefile lies.
# The makefile will detect if this is being compiled in
# an Windows NT environment, else it'll assume its
# a Linux environment.
# Also, by default it will compile in debug mode.
# To build in release mode, use the following:
# make dist
#
# This makefile is compatible with CodeBlocks (RC2).
#

# Set up OS specific stuff
# Since this could be compiled on a different
# windows version, we rather check if the
# OSTYPE var exists and that it is 'linux'.

# If so then we're compiling for linux,
# else we assume for some windows version.
ifeq ($(OSTYPE),linux)
EXT :=
CFLAGS := -DLINUX
OS := Linux
else
EXT :=.exe
CFLAGS := -DWIN32
OS := Windows
endif

# Ordinary vars needed
APP := lacp
OUTPUT := output
BIN := $(APP)$(EXT)
CC := gcc
OBJS := $(patsubst %.c,$(OUTPUT)/%.o,$(wildcard *.c))

# Not our actual targets!
.PHONY: all dist default clean clean_default debugmode releasemode

# Where to start building with what targets
all: default
default: debugmode
dist: releasemode

# The following targets (debug & release) has multiple
# lables because one cannot define variables after the
# first target line, ie. all.
# So, make allows you to change vars but only 1 per target
# line and that change is only valid within that target scope.
# Thats why I have $(BIN) in the debugmode and releasemode targets
# instead of the targets above, sothat the variable changes can be
# carried over to any $(BIN) compilations.
# The double colons are to tell make to execute the var changes
# and commands in order given below.

# DEBUG mode
debugmode:: MODE := DEBUG
debugmode:: CFLAGS += -g -D__DEBUG__
debugmode::
@echo Compiling for $(OS)...
@echo Compiling in $(MODE)...
ifeq ($(OS),Windows)
-@if not exist "./"$(OUTPUT)
mkdir $(OUTPUT)
else
mkdir -p $(OUTPUT)
endif
debugmode:: $(BIN)

# RELEASE mode
releasemode:: MODE := RELEASE
releasemode:: CFLAGS += -O3
releasemode:: LDFLAGS += -s
releasemode::
@echo Compiling for $(OS)...
@echo Compiling in $(MODE)...
ifeq ($(OS),Windows)
-@if not exist "./"$(OUTPUT)
mkdir $(OUTPUT)
else
mkdir -p $(OUTPUT)
endif
releasemode:: $(BIN)

# Clean up
distclean_default: clean
clean_default: clean
clean: @echo Cleaning up...
@$(RM) -f $(BIN)
@$(RM) -r -f $(OUTPUT)

# Actual compilation
$(BIN): $(OBJS)
$(CC) $(OBJS) -o $(BIN)

$(OUTPUT)/%.o: %.c
$(CC) $(CFLAGS) -c $^ -o $@



Those of you who know Makefiles, can remember that it is very important
to have actual TAB characters below every target label to indicate that
those statements belong to that target, almost like in Python.
I apologise if my code doesn't appear to be indented since the blog
site apparently clears out "unnecessary" spaces.
How this Makefile works is, it first checks if OSTYPE is equal to
linux. In Linux if you type 'export' in the console, you'll find a
variable calles OSTYPE. Since my Makefile is only for Windows and Linux
and nothing else, if OSTYPE doesn't exist, then the Makefile assumes
that you are in Windows.
Then if you have just performed a 'make' or 'make default' command,
then the Makefile assumes that you are compiling for debug mode. If you
have specified 'make dist' or make distribution, then it assumes
Release mode.
It creates an output directory where all the object files will be
placed, and it scouts the current directory only for C files and
compiles them into the output directory. So if you create a C file,
etc. the Makefile will automatically include it. If you need subdirs
with files in them, just tell the Makefile to also scout in those
specific directories to compile them into the output directory.
As you can see, now you don't have to update the compiling process for
smallish projects which are designed for Windows and Linux anymore. The
Makefile takes care of it for you. If you have made any cool
modifications to it, please drop me a mail with your change, I would
like to see what else can be done with this.

Check and enjoy!

No comments: