You are on page 1of 16

Chris' Makefile Tutorial

Chris Serson
University of Victoria
June 26, 2007

Contents:

Chapter Page

Introduction 2

1 The most basic of Makefiles 3

2 Syntax so far 5

3 Making Makefiles Modular 7

4 Multi-file projects 11

5 Some more tidbits 13

Conclusion 16
2

Introduction:

The purpose of this tutorial is to introduce new students


to the wonders and terrors of Makefiles. When I was first
introduced to these things, I didn't have a clue what was
going on. I'd have to look over my friend's shoulder and
copy his Makefile or copy and paste an old Makefile and
hope I could just tweak it to run with my latest project.
Even in 4th year, I had trouble with these things and I was
always embarrassed to ask for help seeing as it is
something I should have learned early on in my career as a
student. My hope is that this tutorial will keep other
students from suffering my fate.

The idea behind having a Makefile is that you can save


yourself a lot of typing of compiler commands. Rather than
typing some crazy long string of flags and files, just type
'make' or 'make projectName' and away it goes.
3

Chapter 1 - The most basic of Makefiles:


The most basic of Makefiles is basically just a file that
runs the same command you would normally type into the
command line. For instance, you may type:
> gcc -o myprog myfile.c
which would run off and compile myfile.c to an executable
called myprog. Instead of this, you could put that line in
a Makefile (the name of the file is exactly that,
'Makefile' no extension) and then run:
> make
for the exact same effect. More useful still is the fact
that you can create a Makefile with multiple targets:

<--- in the file


project1: myfile1.c
gcc -o project1 myfile1.c

project2: myfile2.c
gcc -o project2 myfile2.c
<--- end of file

With this file, if you type:


> make
it will run the first compile command. But if you type:
> make project2
it will run the compile command under the 'project2:'
heading. Even better, lets add another target to our
Makefile:

<--- in the file


...
clean:
rm -f *.o project1 project2
<--- end of file

Now if you type:


> make clean
it will delete all compiled files, giving you a clean
workspace to recompile your source code from scratch. As a
note, in case you didn't already know, '.o' files are
'object' files which contain compiled code which has yet to
be linked together. We'll see this again in Chapter 4.
Lets add another useful target:
4

<--- in the file


all: project1 project2
...
<--- end of file

If you add this line to the very top of your Makefile, now
when you type:
> make
it will run your 'all:' target, which points to both of
your 'project_' targets. Of course, you could add the
'all:' target anywhere in the file and you could compile
all of your targets by typing:
> make all
but having it as the first target makes a certain amount of
sense. Another option is to create a 'default:' target at
the top of the file which you could leave empty if you
wanted the default action to do nothing.

<--- in the file


default:
...
<--- end of file

In this case:
> make
would do nothing.
5

Chapter 2 - Syntax so far:


Up to now, we've done some things which may not yet be
clear; so let's try to fix that by defining some basic
syntax of a Makefile.
A 'target' is the word listed before the colon. For
instance, the statement 'project1:' is defining a target
called 'project1'. As we've seen, you can call a target
using:
> make target
which will run the compile command the target specifies.

In a number of cases, there will be a list of files to the


right of the target declaration.

<--- in the file


project1: myfile1.c myfile1.h myfile2.h
<--- end of file

This is the list of files the target needs - its


dependencies. In the case of 'project1', we need the file
'myfile1.c' to compile. Note the space between the target
and the dependencies. I think it is a good idea to put a
'tab' character in for that space. This is a good rule of
thumb to use because the very next line MUST be indented
with a 'tab' character.
This next line is the actual command that gets run.

<--- in the file


project1: myfile1.c
gcc -o project1 myfile1.c
<--- end of file

Something to remember here is that if 'myfile1.c' includes


another file, that file will be compiled automatically
using the above statement. However, you can use the list of
dependencies as a guide to help you remember what files are
needed for what, so adding those files to the list is a
good thing.

<--- in the file


project1: myfile1.c extra.h myfile2.c
gcc -o project1 myfile1.c
<--- end of file
6

Assuming 'myfile1.c' includes both 'extra.h' and


'myfile2.c', the above lines will do the same thing as the
previous incarnation but gives you more information about
the structure of the project.

Comments can be added to a Makefile by using the '#'


character. For example:

<--- in the file


# this is a comment
<--- end of file
7

Chapter 3 - Making Makefiles Modular:


Here's where things start to get more complicated. I'm
going to introduce a bunch of short-cuts to let you make
more modular Makefiles. By this I mean that you should be
able to create Makefiles that can be copied and pasted into
different projects and be able to modify them quickly and
easily without having to dig through the actual guts of the
Makefile.

