CS2301
Lab 4 - Using the Linux "make" Facility

This week's lab introduces an important Linux tool: the make facility. This 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 .h 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.

The file makefile is the usual input file for a tool called make, which can be invoked from the command line of a Linux shell. The purpose of make is to help you build, update, and maintain a collection of related program files. It does this by figuring out which files have changed since you last built or rebuilt your programs. It keeps track of dependencies among files, so that it knows which have to be recompiled as a result of any edits or other changes you make.

A properly designed makefile is normal part of every non-trivial programming project in Linux or Unix, whether at WPI or in the professional world. It allows someone else - a grader, a boss, a colleague, or a user - to rebuild your system in another situation by simply typing the command make. Using the instructions and dependencies encoded in the makefile, make takes care of it from there, and the result should be an executable copy of the program, system, or application. (Equivalent tools are part of Visual Studio, Eclipse, and other integrated development environments.)

Preliminaries

Create a directory to hold the files for Lab 4, and change to this directory now. Download the zip file at the following URL and unzip it into your directory: http://www.cs.wpi.edu/~cs2301/common/lab4-files.zip 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 collection of files needed to build a system or application usually includes header files (.h files), source files (.c files or source code files for the language you are using), compiled files (.o files), and sometimes other kinds of files. Many of these depend on each other. For example, consider the program downloaded 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 include the header file named intarray.h). So, one of the dependencies expressed in the makefile is:

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

                 gcc -Wall -c sinewave.c
To see this dependency, open up the file named makefile using your favorite editor. Near the bottom of the file you'll find these two lines:
sinewave.o: intarray.h sinewave.c
        gcc -c $(CFLAGS) sinewave.c

The first line is called a target line, which begins with a file name and a colon. The file is called the target file. After the colon is a list of zero or more names that the target file depends on. Whenever one of the files after the colon is edited or changed, the target file becomes "out of date". The make tool uses the target line and the last modification dates of the files to determine whether the target file must be rebuilt. It does this recursively, so that if one of the files upon which the target depends is itself out of date, make causes that file to be rebuilt first, etc.

(Note: make depends upon a consistent and reliable time-of-day-and-date clock in order to determine whether one file is "newer" than another. If you are using more than one computer and their clocks are not in sync, modification dates on files could be inconsistent with each other, and make could produce unexpected results.)

After the target line, there is a series of commands that tell exactly how to rebuild the target file. For the case of sinewave.o, only one gcc command is needed to compile the file. Note that this command includes the -c flag to tell gcc that it should only compile the specified file(s) and not attempt to link them into an executable program yet.

The gcc command also includes the notation $(CFLAGS). This is not a compiler switch but rather a variable to make. It lets you control from the command line which compiler switches you wish to apply to all of the files compiled by make. The makefile also contains the following line:

CFLAGS = -g -Wall

This causes the variable CFLAGS to default to -g -Wall, indicating that the programs should be compiled for debugging, and with warnings enabled. We will see below how to override this default.

(Note: There is one other peculiar requirement of makefiles: command lines, such as the gcc command, must each begin with a tab character, not a sequence of blanks!)

Continuing with the theme of dependencies among files, 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 $(CFLAGS) -lm sinewave.o intarray.o -o sinewave

This target line says that if sinewave.o or intarray.o is newer than the target file sinewave, then make must cause the sinewave program to be regenerated with the gcc command 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 -c -g -Wall intarray.c

After this make 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 -c -g -Wall sinewave.c
      gcc -g -Wall -lm sinewave.o intarray.o -o sinewave
Next, edit the file intarray.h in some trivial way, for example, by adding or changing a comment. Now type the command

make

again and you will see that it rebuilds everything. Can you explain why?

Finally, if you wish to turn off the -g switch, you may use one of the following commands:

make CFLAGS=

make CFLAGS=-O

The first simply disables compiling for the debugger. The second tells the compiler to optimize the compiled code for speed. You can consult the man page for the gcc command for other compiler switches, which may also be specified in the CFLAGS variable.

Cleaning Up

Notice the last two lines of the makefile. These say
clean:
        rm -f sinewave.o intarray.o sinewave
This is a target line to "make" the target called clean. You can see that clean does not have any dependencies, but it does have one command line, namely a command to remove the .o files and the original target sinewave. This is common practice in system programming to provide a way of cleaning up intermediate files (in this case the .o files) needed to build a system, leaving only the original files.

Type the command

make clean

and then list your directory. You will see that sinewave and the .o files have disappeared. Now type

make

and you will see that they have been rebuilt.

Using Make from within Emacs

If you use emacs as your editor, you can edit and compile from within emacs. For example, make a small edit in one of the .c or .h files, and then give the emacs "compile" command by typing

(ALT-x compile <ENTER>)

This causes emacs to issue 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 proceeds to rebuild it as if you had invoked make yourself.

This feature is not available in other editors such as kwrite, vi, or pico. However, something equivalent is available in integrated development environments such as Visual Studio and Eclipse.

Get Credit for this Lab

To get credit for this lab, you need to construct and submit a makefile for the files in Homework 4, which is due next Tuesday. The easiest way to do this is to copy the makefile from this lab and edit the dependencies to conform to the files of your HW4 programming project. We will base your Lab 4 credit on your attendance (make sure you sign the attendance sheet today) and your submission of a working makefile for Homework 4 (you do not have to turn in the makefile today, rather, turn it in with your other files when you submit Homework 4).

See you next week.