Introduction

Once programs reach a sufficient size it is common practice to divide the source code into different files. While this makes further editing of the code much easier, it can make compilation more difficult in two ways. Firstly, the actual commands required to compile become more complicated. Even if a single command is used, it must take all of the source files as arguments, and this becomes terrible if the files are spread across different directories. Secondly, and a larger annoyance, is that if one source file is altered, everything would probably be recompiled to make sure the change took effect throughout the whole program, and this makes program maintenance very slow.

Makefiles and the program make provide a solution to this. To handle the first difficulty, the Makefile contains a list of separate activities for building `targets', that can be selected from when running make. This means that if the compilation process is many steps (which it probably will be), then make is able to execute some of those steps but not others. In addition to this, the Makefile contains a list of `dependencies' for each target which is a way of describing the condition under which the target should be rebuilt. If a dependency is a file, then the target will only be rebuilt if it is older then any of its dependencies. This deals with the second difficulty because, assuming all the dependencies are correctly described in the Makefile, changing one source file will only recompile the code that needs to be; any code which does not depend (hence the term dependency) on the source file which was changed will not be recompiled.

Syntax of a Makefile

In the tarfile from the debugging exercise there is a Makefile which serves as a good example. At the top are variable definitions which have the syntax VARIABLE = VALUE. The variable names do not have to be in upper case, but it is good practice to do this. Variables are used with $(VARIABLE), and at such places the value of the variable is substituted in, as you see when you make such a target. The syntax of the target is

targetname: dependency1 dependency2 ...
            command1
            command2
            ...
(the spaces before commands must be tabs). As you can see, building a target may consist of executing more than one command. Sometimes, however, there are no commands, and when you see this you should expect there to be a built in mechanism for producing that particular target. That mechanism will either be in make itself, or specified elsewhere in the Makefile with a `.SUFFIX' directive or a special pattern matching `wildcard' target.

The important dependencies for this Makefile are at the bottom, and after you've looked at them, choose one file to edit which you think will only cause a partial rebuild. Run make to see if you are correct, and repeat this until you are sure you understand how dependencies work.

Using the preprocessor to generate dependencies

Writing the dependencies correctly for very large projects is nearly impossible, but since the dependencies are almost always a result of #include directives it is possible to use the preprocessor to automatically generate them. The `depend' target in the sample Makefile does this, along with the extra step of automatically appending the result to the Makefile itself. If you remove the dependencies below the special "# DO NOT DELETE" line and then run make depend you will see the effect of this target. Automatically modifying files is a risky business, however, and it is much better to do this manually, so as a final exercise try generating the dependencies by calling gcc yourself. Look at the preprocessor options to see the different kinds of dependencies that can be produced.