The first of these short-cuts is the macro. If you can


program enough code that you would even consider needing a
Makefile, you've probably run across constants before.
Macros in a Makefile are basically the same thing as
constants but with slightly different syntax. Once a macro
has been declared, you can use if whereever you could
normally use the value it contains.
For example:

<--- in the file


# the constant
CC=gcc # this defines a macro CC which stands for the
# compiler we will be using for our code.
project1: myfile1.c
$(CC) -o project1 myfile1.c
<--- end of file
8

Notice how the macro is declared and then called. To


declare a macro, simple write 'macroname=value'. Most
people seem to leave out white space, but it will be just
as correct to leave spaces in (ie, 'macroname = value').
Here's another example:

<--- in the file


CC=gcc
# use the macro to name a target
EXE=project1
# then use that macro to add the target to a list of
# targets
TARGETS=$(EXE) project2

# let's use the macro to declare the target now


$(EXE): myfile1.c
$(CC) -o myfile1.c

...

# let's use our TARGETS macro to list all of the possible


# executable files we may have created.
# this will delete 'project1' and 'project2'
clean:
rm -f *.o $(TARGETS)
<--- end of file
9

Another example of a macro would be to create one called


'CFLAGS', or some such, and use it to define compiler flags
you call quite often. For instance, your executables may
always need the '-Wall' flag (this just changes setting
relating to warning messages):

<--- in the file


CC=gcc
CFLAGS=-Wall

project1: myfile1.c
$(CC) $(CFLAGS) project1 myfile1.c
<--- end of file

On to the next thing. There are a number of built in macros


which can simplify the copy-and-paste method of making a
Makefile:
$@ -> this will copy the current target name.
$< -> this will copy the FIRST file name in the
dependency list.
$^ -> this will copy ALL of the file names in the
dependency list.
There are also other built-in macros, but these are the
three I actually find useful at this level. Here's an
example:

<--- in the file


CC=gcc

# example 1:
project1: myfile1.c extra.h
$(CC) -o project1 myfile1.c
# same as
project1: myfile1.c extra.h
$(CC) -o $@ $^
# or
project1: myfile1.c extra.h
$(CC) -o $@ $<
10

# example 2:
# this will not work. It will give a whole bunch of errors
# since it tries to compile both files and myfile1.c
already
# includes myfile2.c. We effectively get a redefinition of
# everything in myfile2.c. Note: it is possible to write
# your code so this error won't actually happen and this
# line will work.
project1: myfile1.c myfile2.c
$(CC) -o $@ $^
# this will work. It only takes the first file and compiles
# it, linking in the other file automatically as
appropriate.
project1: myfile1.c myfile2.c
$(CC) -o $@ $<
<--- end of file
11

Chapter 4 - Multi-file projects:


Now that we have a grasp of all kinds of different crazy
things to help make our Makefile more useful and reusable,
we need to go that extra step and talk about multi-file
projects. By this, I mean projects that include a number of
different '.c' and '.h' files. We've already seen a couple
of examples in the previous chapters that included multiple
files and worked just fine; however, to continue on the
thought of making these Makefiles more modular, we're going
to look at a more stuctured method.
Let's look at a more complex project:
->main.c // includes queue.h
->list.h
->list.c // includes list.h
->queue.h // includes list.h
->queue.c // includes queue.h
The simple approach to compiling this:

<--- in the file


CC=gcc

project: main.c queue.h queue.c list.h list.c


$(CC) -o $@ $^
<--- end of file

This will work just fine, but what an evil list of file
dependencies. Let's try something different:

<--- in the file


CC=gcc
project: main.o queue.o list.o
$(CC) -o $@ $^

main.o: main.c queue.h


$(CC) -c $^

queue.o: queue.h queue.c list.h


$(CC) -c $^

list.o: list.h list.c


$(CC) -c $^
<--- end of file

This style of Makefile shows the structure of the code much


better than the previous way. You can see that the list
code and queue code are strictly separate from the main
12

program, but you can also see where one section of code is
dependant on another. If you wanted, you could remove the
'queue.h' from 'main.o' and 'list.h' from 'queue.o' and it
would make no difference; however it would not be as clear
the link between each code set.

So what exactly is going on? How does this work? What are
those '.o' files? Basically, we've split the program up
into a bunch of pieces called object files (the '.o'
files). These files make up the entirety of the program but
they don't actually do anything on their own because they
are not complete programs. They depend on the other files
to work. So now what we have to do is link them together.
That's what happens when we make the 'project' target.
When you make the 'project' it runs make on each of the
'.o' file targets. If those '.o' files don't exist or are
not up-to-date, they are recompiled and then linked
together as a final step.
13

