This paper is a short introduction to the UNIX make utility. Although make can be used in conjunction with most programming languages all examples given here use C++. 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.
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.
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, variables 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.
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.
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
% CC -o prog1 file1.cc file2.cc file3.cc
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
% make prog1
or if prog1 is the first target defined in the descriptor file
% make
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.
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 variables. Variables are useful for replacing duplicate entries. The object files in this example were used three times, creating a variable 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 variables, special variables, and suffix rules.
OBJS = file1.o file2.o file3.o prog1 : ${OBJS} ${CXX} -o $@ ${OBJS} file1.o file2.o : mydefs.h clean : rm ${OBJS}
Make is invoked from a command line with the following format
make [-f makefile] [-bBdeiknpqrsSt] [variables name=value] [names]
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.
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:
Hint: use Makefile so that it will list near the beginning of the directory and be easy to find.
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 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
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.
A target is usually the name of the file that make creates, often an object file or executable program.
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
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.
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 variable SHELL = /bin/sh or equivalent to use the shell of your preference. This variable should be included in every descriptor file to make sure the same shell is used each time the descriptor file is executed.
Variables allow you to define constants. By using variables you can avoid repeating text entries and make descriptor files easier to modify. Variable definitions have the form
NAME1 = text string NAME2 = another string
Variables 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 variable 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}
Variable names can use any combination of upper and lowercase letters, digits and underlines. By convention variable 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 OBJSvariable contains another variable $(MORE_OBJS). The order that the variables are defined in does not matter but if a variable name is defined twice only the last one defined will be used. Variables cannot be undefined and then redefined as something else.
Make can receive variables from four sources, variables 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.
Internally defined variables are ones that are predefined in make. You can invoke make with the -p option to display a (very long) listing of all the variables, suffix rules and targets in effect for the current build. Here is a partial listing with the default variables from rohan:
CXX = g++ CC = gcc LD = ld MAKE = $(MAKE_COMMAND) MAKE_COMMAND := make MAKEFLAGS = p
There are a few special internal variables 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 variable @ 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}
Variables 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
% make prog1 DEBUG_FLAG=-g
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
% make prog1 "LIBS= -lm -lX11"
could be used to link an executable using the math and X Windows libraries.
Shell environment variables that have been defined as part of the environment are available to make as variables within a descriptor file. The environment variables defined can be displayed from the command line with the command
% env
These variables can be set within the .login file or from the command line. C shell users can set environment variables with setenv:
% setenv DIR /usr/bin
With four sources for variables 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:
If make is invoked with the -e option the priority order from least to greatest is
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 variable which in this case stands for a .cc file that is used to produce a particular target .o file.