4.14 Makefiles


The Role of the Make Utility

In well organized object oriented programs the implementation consists of many interrelated files, usually each class will be represented by a code file (e.g., one with a ".cc" suffix) and a header file (e.g., one with a ".h" suffix). During development and maintenance of realistic systems it quickly becomes very difficult to remember:

  • what parts of the system must be recompiled when a given class has been changed, and
  • what compiler options and include files are needed to compile a given class.

Simply recompiling the entire system each time a change is made is acceeptable for only the smallest of systems. In larger systems this strategy is too inefficient as larger class libraries take one or more hours, even on fast workstations, to recompile.

The make utility is a powerful tool to manage the creation and maintenance of systems and libraries. The actions of the make utility are controlled by a "makefile" that describes:

  • the "dependencies" (see below) among parts of the system, and
  • the "commands" needed to rebuild parts of the system.


Using the information in the makefile and the creation/modification times recorded in the file system for each file, make is able to determine which parts of the system need to be rebuilt (using the dependency information) and how to rebuild those parts (using the command information).


Dependencies


A dependency exists between two parts of a system when the dependent part must be recompiled whenever a change is made in the part on which it depends.

There are two kinds of dependencies among the files that make up on object oriented system:

  • the header file for class X many depend on the header file of class Y (X.h --> Y.h),
  • the code file for class X may depend on the header file of class Y (X.cc --> Y.h).


Notice that code files do not depend on other code files.

The FileChooser class is used to ilustrate these two kinds of dependencies. The definition of the FileChooser class (in FileChooser.h) uses the class File as a return type of the AskUser() method. Thus, the FileChooser.h class must include File.h and FileChooser.h depends on File.h (FileChooser.h --> File.h).

    class FileChooser {
    private:	char  fileSpec[200];
	char* choices[200];
	void makeSpec(char* p, char* f);           // utility function
    public:          FileChooser(char* path, char* filter);   //search at path with filter
          FileChooser(char* path);                 //search at path, no filter
          FileChooser();                           //search at CWD, no filter
     File AskUser();                               //get file via dialog
         ~FileChooser();                           //clean up
     };


The implementation of the FileChooser class (in FileChooser.cc) depends, of course, on FileChooser.h. Further dependencies are founds when the implementation of the class's methods are examined. The AskUser() method is:

    File FileChooser::AskUser() {
       Directory directory(thePath, theFilter);
       Selector selector(thePath);
       char* nextName = directory.First();
       while (nextName) {
           selector.Add(nextName);
           nextName = directory.Next();
       }
   
       char* fileChosen = selector.AskUser();
       return File(fileChosen);
    }

The AskUser method uses a Directory object and a Selector object. Thus, the FileChooser.cc file depends on the Directory.h file and the Selector.h file. A summary of these dependencies is:

	FileChooser.h  --> File.h
	FileChooser.cc --> FileChooser.h Directory.h Selector.h


A final dependency is that the object file (.o suffix) created by compiling a code file (.cc suffix) depends on that code file. Thus,

	FileChooser.o --> FileChooser.cc


Finally, the dependencies are transative and cumulative so that in total:

	FileChooser.o --> FileChooser.cc FileChooser.h Directory.h 
                          Selector.h File.h


This list of dependencies reflects, for example, that if Directory.h changes (i.e., the Directory class interface changes) then the FileChooser class should be recompiled to insure that it conforms properly to these changes.


Rules


A basic element in a makefile is a rule . The general format of a rule is:

	{target}:      {dependency list}
                 {tab} {command list}

The {target} names an object file or a complete system that make will build, or a utility function be be performed by make. The target must be followed by the required colon (":") character. The {dependency list} is a list of other elements on which the given target depends. The {command list} is a list of commands to be performed when the {target} is being built or performed by make. The {tab} is a required character that must begin the line on which the line containing the command list.

