Introducing Makefiles

A Makefile is a specification of dependencies between files and how to resolve those dependencies such that an overall goal, known as a target, can be reached. Makefiles are processed by the make utility. Other references describe the syntax of Makefiles and the various implementations of make in detail. This chapter provides an overview into Makefiles and gives just enough information to write custom rules in a Makefile.am (see the chapter called Introducing GNU Automake) or Makefile.in.

Targets and dependencies

The make program attempts to bring a target up to date by bring all of the target's dependencies up to date. These dependencies may have further dependencies. Thus, a potentially complex dependency graph forms when processing a typical Makefile. From a simple Makefile that looks like this:
     all: foo
     
     foo: foo.o bar.o baz.o
     
     .c.o:
             $(CC) $(CFLAGS) -c $< -o $@
     
     .l.c:
             $(LEX) $< && mv lex.yy.c $@

We can draw a dependency graph that looks like this:
                    all
                     |
                    foo
                     |
             .-------+-------.
            /        |        \
         foo.o     bar.o     baz.o
           |         |         |
         foo.c     bar.c     baz.c
                               |
                             baz.l

Unless the Makefile contains a directive to make, all targets are assumed to be filename and rules must be written to create these files or somehow bring them up to date.

When leaf nodes are found in the dependency graph, the Makefile must include a set of shell commands to bring the dependent up to date with the dependency. Much to the chagrin of many make users, up to date means the dependent has a more recent timestamp than the target. Moreover, each of these shell commands are run in their own sub-shell and, unless the Makefile instructs make otherwise, each command must exit with an exit code of 0 to indicate success.

Target rules can be written which are executed unconditionally. This is achieved by specifying that the target has no dependents. A simple rule which should be familiar to most users is:
     clean:
     	-rm *.o core