Section 11.6. Managing Dependencies: make


[Page 411 (continued)]

11.6. Managing Dependencies: make

You've now seen how several independent object modules may be linked into a single executable. You've also seen that the same object module may be linked into several different executables. Although multimodule programs are efficient in terms of reusability and disk space, they must also be carefully maintained. For example, let's assume that we change the source code of "reverse.c" to use pointers instead of array subscripts. This would result in a faster reverse function. In order to update the two main program executables "main1" and "main2" manually, we'd have to perform the following steps, in this order:

1.

Recompile "reverse.c".

2.

Link "reverse.o" and "main1.o" to produce a new version of "main1".

3.

Link "reverse.o" and "main2.o" to produce a new version of "main2".

Similarly, imagine a situation where a #define statement in a header file is changed. All of the source code files that directly or indirectly include the file must be recompiled, and then all of the executable modules that refer to the changed object modules must be relinked.

Although this might not seem like a big deal, imagine a system with a thousand object modules and fifty executable programs. Remembering all of the relationships between the headers, source code files, object modules, and executable files would be a nightmare. Summarily recompiling everything whenever something changed would be time consuming and a waste of resources. One way to avoid these problems is to use the GNU make utility, which allows you to create a makefile that contains a list of all file interdependencies for each executable. Once such a file is created, to re-create the executable is easy; you just use the make command as follows:


[Page 412]

$ make programname 


Figure 11-8 is a synopsis of make.

Figure 11-8. Description of the make command.

Utility: make [ -f makefile ]

make is a utility that updates a file based on a series of dependency rules stored in a special format "make file". The -f option allows you to specify your own make filename; if none is specified, make will look for the files "GNUmakefile," "makefile," and "Makefile," in that order.


11.6.1. Make Files

To use the make utility to maintain an executable file, you must first create a make file. This file contains a list of all the interdependencies that exist between the files that are used to create the executable. A make file may have any name, but it is easiest to set up one make file per directory and name them "Makefile." In the following examples, I will name our make files in the form "module.make" (and use the -f option) so that it is clear what we expect the make command to do.

In its simplest form, a make file contains make rules of the form shown in Figure 11-9.

Figure 11-9. make dependency specification.

targetList:dependencyList

commandList


Here, targetList is a list of target files and dependencyList is a list of files that the files in targetList depend on. commandList is a list of zero or more commands, separated by newlines, that reconstructs the target files from the dependency files. Each line in commandList must start with a tab character. Rules must be separated by at least one blank line.

For example, let's think about the file interdependencies related to the executable file "main1". This file is built out of two object modules: "main1.o" and "reverse.o". If either file is changed, then "main1" may be reconstructed by linking the files using gcc. Therefore, one rule in "main1.make" would be:


[Page 413]

main1:      main1.o reverse.o             gcc main1.o reverse.o -o main1 