Both the dependency list and the command list must be a single "line". Either list, however, may be quite long. A standard technique is used to make the lists readable while preserving the view that each one appears as a single "line" to the make utility. A backslash ("\") character immediately preceeding a "new line" (or return) character will cause a second line when displayed on the screen or printed but, when read by the make utility, these two characters will appear as whitespace (a blank). Examples of this will be seen below.

The first kind of targets are those that name components of the system that make will build. A common target of this form is one that names an object file to be built by compiling one or more code files. For example, a rule to build the FileChooser.o object file might appear as:

	FileChooser.o:  FileChooser.cc, FileChooser.h, Directory.h, \{cr}
                        Selector.h, File.h {cr}
	        {tab}   g++ {...} -o FileChooser.o FileChooser.cc {...}

In this example, the target is named FileChooser.o. Since the list of dependencies is long, the backslash character is used to break the single logical "line" into two physical lines. The {tab} shows that a tab character must begin the line containing the command. In this case, a command to compile FileChooser.cc using the g++ compiler is shown in abbreviated form. The notation {...} is used to show where additional parts of the command would go. The full command is shown below.

The second type of target is one that names a complete system or library to be built by make. For example, a target such as:

	Program:  FileChooser.o Directory.o Frame.o {...}
		  g++ {...} -o Program *.o {...}

describes how to build an executable program named Program by having g++ link all of the object files in the current directory. Note that g++ can be used both the compile code files and link object files. The target Program can be refered to in the invocation of make as

	make Program 

which will cause the make utility to find and perform the actions required by a rule whose target is named "Program" in a file named "makefile". Alternatively, if "Program" is the first or only target in the makefile, then the same actions will occur simply by using the command "make" with no arguments.

The third type of target names a utility function performed by the makefile. A common target of this form is the "clean" target that "cleans up" after the make utility by removing intermediate files produced during the running of make that are not needed for longer term use. A typical example is:

	clean:
		rm *.o core ./tmp/*

which deletes all object files in the current directory, a core file that might have been produced when a test program crashed, and all files in a subdirectory named "tmp".

Variables


A makefile may contain "variables" whose "value" may be refered to more than once in the makefile. For example, the makefile may repeatedly need to give the list of include files on each compile command or it may have to give the long list of all of the object files in two or more places in the makefile. To avoid this repetition, a variable is defined once and used in the makefile wherever its value is needed.

Variables are like simple macros. The value of a variable is a simple string. That string is inserted verbatim wherever the value of the variable is referenced.

By convention variables are given suggestive names that are written in capital letters such as OBJECTS, OBJDIR, TMPFILE. The value of a variable is a string of any length that is "assigned" to a variable Eusing an "=" operator. For example:

	CC  = g++
	COMPLIBS= -lgen -ldl -lsocket -lnsl -lg++
	CCFLAGS = -Wall -O
	INCLUDEPATH=-I/usr/include/X11
	LIBPATH=-L/usr/lib/X11
        OBJECTS = Base.o         BasicFrame.o  Button.o         \
                  Counter.o      Date.o        Directory.o      \
                  File.o         FileQuery.o   FileNavigator.o  \
                  FileChooser.o  Frame.o       Location.o       \
                  Message.o      Selector.o    Shape.o          \
                  TextBox.o      Timer.o       Program.o

This example defines several make variables: OBJECTS is a list of .o files written over several lines using the "\{cr}" technique; CC defines what the C++ compiler is called on this system - variables like this one are often defined to aid in porting the software among different platforms that may use different compilers; COMPLIBS is a list of libraries to include when an executable program is built; CCFLAGS defines options to the g++ compiler indicating that all warning messages should be displayed (-Wall) and to optimize the compiled code (-O); INCLUDEPATH is a compiler option specifying where to look for header files (in this case header for the X windows system); LIBPATH is a variable that specifies where to look in the file system to find the X windows system library.

The value of a variable is obtained by enclosing the variable name in "$()" as in $(OBJECTS) or $(CC). Several examples of using the values of variables are these:

        LDLIBS = -lwx_motif -lXm -lXt -lX11 -lm $(COMPLIBS)
        PATHS = $(INCLUDEPATH) $(LIBPATH)
	Program.o:  Program.cc
		$(CC) -c $(CCFLAGS) -o $@ Program.cc
	all:    $(OBJDIR) $(OBJECTS) 
		$(CC) $(CCFLAGS) $(PATHS) -o Program $(OBJECTS)  $(LDLIBS)

The first two examples show how one variable can be used in defining the value of another variable. In each case the value of the variable is simply concatenated with the other characters around it to form the value of the variable being defined. The third example shows a rule where the command list uses several variables: the name of the compiler to use (CC), and the flags to be passed to the compiler (CCFLGS). The symbol "$@" is a built-in make variable that refers to the target's name without any suffix - in this case $@ would be "Program" because "Program.o" is the target and the ".o" is the suffix that is removed to define the current value of $@. The final example shows that variables can be used in both the command list and the dependency list. In this example the target "all" depends on everything that is named in the values of OBJDIR and OBJECTS.

 



Last updated: July 3, 1996 / kafura@cs.vt.edu

 




©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement