|Fundamentals of Computer Science
What is the make utility?
make is a Unix/Linux utility program that updates files based upon the dependency rules listed in a makefile.
If you have a C++ program that is separated into a number of .cc and .h files, you can use the make utility for more efficient program compilation. First, let's consider an inefficient way of maintaining your program, and then we will compare this inefficient method to the use of the make utility.
An inefficient but workable method to handle repeated recompilation of a program is the following: Make a directory for your project with the mkdir command. Let's call the directory project1. Put all the .cc and .h files for your program in this directory, and don't put any files other than those pertaining to your project there. Be sure that you have exactly one main function in one of the .cc files. When you are ready to compile your program, type
CC *.cc -o myproject
where myproject is the name you wish to give to the executable program. You will notice that all the .cc files are compiled (because the *.cc refers to anything that ends in .cc), and the linker automatically links the object code into one executable file called myproject. (If you want your executable to default to the name a.out, leave out the
The problem with the method just described is that you recompile all your source code every time you make a change to your program. In fact, you need to recompile only the source code that you have changed since the last compilation, or any modules which depend on this source code (for example, a .cc file may depend on the definitions in a .h header file).
The make utility, on the other hand, looks at a file called makefile to see the dependencies that exist among the modules of your program, and it recompiles only the modules that have been changed since the last compilation, or the modules that depend on other modules that have been changed.
How do you create a makefile for the make utility to use?
In order to use make for program compilation, you must first create a makefile for the program. Since the makefile is just a text file, you can use any text editor to create it.
In its simplest form, a makefile contains rules of the following form:
where targetList is a list of target files and dependencyList is a list of files that the files in targetList depend on. commandList is a list of zero or more commands, separated by newlines, that reconstruct the target files from the dependency files.
The make utility is very particular about spaces and tabs in your makefile, and if you have any trouble it will probably be on the trivial details of spaces, newlines, and tabs. Be careful to remember these rules as you construct your makefile: Each line in commandList must start with a tab character. Rules must be separated by at least one blank line.
As an example, let's look at the file interdependencies related to an executable file called myprog1. This file is built out of two object modules: main1.o and functions.o. If either file is changed, then myprog1 must be reconstructed by linking the files with the CC utility. Thus, one rule in your makefile should be
myprog1: main1.o functions.o
CC main1.o functions.o -o myprog1
The dependency for the object files must be stated in a similar manner. main1.o is built from two files, main1.cc and functions.h. If either file is changed, then main1.o must be rebuilt. Thus, the remaining rules in the makefile are
main1.o main1.cc functions.h
CC -c main1.c
functions.o: functions.cc functions.h
CC -c functions.cc
In our example above, we have included more rules than are actually necessary, since some rules are known by default by the make utility (e.g., the system knows to associate a .o file with the .cc file of the same name). If you see an example that doesn't have all the rules we have included here, don't be surprised.
Does the order of the rules in the makefile matter?
The order of the rules in the makefile does make a difference.
The make utility creates a "tree" of interdependencies by initially examining the first rule. Each target file in the first rule is a root node of a dependency tree, and each file in its dependency list is added as a leaf of each root node. The make utility then visits each rule associated with each file in the dependency list and performs the same actions, adding to the tree. In our example, the resulting tree would have a root node myprog1 with two children nodes -- main1.o and functions.o. main1.o in turn would have two children nodes -- main1.cc and functions.h; and functions.o would have two children nodes -- functions.cc and functions.h.
The make utility moves up the tree from the leaf nodes to the root node, checking to see whether the last modification times of child nodes are more recent than the last modification times of their immediate parent node. For every case where this is so, the associated parent's rule is executed. The rule must also be executed if no file is present.
How do you execute the make utility?
Once the makefile has been created, you can execute the make utility simply by typing make. The system will look in your current directory for a file called makefile (or Makefile) and will use that file for the dependency lists, determining from there which modules of your program need to be recompiled and/or linked.
Note that a makefile can include the dependencies for more than one program. It can also contain a kind of alias for frequently used commands. To execute the command that you want from the makefile, type
where command is one of the executables defined in your makefile. In our attached example, we would type make clean to remove all the .o files in our current directory, or make average to recompile our program.
How are macros used in a makefile?
If you put a line of the form
token = replacementText
at the top of a makefile, every occurrence of $(token) in the makefile will be replaced by replacementText. (See the example attached.)
There are some macros that are predefined. For example, the CCFLAGS macro is predefined to give the flags (i.e. options) you want to use with CC compilation. You can redefined the predefined macros if necessary.
RM = /bin/rm -f
CCFLAGS = -g
addr: main.o addr.o
$(CCC) -o addr main.o addr.o
main.o: main.cc addr.h
$(CCC) $(CCFLAGS) -c main.cc
addr.o: addr.cc addr.h
$(CCC) $(CCFLAGS) -c addr.cc
$(RM) *.o *~