This line of reasoning must now be carried forward to the two object files. The file "main1.o" is built from two files, "main1.c" and "reverse.h" (remember that any file that is either directly or indirectly #include'd in a source file is effectively part of that file). If either file is changed, then "main1.o" may be reconstructed by compiling "main1.c". Here, therefore, are the remaining rules in "main1.make":

main1.o:    main1.c reverse.h             gcc -c main1.c reverse.o:  reverse.c reverse.h             gcc -c reverse.c 


11.6.2. The Order of Make Rules

The order of make rules is important. The make utility creates a "tree" of interdependencies by initially examining the first rule. Each target file in the first rule is a root node of a dependency tree, and each file in its dependency list is added as a leaf of each root node. In our example, the initial tree would look as shown in the Figure 11-10.

Figure 11-10. Initial make dependency tree.


The make utility then visits each rule associated with each file in the dependency list and performs the same actions. In our example, the final tree would therefore look as shown in the Figure 11-11.

Figure 11-11. Final make dependency tree.


Finally, the make utility works up the tree from the bottom leaf nodes to the root node, looking to see if the last modification time of each child node is more recent than the last modification time of its immediate parent node. For every case where this is so, the associated parent's rule is executed. If a file is not present, its rule is executed regardless of the last modification times of its children. To illustrate this, I've numbered Figure 11-12 to illustrate the order in which the nodes would be examined.


[Page 414]

Figure 11-12. make ordering.


11.6.3. Running Make

Once a make file has been created, you're ready to run make to re-create the executable file whose dependency information is specified by the makefile.

To show you how this works, I deleted all of the object modules and the executable to force every command list to execute. When I then performed the make, here's what I saw:

$ make -f main1.make     ...make executable up-to-date. gcc -c main1.c gcc -c reverse.c gcc main1.o reverse.o -o main1 $ _ 


Notice that every make rule was executed, in the exact order shown in Figure 11-12.

Since I created a second executable when I made the palindrome program, I also fashioned a second make file, called "main2.make". Here it is:

main2:          main2.o reverse.o palindrome.o                 gcc main2.o reverse.o palindrome.o -o main2 main2.o:        main2.c palindrome.h                 gcc -c main2.c reverse.o:      reverse.c reverse.h                 gcc -c reverse.c palindrome.o:   palindrome.c palindrome.h reverse.h                 gcc -c palindrome.c 


When I performed a make using this file, I saw the following output:

$ make -f main2.make     ...make executable up-to-date. gcc -c main2.c gcc -c palindrome.c gcc main2.o reverse.o palindrome.o -o main2 $ _ 



[Page 415]

Notice that "reverse.c" was not recompiled. This is because the previous make had already created an up-to-date object module, and make only recompiles files when necessary.

11.6.4. Make Rules

The make files that I've shown you so far are larger than they need to be. This is because some of the make rules that I supplied are already known by the make utility in a more general way. For example, note that several of the rules are of the form:

xxx.o:      reverse.c reverse.h             gcc -c xxx.c 


where xxx varies between rules. The make utility contains a predefined rule similar to the following:

.c.o:             gcc -c -O $< 


This cryptic-looking rule tells the make utility how to create an object module from a C source code file. The existence of this general rule allows me to leave off the C recompilation rule. Here, therefore, is a sleeker version of "main2.make":

main2:          main2.o reverse.o palindrome.o                 gcc main2.o reverse.o palindrome.o -o main2 main2.o:        main2.c palindrome.h reverse.o:      reverse.c reverse.h palindrome.o:   palindrome.c palindrome.h reverse.h 


The make utility also includes other inference rules. For example, make knows that the name of an object module and its corresponding source code file are usually related. It uses this information to infer standard dependencies. For example, it deduces that "main2.o" is dependent on "main2.c", and thus you may leave this information off the dependency list. Here is an even sleeker version of "main2.make":

main2:          main2.o reverse.o palindrome.o                 gcc main2.o reverse.o palindrome.o -o main2 main2.o:        palindrome.h reverse.o:      reverse.h palindrome.o:   palindrome.h reverse.h 


11.6.5. Forcing Compilation

To confirm that the new version of the make file worked, I requested a make and obtained the following output:


[Page 416]

$ make -f main2.make 'main2' is up to date. $ _ 


Obviously, since I'd already performed a successful make, another one wasn't going to trigger any rules! To force a make for testing purposes, I used a handy utility called touch, which makes the last modification time of all the named files equal to the current system time. Figure 11-13 is a brief synopsis.

Figure 11-13. Description of the touch command.

Utility: touch -c { fileName }+

touch updates the last modification and access times of the named files to the current time. By default, if a specified file doesn't exist, it is created with zero size. To prevent this, use the -c option.


I "touched" the file "reverse.h", which subsequently caused the recompilation of several source files:

$ touch reverse.h       ...fool make. $ make -f main2.make gcc -c -O reverse.c gcc -c -O palindrome.c gcc main2.o reverse.o palindrome.o -o main2 $ _ 


11.6.6. Macros

The make utility supports primitive macros. If you specify a line of the form shown in Figure 11-14 at the top of a make file, every occurrence of $(token) in the make file is replaced by replacementText.

Figure 11-14. A macro in make.

token = replacementText


In addition to containing rules, the standard rules file contains default definitions of macros such as CFLAGS that are used by some of the built-in rules. For example, the rule that tells the make utility how to update an object file from a C source file looks like this:

.c.o:             gcc -c $(CFLAGS) $< 



[Page 417]

The standard rules file contains a line of the form:

CFLAGS = -O


If you wanted to recompile a suite of programs using the -O2 option of gcc (for a different level of optimization), you would override the default value of CFLAGS at the top of the make file like this:

CFLAGS =         -O2 main2:           main2.o reverse.o palindrome.o                  gcc main2.o reverse.o palindrome.o -o main2 main2.o:         palindrome.h reverse.o:       reverse.h palindrome.o:    palindrome.h reverse.h 


To recompile the suite of programs, I used the touch utility to force recompilation of all the source files:

$ touch *.c       ...force make to recompile everything. $ make -f main2.make gcc -O2 -c main2.c gcc -O2 -c palindrome.c gcc -O2 -c reverse.c gcc main2.o reverse.o palindrome.o -o main2 $ _ 


Notice that the -O2 argument was not included in my gcc command to link everything, because I have that command defined in my make file.

11.6.7. Maintaining an Archive Using Make

Although an archive can be built and maintained from the command line, it's much better to use make. To refer to an object file inside an archive, place the name of the object file inside parentheses, preceded by the name of the archive. The make utility has built-in rules that take care of the archive operations automatically. Here is the updated "main2.make" file that uses archives instead of plain object files:

main2:         main2.o string.a(reverse.o) string.a(palindrome.o)                gcc main2.o string.a -o main2 main2.o:       palindrome.h string.a(reverse.o):    reverse.h string.a(palindrome.o): palindrome.h reverse.h 


Here is the output from a make performed using this file:

$ rm *.o                        ...remove all object modules. $ make -f main2.make            ...perform a make. 
[Page 418]
gcc -c main2.c gcc -c reverse.c ar rv string.a reverse.o ...object module is saved. a - reverse.o ar: creating string.a rm -f reverse.o ...original is removed. gcc -c palindrome.c ar rv string.a palindrome.o a - palindrome.o rm -f palindrome.o gcc main2.o string.a -o main2 ...access archived modules. $ _


Notice that the built-in make rules automatically removed the original object file once it had been copied into the archive.

11.6.8. Other Make Capabilities

As you can see, make is a rather complicated utility. I suggest that you consult the Linux man page and [Oram, 1991] for more details.




Linux for Programmers and Users
Linux for Programmers and Users
ISBN: 0131857487
EAN: 2147483647
Year: 2007
Pages: 339

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net