Introduction to Makefiles and Autotools
Version 0.1
7 February 2005
Ikke
2005
Ikke
Published under the "Creative Commons Attribution-ShareAlike License Belgium"
Prerequisites
Before you can get started, there are some thing you need:
The files used in my previous article, "Using Glib Signals with GOB"
A decent editor, I'm addicted to Vim
A GNU Make-compatible make-implementation, like GNU Make itself
If you're willing to play with autotools, you need GNU AutoMake and AutoConf
Writing Makefiles, the manual way
First steps
There are some rules of thumb you can use when writing a Makefile manually:
Find out what programs you need to process your source files
Find out what package you depend on: which headers, which libraries
Make a list of all source files, and find out which one depends on some other file(s)
Let's walk though these step by step:
What programs do we need? We need gob2 of course, to process our gob file. Next to this, we need the stuff you need most of time when creating a Makefile: a compiler and a linker. And guess what, GCC contains both.
What packages do we need? Remember the command line you had to execute when compiling the test-signal executable?gcc -Wall `pkg-config --libs --cflags gobject-2.0 glib-2.0` -o testsignal test-signal-test.c test-signal.cWe don't even need this line to figure out what dependencies we got, actually: we're building a GObject, so we need all libs and headers provided by the gobject package, version 2.x in our case, same thing for glib.
How can we find out where these things are located? Well, the smart people at freedesktop.org made a nifty tool called pkg-config. When a library is installed, it can install a pkg-config resource file, which lists the directories where the library stuff is installed. For some samples of these files, check /usr/lib/pkgconfig. The pkg-config command line utility can parse these files and give you the information you need.
What source files have we got, and which ones depend on each other? We got 2 files, test-signal.gob and test-signal-test.c. In the end we want to generate an executable called test-signal, which need test-signal.c (the GObject implementation) and test-signal-test.c.
We'll work in 2 stages here: first we'll compile all necessary .c files to an object file (.o), then in the end link all object files together in a nice executable.
Here's what should happen: test-signal.gob must be parsed by gob to create test-signal.c and test-signal.h. Then test-signal.c and test-signal-test.c should be compiled, and finally test-signal.o and test-signal-test.o should be linked into test-signal.
Some little intermezzo. You might be asking "If I need to consider all this, what's the use of Makefiles? Can't I just write some Bash script which executes the gcc command like I did before?" Well, no. Make does more than just executing some commands. It also check whether it should do something. Imagine you got 100 source files. You got all of them in one huge GCC command, you execute the command, run the resulting application and find some bug. You fix the bug by editing some lines in a source file, and re-execute your huge command. GCC would recompile all 100 files now, which may take some time. If you use Makefiles, make will find out only one file has been altered since last build, it'll let GCC only recompile that file, then relink all object files together, which will make a huge difference in time. Next to this: using autotools makes everything much simpler ;-)
Writing our Makefile
I always tend to use the same format when writing a Makefile (although I don't write many of the: I use autotools :-)). I start by defining the executables:
GOB2=gob2
CC=gcc
LD=gcc
This is not necessary, but can be useful sometimes. New we can use these variables later on. If we want to change the linker, we only have to edit the Makefile in one place.
Next we define the CFLAGS, the flags given on the compiler line when compiling a source file into an object file:
CFLAGS=`pkg-config --cflags glib-2.0`
CFLAGS+=`pkg-config --cflags gobject-2.0`
CFLAGS+=-Wall -g
As you can see, we request the CFLAGS necessary for glib-2.0 and gobject-2.0 (2.x actually) by querying pkg-config. In the end we add 2 extra compiler flags: -Wall which tells the compiler to show all possible warnings, and -g, which tells GCC to include debugging information. This results in a bigger executable, but is very useful if we want to debug the program using GDB.
Now we define the LDFLAGS, the flags given to the linker:
LDFLAGS=`pkg-config --libs glib-2.0`
LDFLAGS+=`pkg-config --libs gobject-2.0`
This should be fairly self-explaining.
Our project consists of only one executable, so I define an OBJS variable including all the object files necessary to build our executable:
OBJS=test-signal.o test-signal-test.o
Next comes the name of the executable we want to create:
PROG=test-signal
Now the real magic starts. Until now we only defined some variables for later use. We were not forced to do so, but it's more convenient later on. Actually, when building small things, we can just re-use this Makefile, and only change the variable definitions (OBJS, PROG, maybe the CFLAGS and LDFLAGS if necessary).
We need to define some rules, telling make what commands to execute to build files.
%c %h %-private.h: %.gob
$(GOB2) $<
This rules says: "When a foo.c, foo.h or foo-private.h is needed, and does not exist, it should be generated from foo.gob by executing $(GOB2) $<, which expands to gob2 foo.gob. Indeed, % represents "some string", $(GOB2) is the variable we defined at the beginning of the Makefile, and $< gets expanded as the first item on the right of the semicolon (":").
One major thing to notice: Makefiles are sensitive to indenting, with tabs. You may not use spaces. So in the last snippet there's a [TAB] before "$(GOB2)".
This it the general form of a rule:
filenametobuild: dependenciesofthefiletobuild
[TAB]what to do first[ENTER]
[TAB]what to do next if necessary[ENTER] etc.
Now we define how to link our program:
$(PROG): $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) -o $(PROG)
$(PROG) is built out of $(OBJS) by executing $(CC) $(LDFLAGS) $(OBJS) -o $(PROG), expanded here to gcc `pkg-config --libs glib-2.0` `pkg-config --libs gobject-2.0` test-signal.o test-signal-test.o -o test-signal.
We still need to tell make how to create object files out of C source files:
%.o: %.c
$(CC) $(CFLAGS) -c $<
You should be able to figure out what this does by yourself now.
We finish by defining some convenience targets:
all: $(PROG)
default: all
clean:
rm -f $(OBJS) $(PROG)
rm -f test-signal.[ch]
rm -f test-signal-private.h
This allows us to execute make, which will start building the "default" target, or make all which will build the "all" target, or make clean to execute the commands inside the "clean" target. You should notice
A target is not forced to "do" something ("all" and "default"). You can just say "it depends on 'foo' and/or 'bar'", which will be built then.
The dependencies of a target may be empty ("clean").
Let's finish with a listing of the complete Makefile:
GOB2=gob2
CC=gcc
LD=gcc
CFLAGS=`pkg-config --cflags glib-2.0`
CFLAGS+=`pkg-config --cflags gobject-2.0`
CFLAGS+=-Wall -g
LDFLAGS=`pkg-config --libs glib-2.0`
LDFLAGS+=`pkg-config --libs gobject-2.0`
OBJS=test-signal.o test-signal-test.o
PROG=test-signal
%.c %.h %-private.h: %.gob
$(GOB2) $<
$(PROG): $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) -o $(PROG)
%.o: %.c
$(CC) $(CFLAGS) -c $<
all: $(PROG)
default: $(PROG)
clean:
rm -f $(OBJS) $(PROG)
rm -f test-signal.[ch]
rm -f test-signal-private.h
Save this file as "Makefile" in your source directory, and execute make. This should be the result:
gob2 test-signal.gob
gcc `pkg-config --cflags glib-2.0` `pkg-config --cflags gobject-2.0` -Wall -g -c test-signal.c
test-signal.c: In function `test_signal_testsignal':
test-signal.c:141: warning: implicit declaration of function `memset'
gcc `pkg-config --cflags glib-2.0` `pkg-config --cflags gobject-2.0` -Wall -g -c test-signal-test.c
gcc `pkg-config --libs glib-2.0` `pkg-config --libs gobject-2.0` test-signal.o test-signal-test.o -o test-signal
rm test-signal.c
Notice the rm test-signal.c: make removes the files it generates itself, so when you update test-signal.gob, test-signal.c will be recreated too.
Now you should be able to execute ./test-signal.
We got a nice Makefile now, but it took some time and effort to write, isn't it? And we don't have a "normal" FOSS installation system like ./configure, make and make install.
Well, that's what we're going to do now. The coming stuff is much easier than writing Makefiles by hand (you know, FOSS devs are lazy people ;-)), but it is useful to know how Makefiles are formatted and how they work, though.
Time for some really 1337 (sorry, couldn't stop myself) stuff: introducing GNU Autotools.