CS2301
Lab 5

This week's lab introduces an important Unix tool: the make facility. This facility allows you to easily maintain and keep your files up to date when you are working on a project that is split across several .c files (and their associated header files). You will find your multi-file programs easier to manage if you keep all of the files for one project in a directory, and create a makefile, as described below, to manage the compilation of the files.

Preliminaries

Create a directory to hold the files for Lab 5, and change to this directory now. Copy the four files for Lab 5 (makefile, intarray.h, intarray.c, sinewave.c) to your directory using the following command (notice there is a space and a period at the end of the command):
      cp /cs/cs2301-b07/labs/lab5/* .
If you now list the files in your directory, you should see entries for intarray.c, intarray.h, sinewave.c, and makefile.

What's in a Makefile?

The file makefile is the usual input file for a tool called make. The purpose of make is to help you maintain and update a collection of related program files. The collection usually has header files (.h files), source files (.c files), and compiled files (.o files) - all of which depend on each other. For example, consider the sinewave program for this week's lab. The final product is an executable file named sinewave, which is created by linking together two other compiled files, sinewave.o and intarray.o. The two compiled files are created by compiling sinewave.c and intarray.c (both of which use the header file intarray.h). So, one of the dependencies that needs to be expressed is:

if sinewave.c or intarray.h changes, then sinewave.o must be regenerated by giving the compiler command:

gcc -Wall -c -g sinewave.c

This requirement to sometimes regenerate sinewave.o is one of the dependencies that the example makefile expresses. To see this dependency, use emacs to open up the file named makefile.

Near the bottom of the file you'll find these two lines:

sinewave.o: intarray.h sinewave.c
        gcc -Wall -c -g sinewave.c

The first line is called a target line, which begins with a file name and a colon. After the colon is a list of more file names. Here's how to interpret the line: The file before the colon (called the target file) depends on the other files (after the colon). Whenever one of the files after the colon changes, the make tool knows that the target file needs to be recompiled. After the target line, there is a series of commands that tell exactly how to regenerate the target file. For the case of sinewave.o, we only need the one gcc command to regenerate the file. (Notice that we included the -c flag to indicate that we should only compile and not create an executable file yet. We also included the -g flag in case we want to use the debugger.)

There is one other peculiar requirement: The command lines (such as the gcc command) must each begin with a tab (not with 8 spaces!).

As a second example of a dependency, the executable file sinewave is created by compiling together the object files intarray.o and sinewave.o. If either of these two object files should change, then sinewave also needs to be recreated. Here is the appropriate target line and command from our makefile:

sinewave: intarray.o sinewave.o
        gcc -Wall -g sinewave.o intarray.o -o sinewave

This target line says that if sinewave.o or intarray.o should happen to change, then the sinewave program must be regenerated with the gcc command that is shown.

Using Make to Regenerate a Specified Target File

There are two simple ways to use the make facility to automatically regenerate your files. The first approach regenerates a specific file. For example, suppose you want to regenerate intarray.o. Then you can use the make command shown here (type the command in now):

make intarray.o

The make command will find the dependency information in the makefile file. It sees that intarray.o depends on other files, so it will first ensure that those files are present (and regenerate them if necessary). In this example, the two files intarray.h and intarray.c are necessary for generating intarray.o. These two files are present, so the make command proceeds to generate intarray.o, using the gcc command that is specified in the makefile. When the gcc command is executed, it is displayed on the screen, so you will see this appear on the screen:

gcc -Wall -c -g intarray.c

After this command finishes, you should list the files in your directory, where you will find the object file intarray.o is now present.

Using Make without Specifying a Target File

You may also use the make command without specifying a file, like this:

make

Without a specified file, the make command will regenerate the first target that it finds in makefile. Try this now, and you will see that the executable file sinewave is regenerated, since sinewave is the first target file in makefile. During the process of regenerating the sinewave file, the make command had to carry out several steps. In the first step, the make command realizes that sinewave depends on intarray.o and also on sinewave.o. But the file sinewave.o is not present. So, the make command first regenerates sinewave.o, and then it can proceed to regenerate the executable file sinewave. On the screen you'll see the two steps displayed:

      gcc -Wall -c -g sinewave.c
      gcc -Wall -g sinewave.o intarray.o -o sinewave

Using Make from within Emacs

The best feature of make is how it automatically keeps track of exactly which object files and executable files need to be recompiled. As an example, you should now change one of the source files. I suggest that you use emacs to make a small change to the sinewave.c program, perhaps adding another small output statement. Then save the new sinewave.c and from within emacs give the compile command (ALT-x compile <ENTER>).

What happens? The emacs compile command automatically issues the "make -k" command (the -k tells make to continue as much as possible after an error). The make program realizes that sinewave.c has changed, and therefore the object file sinewave.o is regenerated with the command:

gcc -Wall -c -g sinewave.c

Next, the make program realizes that sinewave.o has just changed, and therefore the executable file sinewave is regenerated with:

gcc -Wall -g sinewave.o intarray.o -o sinewave

But, notice that the object file intarray.o was not recompiled. The dependencies in the makefile were sufficient to show that intarray.o did not need recreation.

Get Credit for this Lab

Sign the attendance sheet if you haven't already done so.

There's nothing to turn in this week...your attendance counts as your credit.

See you next week.