make: Keeps a Set of Programs CurrentTip: This section covers the GNU make program This section describes the GNU make program that is distributed with Mac OS X. 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) and Xcode. 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 12-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 12-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 479 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 12-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 12-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 12-3. The executable file form depends on two object files, and each of the object files depends on its respective source file 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 point 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: $ make 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 were more recently modified than the target: $ make form.h Implied DependenciesYou 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 filename 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 12-1 lists some filename extensions that make recognizes and the type of file that corresponds to each filename extension.
C and C++ are traditional programming languages that are available with Mac OS X. 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 that 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 *~ 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. Below 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 alex 311 Jun 21 15:56 makefile -rw-rw---- 1 alex alex 354 Jun 21 16:02 calc.o -rwxrwx--- 1 alex alex 6337 Jun 21 16:04 compute -rw-rw---- 1 alex alex 49 Jun 21 16:04 compute.h -rw-rw---- 1 alex alex 880 Jun 21 16:04 compute.o -rw-rw---- 1 alex alex 780 Jun 21 18:20 compute.c -rw-rw---- 1 alex alex 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. touch The next example uses touch (page 877) 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 n If you want to see what make would do if you ran it, run make with the n (no execute) option. This option displays the commands that make would execute but it does not execute them. t 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 *.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. j 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 compilations. 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 occupied by the extra files. 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 and all files with filenames that end with a tilde (~): $ make clean rm *.o *~ See page 786 for an example of a more complex makefile.
|