An Introduction to Makefiles

What the heck is a Makefile? (Isn't that two separate words?)

Well, I'm glad you asked. No makefile is one word. Don't ask just is.

Makefiles are used to make compilation and linking easier for you, the programmer. Typing:

gcc -g -o myprogram myprogram.s iofunc.o

can get a little tedious. Now imagine if your program consisted of 2 files, now 10 files, now 100 files! Compiling would be unbearable! Lucky for us, there are makefiles!

Makefile Basics
A makefile is a simple file which is understood by a program called 'make' (and for the GNU lovers out there, there's gmake). A makefile is ALWAYS named "Makefile" (remember, Unix is case sensitive, so your makefile needs to start with a capital M).

To create your first makefile, type the following:
touch Makefile
emacs Makefile

If everything worked, that should have created for you an empty file called Makefile, and opened it up in emacs. Now we need to enter something into the body of the makefile. Makefiles can have variables contained within them. Common variables include:
CC - The compiler to use
CFLAGS - The compiler flags
EXEC - The name of the executable to create

So, let's make some variables. Type the following into the makefile (in the emacs window we just opened):
(Add a blank line in here)

Okay, now we need to tell make what to do.

Targets, Dependencies, and Commands

Now that we have a few variables, we need to tell Make what to do when it runs our makefile. We'll need to make a Target. A target is what we're trying to build or compile. So for assignment 3, we might call it asn3.out. Following a target are the dependencies, these are the files that we need to build the target with. Make will go out and see if the dependencies are newer than the target, if they are, it will know to rebuild the target. A sample target line would look like this:
asn3.out: asn3.m iofunc.o stringio.s macrodefs.m

There you can see the target is asn3.out (and notice the colon that separates the target from the dependencies). The dependencies are asn3.m, iofunc.o, stringio.s and macrodefs.m.

Now we have to tell make how to rebuild that darned target. Make will simply execute the following lines (which MUST be indented with a tab) to build the file. So...what do we do? Add those lines.
m4 asn3.m > asn3.s
gcc -o asn3.out asn3.s iofunc.o stringio_libs.c
(Don't forget to start each line with a tab!)

Note: You need to replace the word 'TAB' with a real TAB character...made by pressing the TAB key.

Notice that macrodefs.m is in the dependency list even though it doesn't appear in the commands. You'd want to recompile if you changed macrodefs.m, this is why it appears here.

But Dan, we never used those variables we made!

Oh yeah, good point. Okay, let's beef up our makefile a bit and use those variables. Change the line:

gcc -o asn3.out asn3.s iofunc.o stringio_libs.c

$(CC) $(CFLAGS) -o $(EXEC) asn3.s iofunc.o stringio_libs.c

Notice that when I reference the variables, I use $(variable name). This notation (dollar sign and brackets) simply tells make that I want it to use the appropriate variable there. We can also use the $(EXEC) variable in as the target name. (I'll leave that up to you to change.)

Running Make and Common Targets

To run your makefile, simply type "make" or "make target" at the shell prompt in the directory where your makefile is. If you don't specify a target, make will build the first target it finds. This first target should always be your executable, this way, everyone will know how to build your program!

There are a few targets that are usually expected, they are:
and sometimes test...but we won't worry about that one today.

Clean and realclean simply remove your intermediate files (for our class, that would be the .s file, since we rebuild it from the .m each time). 'realclean' generally removes temporary files (usually ending with ~) and .o files...but in our case, we don't want to remove the .o files because we need them to build our programs! Don't worry about realclean. Let's make the clean target:

(blank line between the last command line of the previous target and this new target)
    rm -f asn3.s
    rm -f $(EXEC)

The final product

If we did everything right, we should end up with a file that looks like this:
# A sample makefile for CS208
# By Dan Santoni


$(EXEC): asn3.m iofunc.o stringio.s macrodefs.m
    m4 asn3.m > asn3.s
    $(CC) $(CFLAGS) -o $(EXEC) asn3.s iofunc.o stringio_libs.c

    rm -f asn3.s
    rm -f $(EXEC)

Common Problems:

If you get '*** missing separator' you probably used spaces instead of tabs.

If you get '*** No rule to make target `foo', needed by `find'. Stop.' then you have a dependency in your makefile that doesn't exist. Check for type-os and make sure all the dependency files are in your assignment directory.

By Dan Santoni
with notes by Maia Hoeberechts