Chapter 5 - Some more tid-bits:


You should now have enough knowledge to pump out Makefiles
for your standard, every day project. This chapter is
dedicated to stuff that you may or may not find useful.
These are things that I thought are pretty nifty.

Splitting long lines up


If you happen to have a line of text in your Makefile that
is way to long to fit the average line width without
scrolling, use the backslash character to split the line.
This tells the make program that the line is continued on
the next line.
Example:

<--- in the file


...
bigproject: main.o queue.o stack.o list.o recursive.o \
fileio.o database.o network.o
...
<--- end of file

Multi-folder projects
Let's say you have a big project and you want to keep your
headers in one folder (/headers) and your source (/source)
in another; and maybe your object files (the '.o's) should
be in their own folder as well (/obj). No problem. Just
make sure your includes are correct in your code and add
another macro:

<--- in the file


HDIR=headers
SDIR=source
ODIR=obj

project: $(ODIR)/main.o $(ODIR)/queue.o $(ODIR)/list.o


$(CC) -o $@ $^

$(ODIR)/main.o: $(SDIR)/main.c $(HDIR)/queue.h


$(CC) -c $^

$(ODIR)/queue.o: $(HDIR)/queue.h $(SDIR)/queue.c \


$(HDIR)/list.h
$(CC) -c $^
14

$(ODIR)/list.o: $(HDIR)/list.h $(SDIR)/list.c


$(CC) -c $^
<--- end of file

Condensing your targets


You may find all those '.o' targets really annoying. We can
get rid of them, but we'll lose a lot of readability. You
can do this using the '%' character as below:

<--- in the file


project: main.o queue.o list.o
$(CC) -o $@ $^

%.o: %.c
$(CC) -c $^
<--- end of file

What this does is say that every .o file is compiled from a


.c file of the same name.

The COMPILEOBJ macro


I thought this little macro would be handy for replacing
all of those nasty strings of characters you get with every
object target. Mind you, this isn't really as useful if
you're using the previous tid-bit; however, it does still
move all of the things you're likely to change to the top
of the file. This is a good technique since you don't want
to go digging through the file every time you make a
change. You can make a COMPILEEXE macro in the same manner:

<--- in the file


CC=gcc
COMPILEOBJ=$(CC) -c $^
CREATEEXE=$(CC) -o $@ $^

project: main.o queue.o list.o


$(CREATEEXE)

%.o: %.c
$(CREATEOBJ)
<--- end of file
15

The OBJS macro


This one is probably pretty obvious, but I'll mention it
anyway. Rather than writing all of the object files
directly in the main project's dependency list, build
another macro at the top of the file:

<--- in the file


CC=gcc
COMPILEOBJ=$(CC) -c $^
CREATEEXE=$(CC) -o $@ $^
OBJS=main.o queue.o list.o
project: $(OBJS)
$(CREATEEXE)

%.o: %.c
$(CREATEOBJ)
<--- end of file

LFLAGS vs CFLAGS
We've seen the CFLAGS macro used before to define the flags
used in relation to the compiler. But if you've noticed, we
use different flags to compile the object files than we do
to link them together. You may also want to consider a
DEBUG macro that contains flags relating to any debugging
you are doing:

<--- in the file


CC=gcc
DEBUG=-g # as an example. You can look up what this does
CFLAGS=-Wall -c $(DEBUG)
LFLAGS=-Wall $(DEBUG) -o # the -o doesn't actually do
# anything
# other than rename the executable
# and could be left out of LFLAGS but
# I've added it in to illustrate my
# point.
COMPILEOBJ=$(CC) $(CFLAGS) $^
CREATEEXE=$(CC) $(LFLAGS) $@ $^
OBJS=main.o queue.o list.o
project: $(OBJS)
$(CREATEEXE)

%.o: %.c
$(CREATEOBJ)
<--- end of file
16

Conclusion:
So by now you should have a fair understanding of how a
Makefile works. I've tried to simplify things and give lots
of examples while still giving you a lot to work with. I
hope I've succeeded, but if I haven't then below are a few
websites which I used as references. Also, a websearch for
"Makefile Tutorial" will turn up lots of helpful
information.

1. http://palantir.swarthmore.edu/maxwell/classes/tutorials/maketutor/
2. http://www.hsrl.rutgers.edu/ug/make_help.html
3. http://www.cs.umd.edu/class/spring2002/cmsc214/Tutorial/makefile.html

You might also like