Under UNIX make[Fel79] is the standard utility to build programs. It was designed to automate the building of targets from their source, but as its author, Stuart Feldman, noted, it was never suited to describe large scale software projects. This is arguable because the make language has only one useful statement: the expression of a direct dependence among files with an optional update actions.
If the update action is missing, make only tries to update all sources in the expression. In addition a target can be specified multiply for dependences, but only once with update actions. Describing dependences in a bottom-up fashion to build a system can be done easily by this approach, describing a large system in this way can be a very troublesome and an error-prone job. For small programs make can be a fast and efficient way to generate software. make parses on each invocation the local makefile to get the dependence information. The default goal is the first target found in the description file or the given target on the command line. An example is shown in Figure 1.1.
Abbildung 1.1: Simple UNIX makefile for make
The direct definition of dependences can be clearly seen. The target is separated by a ``:'' from its sources. This simple definition can be used to describe any relationship between files. The update command from the sources to the target can be given explicitly, or, an implicit default update action will be used, e.g., to compile the C source files to object files. Each line in the update command is executed in a sub-shell and the return code is checked for failure. This allows to use any program for target update. To reach complex goals, sub-shells and recursive invocations of make, each time reading the description file again, are required, e.g., to build some targets first in subdirectories of the current working directory. All these dependences across directory boundaries must be hand-coded and may introduce a lot of errors, e.g., misspelled names.
make uses a ``depth first'' approach in checking dependences. That means that the first source of a target is checked recursively and then the next one, until all sources are checked and the target can be updated. Since it is directory oriented in checking dependences, subdirectories must be checked first and recursions are needed for each directory level. This can slow down the checking process significantly if only a few files in different directories need to be updated in order to create a program because the description files are read many times.