Make, Makefiles, and Autoconf

Make and Makefiles

Ok, the first thing to remember with make, as with all things in computers, is Don't Panic1. It's not really hard, just unfamiliar. Soon enough it will be easy and natural. Just be patient.

The second thing is that this tutorial assumes that you know how to compile programs on your own. If you don't, you should learn how to compile programs first, then come back here and learn how to do it automatically.

Make is a system designed to create programs from large source code trees and to maximize the efficiency of doing so. To that effect, make uses a file in each directory called a Makefile. This file contains instructions for make on how to build your program and when.

There are three important parts to a Makefile: the target, the dependencies, and the instructions. Just so that you know what this will look like, it's of the form:
target: dependencies
        instructions
For example:

how_are_you_doing_world: whats_up.o conversation.o display.o
        gcc -o how_are_you_doing_world whats_up.o conversation.o display.o

whats_up.o: main.h structs.h whats_up.c
        gcc -c whats_up.c

conversation.o: main.h structs.h conversation.h conversation.c
        gcc -c conversation.c

display.o: main.h structs.h typedefs.h display.h display.c
        gcc -c display.c
    

Now, for what these things mean:

target
This is a file to be built. It can be an executable file, or an object file, or really anything that you want. It's the part that looks like:
my_program: my_program.o my_program1.o etc.
gcc -o my_program my_program.o my_program1.o etc.
dependencies
These are the files that are used to create the target. For example, if we want hello_world.o, it will probably depend on the file hello_world.c. Thus we would list hello_world.c as a dependency for hello_world.o. This is the part that looks like:
my_program: my_program.o my_program1.o etc.
gcc -o my_program my_program.o my_program1.o etc.
instructions
These are the commands (just as you would enter them at the command prompt) to build the target from the dependencies. They can be as simple or as complex as you like. To continue with our previous example, if we want to get from hello_world.c to hello_world.o we could use these instruction lines:
gcc -c hello_world.c
In general, it's the part that looks like: my_program: my_program.o my_program1.o etc.
gcc -o my_program my_program.o my_program1.o etc.

That's not too bad so far, is it? We have files that we want, files to build those files from, and instructions for how to do it. So far it's just like a batch file or shell script.

Things are a little more complicated than this, though. Don't worry, it's useful complication. It will save you work and time and trouble in the end.

The main power of a makefile is that it doesn't just go about executing all of the build instructions for a specific target, it only executes the ones that it needs too. Trust me, when you have three dozen source files for your program and you make a change to just one of them, it saves a lot of time to just recompile that file and then link the whole program instead of compiling the whole project again.

Not surprisingly, make figures out what needs to be compiled based on the modification times of targets and their dependencies. If the target is newer than its dependencies, then it is up to date and nothing needs to be done for it. This is natural, if your hello_world.o file is newer than your hello_world.c file, it means that the hello_world.c file hasn't been changed since the hello_world.o was created, and thus you don't need to rebuild the hello_world.o file again, it would be identical to the one on your hard drive. Why waste time recreating what you already have?

As you can guess, if the dependencies are newer than the target, that means that the target needs to be rebuild. It is said to be out of date. This makes sense too. Let's say that you built the hello_world.o file, then made a change to the source file. Now hello_world.c is newer than hello_world.o, and if you build hello_world.o, it would be different than the one that you have on your hard drive. So you need to rebuild it.

When a target needs to be rebuilt, make executes all of the lines that make up the instructions.

There's nothing really strange about any of this, is there? It's pretty logical, when you think about it. You want to save work by only building files which are out of date. If you already have an up-to-date copy of some components of your program, why rebuild them? To do that, you tell make what files depend on which, and then make checks the dates of all of them and only builds those files which are out of date.

There are a few things to know about this process:

Now for some examples:

# This is the Makefile for my alarm clock
# It's not very complex.
alarmclock: alarm.o display.o snooze.o
        gcc -o alarmclock alarm.o display.o snooze.o

alarm.o: alarm.c alarm.h main.h
        gcc -c alarm.c

display.o: display.c display.h main.h
        gcc -c display.c

snooze.o: snooze.c snooze.h main.h
        gcc -c snooze.c

clean:
        rm -f *.o
    

Our program here is an alarm clock, creatively named alarmclock. There are three source files which make it up: alarm.c display.c and snooze.c. There are also four header files which make it up: alarm.h display.h snooze.h main.h.

The easiest way to figure out this Makefile is to start from the beginning. The first target is our program, alarmclock. It is built by linking the three object files alarm.o display.o and snooze.o. You can tell this because they are the dependencies of the target alarmclock. Finally, alarmclock will be built by executing the command

gcc -o alarmclock alarm.o display.o snooze.o
    

You can do the same thing for each of the dependencies of alarmclock. For example, there is the target alarm.o. It is built from the files alarm.c, alarm.h, and main.h. You can tell this because they are the dependencies of alarm.o. If any of those files have a newer modification date than the file alarm.o then the command

gcc -c alarm.c
    
will be executated to create the file alarm.o.

And so on. Please note that make simply follows a set of rules so that if you want to be clever and can figure out a way to go outside of them to get your work done more easily, make will not stop you. Consequently, it won't stop you from making mistakes, either. This is sort of like Unix in general. Unix assumes that you know what you're doing, so it doesn't get in your way from doing what you tell it to. This can work out very well when you are trying to do something which Unix was never meant to be able to do. Unix will let you do it. The downside to this, of course, is that if you don't know what you're doing Unix won't try to stop you. The price of freedom is that you have to know what you're doing. Freedom always means more work, but once you get used to being free, I think that you'll like it too. And of course, there's always autoconf. :-)

Autoconf

Autoconf as a general term is a collection of utilites which will allow you to make Makefiles that are very portable and flexible with a minimum of work. As a side effect, it usually makes creating Makefiles much easier, too.

In its simplest form, Autoconf involves writing two files and invoking a bunch of utilities to create a whole bunch more. The files that you will be responsible for creating are Makefile.am and configure.in. Note that the case is important.

Makefile.am

This file is going to be pretty small, generally speaking. It will usually take the form:
bin_PROGRAMS=gclock
gclock_SOURCES= gclock.c
EXTRA_DIST = README INSTALL AUTHORS COPYING NEWS ChangeLog
    

configure.in

AC_INIT(gclock.c)
AM_INIT_AUTOMAKE(gclock,0.1.0)
AC_PROG_CC
AC_PROG_INSTALL
AC_HEADER_STDC
AM_PATH_GTK(1.0.1)
CFLAGS="$CFLAGS $GTK_CFLAGS"
CPPFLAGS="$CPPFLAGS $GTK_CFLAGS"
LDFLAGS="$LDFLAGS $GTK_LIBS"
AC_OUTPUT(Makefile)
    

1. With Apologies to the Hitchikers Guide to the Galaxy.
Futher reading:
Last modified: Thu Sep 23 15:23:31 EDT 1999