Noeud:Make Rules, Noeud « Next »:, Noeud « Up »:The Makefile



Make Rules

In order for Make to help with your project, you must tell it about the relationships between all of the files it will be maintaining for you. Each relationship is expressed as a dependency rule and takes the following form:

     target: dependency ...
             command
             ...
     
     Example 5.3: Format of a dependency rule
     

Here, target relates to a file that needs to be built during the compilation phase of your project, for example hello.o. A space delimited list of dependency files name every other file that needs to be up to date before target can be built. And an indented list of command each comprise a line of shell script code for Make to run when target needs refreshing. You might say that Make uses command to build target from dependency.

NOTE
Make will understand that each command is part of the preceding rule only if the first character on each line is a literal tab. Although a string of spaces, or even a space followed by a tab look the same in most editors, they will cause an error like this when you try to run make:
               $ make
               Makefile:159: *** missing separator.  Stop.
               

Notice that GNU make tells you the name of and the line number within it that caused the error (line 159 of Makefile in this case), making it easy to locate and correct once you know what the cryptic error message means.


Recall that in the last chapter you saw that the following command will build the m4 binary:

     $ gcc -o m4 main.c freeze.c stackovf.c ../libs/libm4.a
     

The simplest rule to achieve the same effect using a Makefile is as follows:

     m4: main.c freeze.c stackovf.c ../libs/libm4.a
             gcc -o m4 main.c freeze.c stackovf.c ../libs/libm4.a
     
     Example 5.4: A sample dependency rule
     

With just this one rule in the Makefile, recompiling m4 is a simple matter of entering the command make m4 at your shell prompt:

     $ make m4
     gcc -o m4 main.c freeze.c stackovf.c ../libs/libm4.a
     $ make m4
     make: `m4' is up to date.
     

On the second invocation, make doesn't build m4 again, because it is already newer than all of the dependencies listed in the rule.

Anyway, as we explained in A Refreshing Change, this isn't a good way to structure a project Makefile, since absolutely everything is recompiled each time any file is changed. You can avoid such needless recompilations, and drastically cut down on the amount of work GCC has to do when recompiling after changing some source files, by breaking the compilation down into phases. Recall that source code can be compiled into object code files, which are in turn linked to form the target executable (voir The GNU Compiler Collection). Each stage is represented by its own rule in your Makefile, like this:

     m4: main.o freeze.o stackovf.o ../lib/libm4.a
             gcc -o m4 main.o freeze.o stackovf.o ../lib/libm4.a
     
     main.o: main.c
             gcc -c main.c
     
     freeze.o: freeze.c
             gcc -c freeze.c
     
     stackovf.o: stackovf.c
             gcc -c stackovf.c
     
     Example 5.5: Compilation in phases with make
     

Now, with this Makefile in place, imagine that freeze.c has been edited since the compilation we just did with example 5.4. Invoking make m4 now executes only the commands from the set of rules needed to refresh the m4 target file:

     $ make m4
     gcc -c freeze.c
     gcc -o m4 main.o freeze.o stackovf.o ../lib/libm4.a
     

Previously, gcc had needed to recompile all three source files, even if only freeze.c had actally changed. This can save literally hours when recompiling projects spread across a great many source files.

A typical Makefile will specify many rules. Together they can be represented as a tree of dependencies, where many of the dependencies for a particular target are themselves targets in another rule, often with more dependencies of their own. The root of this tree is the target that Make ultimately aims to bring up to date, and is normally specified on the command line. We used make m4 above, which caused Make to start building the tree with the m4 rule at its root, aiming to bring m4 up to date.

                            ,----.
                            | m4 |
                            `----'
                               |
           ,-------------+-----+------+--------------.
          /             /              \              \
         v             v                v              v
     ,--------.   ,----------.   ,------------.   ,---------.
     | main.o |   | freeze.o |   | stackovf.o |   | libm4.a |
     `--------'   `----------'   `------------'   `---------'
         |             |                |
         v             v                v
     ,--------.   ,----------.   ,------------.
     | main.c |   | freeze.c |   | stackovf.c |
     `--------'   `----------'   `------------'
     
     Example 5.6: Part of a Makefile dependency tree
     

If make is invoked without any arguments, then the target of the first dependency rule in the Makefile is refreshed by default. To take advantage of this behaviour, it is common practice to name the first rule all, and set it up to be dependent on all of the programs and libraries compiled by this Makefile. By doing this, invoking make all or even just make will refresh all of the targets managed by this Makefile. For example, in the directory where all of a project's plugin modules are compiled, the Makefile would contain an all target that depends on all of the plugin targets that are built by that Makefile:

     all: gnu.so load.so m4.so traditional.so perl.so stdlib.so
     
     gnu.so: $(gnu_so_OBJECTS)
             $(LD) $(LDFLAGS) -o gnu.so $(gnu_so_OBJECTS) $(gnu_so_LDADD)
     load.so: $(load_so_OBJECTS)
             ...
     
     Example 5.7: A typical all target Makefile fragment
     

Make can determine whether any target in its rules has gone out of date from the modification times of the files referred to in the target and dependency parts of all of its rules. Make first checks that each of the dependency files listed against the target do not have later modification times than the target itself. If any of them do, then Make executes the list of command lines associated with the out of date target. However, the timestamp checks are performed recursively, so before it can determine whether the current target is older than any of its listed dependency files, it must first recursively ensure that each of those files is up to date with respect to their own dependency lists.

If you look at the commands in each rule from example 5.5, you will see that each command is written so that it builds a new target file from those files listed as its dependencies. Whenever target names a file, this statement is always true: command transforms dependency into target. If you bear this in mind as you create new rules, then choosing appropriate file names and commands becomes quite straight forward.