An Introduction to the UNIX Make Utility


Contents

Introduction

This paper is a short introduction to the UNIX make utility. The intended audience is computer science students at Middle Tennessee State University (MTSU) of intermediate ability level, if you're taking CSCI 217 this paper will be of use to you. Although make can be used in conjunction with most programming languages all examples given here use C++ as this is the most common programming language used at MTSU. It is assumed that you have a good understanding of a C++ compiler. As an introduction this paper intends to teach the reader how to use the most common features of make. A more comprehensive guide may be found by examining the list of references provided.

Layout guide

Throughout the paper various text styles will be used to add meaning and focus on key points. All references to the make utility, file names and any sample output use the fixed font style, i.e. fixed font example. If the example is prefixed with a percent character ( % ) it is a UNIX C-shell command line. Words that are key to make terminology are highlighted in bold when they occur for the first time.

Overview

The make utility is a software engineering tool for managing and maintaining computer programs. Make provides most help when the program consists of many component files. As the number of files in the program increases so to does the compile time, complexity of compilation command and the likelihood of human error when entering command lines, i.e. typos and missing file names.

By creating a descriptor file containing dependency rules, macros and suffix rules, you can instruct make to automatically rebuild your program whenever one of the program's component files is modified. Make is smart enough to only recompile the files that were affected by changes thus saving compile time.

What make does

Make goes through a descriptor file starting with the target it is going to create. Make looks at each of the target's dependencies to see if they are also listed as targets. It follows the chain of dependencies until it reaches the end of the chain and then begins backing out executing the commands found in each target's rule. Actually every file in the chain may not need to be compiled. Make looks at the time stamp for each file in the chain and compiles from the point that is required to bring every file in the chain up to date. If any file is missing it is updated if possible.

Make builds object files from the source files and then links the object files to create the executable. If a source file is changed only its object file needs to be compiled and then linked into the executable instead of recompiling all the source files.

Simple Example

This is an example descriptor file to build an executable file called prog1. It requires the source files file1.cc, file2.cc, and file3.cc. An include file, mydefs.h, is required by files file1.cc and file2.cc. If you wanted to compile this file from the command line using C++ the command would be

This command line is rather long to be entered many times as a program is developed and is prone to typing errors. A descriptor file could run the same command better by using the simple command

or if prog1 is the first target defined in the descriptor file

This first example descriptor file is much longer than necessary but is useful for describing what is going on.

     prog1 : file1.o file2.o file3.o
             CC -o prog1 file1.o file2.o file3.o

     file1.o : file1.cc mydefs.h
             CC -c file1.cc

     file2.o : file2.cc mydefs.h
             CC -c file2.cc

     file3.o : file3.cc
             CC -c file3.cc

     clean :
             rm file1.o file2.o file3.o

Let's go through the example to see what make does by executing with the command make prog1 and assuming the program has never been compiled.

  1. make finds the target prog1 and sees that it depends on the object files file1.o file2.o file3.o
  2. make next looks to see if any of the three object files are listed as targets. They are so make looks at each target to see what it depends on. make sees that file1.o depends on the files file1.cc and mydefs.h.
  3. Now make looks to see if either of these files are listed as targets and since they aren't it executes the commands given in file1.o's rule and compiles file1.cc to get the object file.
  4. make looks at the targets file2.o and file3.o and compiles these object files in a similar fashion.
  5. make now has all the object files required to make prog1 and does so by executing the commands in its rule.

You probably noticed we did not use the target, clean, it is called a phony target and is explained in the section on dependency rules.

This example can be simplified somewhat by defining macros. Macros are useful for replacing duplicate entries. The object files in this example were used three times, creating a macro can save a little typing. Plus and probably more importantly, if the objects change, the descriptor file can be updated by just changing the object definition.

     OBJS = file1.o file2.o file3.o

     prog1 : $(OBJS)
           CC -o prog1 $(OBJS)

     file1.o : file1.cc mydefs.h
           CC -c file1.cc

     file2.o : file2.cc mydefs.h
           CC -c file2.cc

     file3.o : file3.cc
           CC -c file3.cc

     clean :
           rm $(OBJS)

This descriptor file is still longer than necessary and can be shortened by letting make use its internal macros, special macros, and suffix rules.

     OBJS = file1.o file2.o file3.o

     prog1 : ${OBJS}
           ${CXX} -o $@ ${OBJS}

     file1.o file2.o : mydefs.h

     clean :
           rm ${OBJS}

Invoking make

Make is invoked from a command line with the following format

However from this vast array of possible options only the -f makefile and the names options are used frequently. The table below shows the results of executing make with these options.

Frequently used make options
Command Result
make use the default descriptor file, build the first target in the file
make myprog use the default descriptor file, build the target myprog
make -f mymakefile use the file mymakefile as the descriptor file, build the first target in the file
make -f mymakefile myprog use the file mymakefile as the descriptor file, build the target myprog

When using a default descriptor file make will search the current working directory for one of the following files in order:

makefile
Makefile

Hint: use Makefile so that it will list near the beginning of the directory and be easy to find.

Descriptor files

To operate make needs to know the relationship between your program's component files and the commands to update each file. This information is contained in a descriptor file you must write called Makefile or makefile.

Comments

