|< Day Day Up >|
make: Keeps a Set of Programs Current
tip: This section covers the GNU make program
This section describes the GNU make program. Other make tools (BSN make, GNUStep make, Borland make, and so on) are available as well as similar tools such as ant (the Apache build tool). Makefiles created for GNU make are often incompatible with other make tools, which can be problematic if you are trying to compile code targeted for another platform.
In a large program with many source and header files, the files typically depend on one another in complex ways. When you change a file that other files depend on, you must recompile all dependent files. For example, you might have several source files, all of which use a single header file. When you change the header file, you must recompile each of the source files. The header file might depend on other header files, and so forth. Figure 10-3 shows a simple example of dependency relationships. Each arrow in this figure points from a file to another file that depends on it.
Figure 10-3. Dependency graph for the target form
When you are working on a large program, it can be difficult, time-consuming, and tedious to determine which modules need to be recompiled because of their dependency relationships. The make utility automates this process.
Dependency lines: target files and prerequisite files
At its simplest, make looks at dependency lines in a file named Makefile or makefile in the working directory. The dependency lines indicate relationships among files, specifying a target file that depends on one or more prerequisite files. If you have modified any of the prerequisite files more recently than their target file, make updates the target file based on construction commands that follow the dependency line. The make utility normally stops if it encounters an error during the construction process.
The file containing the updating information for the make utility is called a makefile. (See page 388 for a trivial example.) A simple makefile has the following syntax:
target: prerequisite-list TAB construction-commands
The dependency line consists of the target and the prerequisite-list, separated by a colon. Each construction-commands line (you may have more than one) must start with a TAB and must follow the dependency line. Long lines can be continued with a BACKSLASH ( \) as the last character on the line.
The target is the name of the file that depends on the files in the prerequisite-list. The construction-commands are regular shell commands that construct (usually compile and/or link) the target file. The make utility executes the construction-commands when the modification time of one or more files in the prerequisite-list is more recent than that of the target file.
The following example shows the dependency line and construction commands for the file named form in Figure 10-3. The form file depends on the prerequisites size.o and length.o. An appropriate gcc command constructs the target:
form: size.o length.o TAB gcc -o form size.o length.o
Each of the prerequisites on one dependency line can be a target on another dependency line. For example, both size.o and length.o are targets on other dependency lines. Although the example in Figure 10-3 is simple, the nesting of dependency specifications can create a complex hierarchy that dictates relationships among many files.
The following makefile (named Makefile) corresponds to the complete dependency structure shown in Figure 10-3. The executable file form depends on two object files, and the object files each depend on their respective source files and a header file, form.h. In turn, form.h depends on two other header files.
$ cat Makefile form: size.o length.o gcc -o form size.o length.o size.o: size.c form.h gcc -c size.c length.o: length.c form.h gcc -c length.c form.h: num.h table.h cat num.h table.h > form.h
Although the last line would not normally be seen in a makefile, it illustrates the fact that you can put any shell command on a construction line. Because the shell processes makefiles, the command line should be one that you could enter in response to a shell prompt.
The following command builds the default target form if any of its prerequisites are more recent than their corresponding targets or if any of the targets do not exist:
Thus, if the file form has been deleted, make will rebuild it, regardless of the modification dates of its prerequisite files. The first target in a makefile is the default and is built when you call make without any arguments.
If you want make to rebuild a target other than the first in the makefile, you must provide that target as an argument to make. The following command rebuilds only form.h if it does not exist or if its prerequisites are more recent than the target:
$ make form.h
You can rely on implied dependencies and construction commands to facilitate the job of writing a makefile. For instance, if you do not include a dependency line for an object file, make assumes that it depends on a compiler or assembler source code file. Thus, if a prerequisite for a target file is xxx.o and no dependency line identifies xxx.o as a target, make looks at the extension to determine how to build the .o file. If it finds an appropriate source file, make provides a default construction command line that calls the proper compiler or the assembler to create the object file. Table 10-1 lists some filename extensions that make recognizes and the type of file that corresponds to each suffix.
C and C++ are traditional programming languages that are available with many Linux distributions. The bison and flex tools create command languages.
In the next example a makefile keeps the file named compute up-to-date. The make utility ignores any line that begins with a pound sign (#). Thus the first three lines of the following makefile are comment lines. The first dependency line shows that compute depends on two object files: compute.o and calc.o. The corresponding construction line gives the command make needs to produce compute. The second dependency line shows that compute.o depends not only on its C source file but also on the compute.h header file. The construction line for compute.o uses the C compiler optimizer ( O3 option). The third set of dependency and construction lines is not required. In their absence, make infers that calc.o depends on calc.c and produces the command line needed for the compilation:
$ cat Makefile # # Makefile for compute # compute: compute.o calc.o gcc -o compute compute.o calc.o compute.o: compute.c compute.h gcc -c -O3 compute.c calc.o: calc.c gcc -c calc.c clean: rm *.o *core* *~
There are no prerequisites for clean, the last target. This target is commonly used to get rid of extraneous files that may be out-of-date or no longer needed, such as .o files.
Following are some sample executions of make based on the previous makefile. As the ls command shows, compute.o, calc.o, and compute are not up-to-date. Consequently the make command runs the construction commands that re-create them.
$ ls -ltr total 22 -rw-rw---- 1 alex pubs 311 Jun 21 15:56 makefile -rw-rw---- 1 alex pubs 354 Jun 21 16:02 calc.o -rwxrwx--- 1 alex pubs 6337 Jun 21 16:04 compute -rw-rw---- 1 alex pubs 49 Jun 21 16:04 compute.h -rw-rw---- 1 alex pubs 880 Jun 21 16:04 compute.o -rw-rw---- 1 alex pubs 780 Jun 21 18:20 compute.c -rw-rw---- 1 alex pubs 179 Jun 21 18:20 calc.c $ make gcc -c - O3 compute.c gcc -c calc.c gcc -o compute compute.o calc.o
If you run make once and then run it again without making any changes to the prerequisite files, make indicates that the program is up-to-date and does not execute any commands:
$ make make: 'compute' is up to date.
The next example uses the touch utility to change the modification time of a prerequisite file. This simulation shows what happens when you alter the file. The make utility executes only the commands necessary to bring the out-of-date targets up-to-date:
$ touch calc.c $ make gcc -c calc.c gcc -o compute compute.o calc.o
In the next example, touch changes the modification time of compute.h. The make utility re-creates compute.o because it depends on compute.h and re-creates the executable because it depends on compute.o:
$ touch compute.h $ make gcc -c - O3 compute.c gcc -o compute compute.o calc.o
If you want to see what make would do if you ran it, run make with the n (no execute) option. The n option shows the commands that make would execute but it does not execute them.
As these examples illustrate, touch is useful when you want to fool make either into recompiling programs or into not recompiling them. You can use touch to update the modification times of all source files so that make considers nothing to be up-to-date; make will then recompile everything. Alternatively, you can use touch or the t option to make to touch all relevant files; make then considers everything to be up-to-date. Using touch in this manner is useful if the modification times of files have changed yet the files remain up-to-date (as can happen when you copy a set of files from one directory to another).
The following example uses make n several times to see what make would do if you gave a make command. The first command shows that the target, compute, is up-to-date. Next touch makes the modification dates on all the *.c files more recent than their targets and make n shows what make would do if you called it without the n option. The make t command then brings all the targets up-to-date. The final make n confirms that compute is up-to-date.
$ make -n make: 'compute' is up to date. $ touch *.c $ make -n gcc -c -O3 compute.c gcc -c calc.c gcc -o compute compute.o calc.o $ make -t touch compute.o touch calc.o touch compute $ make -n make: 'compute' is up to date.
The j (jobs) option performs a number of tasks in parallel; the numeric argument to j specifies the number of jobs or processes. Most make tasks hit the disk first and then the CPU, resulting in CPU usage dropping between compiles. On a multiprocessor system, you can reduce CPU usage by using make j n, where n is the number of CPUs plus 1. Running tasks in parallel can significantly reduce the build time for a large project.
Once you are satisfied with the program you have created, you can use the makefile to remove extraneous files. It is helpful to keep intermediate files around while you are writing and debugging a program so that you need to rebuild only the ones that change. When you will not be working on the program for a while, you can release the disk space. Using a clean target in a makefile means that you do not have to remember all the little pieces that can safely be deleted. The next example simply removes all object (.o) files:
$ make clean rm *.o
|< Day Day Up >|