Comments can be entered in the descriptor file following a pound sign ( # ) and the remainder of the line will be ignored by make. If multiple lines are needed each line must begin with the pound sign.

     # This is a comment line

Dependency rules

A rule consist of three parts, one or more targets, zero or more dependencies, and zero or more commands in the following form:

     target1 [target2 ...] :[:] [dependency1 ...] [; commands]
             [<tab> command]

Note: each command line must begin with a tab as the first character on the line and only command lines may begin with a tab.

Target

A target is usually the name of the file that make creates, often an object file or executable program.

Phony target

A phony target is one that isn't really the name of a file. It will only have a list of commands and no prerequisites. One common use of phony targets is for removing files that are no longer needed after a program has been made. The following example simply removes all object files found in the directory containing the descriptor file.

     clean :
        rm *.o

Dependencies

A dependency identifies a file that is used to create another file. For example a .cc file is used to create a .o, which is used to create an executable file.

Commands

Each command in a rule is interpreted by a shell to be executed. By default make uses the /bin/sh shell. The default can be over ridden by using the macro SHELL = /bin/sh or equivalent to use the shell of your preference. This macro should be included in every descriptor file to make sure the same shell is used each time the descriptor file is executed.

Macros

Macros allow you to define constants. By using macros you can avoid repeating text entries and make descriptor files easier to modify. Macro definitions have the form

     NAME1 = text string
     NAME2 = another string

Macros are referred to by placing the name in either parentheses or curly braces and preceding it with a dollar sign ( $ ). The previous definitions could referenced

     $(NAME1)
     ${NAME2}

which are interpreted as

     text string
     another string

Some valid macro definitions are

     LIBS = -lm
     OBJS = file1.o file2.o $(more_objs)
     more_objs = file3.o
     CXX = CC
     DEBUG_FLAG =         # assign -g for debugging

which could be used in a descriptor file entry like this

     prog1 : ${objs}
        ${CXX} $(DEBUG_FLAG) -o prog1 ${objs} ${LIBS}

Macro names can use any combination of upper and lowercase letters, digits and underlines. By convention macro names are in uppercase. The text string can also be null as in the DEBUG_FLAG example which also shows that comments can follow a definition.

You should note from the previous example that the OBJSmacro contains another macro $(MORE_OBJS). The order that the macros are defined in does not matter but if a macro name is defined twice only the last one defined will be used. Macros cannot be undefined and then redefined as something else.

Make can receive macros from four sources, macros maybe defined in the descriptor file like we've already seen, internally defined within make, defined in the command line, or inherited from shell environment variables.

Internal macros

Internally defined macros are ones that are predefined in make. You can invoke make with the -p option to display a listing of all the macros, suffix rules and targets in effect for the current build. Here is a partial listing with the default macros from MTSU's mainframe frank.

     CXX = CC
     CXXFLAGS = -O
     GFLAGS = 
     CFLAGS = -O
     CC = cc
     LDFLAGS = 
     LD = ld
     LFLAGS =
     MAKE = make
     MAKEFLAGS = b
Special macros

There are a few special internal macros that make defines for each dependency line. Most are beyond the scope of this document but one is especially useful in a descriptor file and you are likely to see it even in simple descriptor files.

The macro @ evaluates to the name of the current target. In the following example the target name is prog1 which is also needed in the command line to name the executable file. In this example -o @ evaluates to -o prog1.

     prog1 : ${objs}
        ${CXX} -o $@ ${objs}

Command line macros

Macros can be defined on the command line. From the previous example the debug flag, which was null, could be set from the command line with the command

Definitions comprised of several words must be enclosed in single or double quotes so that the shell will pass them as a single argument. For example

could be used to link an executable using the math and X Windows libraries.

Shell variables

Shell variables that have been defined as part of the environment are available to make as macros within a descriptor file. C shell users can see the environment variables they have defined from the command line with the command

These variables can be set within the .login file or from the command line with a command like:

Macro assignment priority

With four sources for macros there is always the possibility of conflicts. There are two orders of priority available for make. The default priority order from least to greatest is:

  1. internal definitions
  2. shell environment variables
  3. descriptor file definitions
  4. command line macro definitions

If make is invoked with the -e option the priority order from least to greatest is

  1. internal definitions
  2. descriptor file definitions
  3. shell environment variables
  4. command line macro definitions

Suffix rules

Make has a set of default rules called suffix or implicit rules. These are generalized rules that make can use to build a program. For example in building a C++ program these rules tell make that .o object files are made from .cc source files. The suffix rule that make uses for a C++ program is

     .cc.o:
        $(CXX) $(CXXFLAGS) -c $<

where $< is a special macro which in this case stands for a .cc file that is used to produce a particular target .o file.

References

  1. Becker, B. (Jan, 1996). A GNU Make Tutorial http://www.undergrad.math.uwaterloo.ca/~cs241/make/tutorial/index.html
  2. Hewlett-Packard, (Nov, 1993). make (1) man pages. 16 pp.
  3. Oram,A. & Talbott, S. (Feb, 1993). Managing Projects with make. 149 pp.
  4. Stallman, R & McGrath Roland (Dec, 1993). GNU Make http://csugrad.cs.vt.edu/manuals/make/make_toc.html
  5. Wang, P. (1993). ANSI C on UNIX. 432 pp.