make
and MakefilesIn any programming project, even a small one, there is a considerable amount of typing involved in building a final executable file from a collection of source code files: Each source code file must be compiled into an object file. The object files must be linked with system libraries into an executable file. The compilation commands may have a considerable number of options. The linking command may contain several system libraries. And if changes are made to any source code file, which files need to recompiled? Or should all of them be recompiled and linked? The opportunities for mistakes are great, especially during peak programming hours (10:00 p.m. - 5:00 a.m.).
The make
program was designed to build and maintain programming projects.
Like most Unix commands, make
has numerous options. The most useful of these are
-f filename |
use filename Uses the filename specified as the makefile containing the rules to be used. By default, make searches for files of rules named makefile or Makefile . |
-k |
keep going By default, make stops building a project as soon as an error occurs. The use of this option forces make to attempt building other pieces of the project as well. This enables the person building the project to collect a large list of errors for debugging purposes, rather than being limited to only one error at a time. |
target |
build target By specifying the name of a target in the makefile on the command line, only the rules necessary to make that target and its dependencies are executed. This is useful for project maintenance. Pseudo-targets (see below) that represent common sequences of actions can be created. For example, by creating a pseudo-target named clean that removes all object files and executables, the project can be quickly cleaned up by typing make clean at the command prompt. |
A makefile consists largely of rules, which specify how various intermediate files in the projects are constructed. Rules are separated by blank lines. Makefile rules consist of three parts - a target, a dependency list and a recipe which are laid out in the following manner:
target: prerequisites recipe
The target in a makefile rule is usually the name of a file that is to be made as part of the project. This is most commonly an executable file or an object code file. But it doesn't have to be a file (see Phony Targets below). The target must be separated from the prerequisites with a colon. The target name should start in the first column position on the line.
The prerequisites are a list of files which must all exist and be up to date in order to create the target. The file names in the list of prerequisites must be separated by spaces and placed on one line. If the line becomes too long for the editor (a very real possibility for the prerequisites in a large project), then the line may be logically extended by typing a \ immediately followed by a new line. Anything on the following line is considered to be logically part of the line extended with the \.
If the target is an executable file, the prerequisites are typically a list of object code files. For example:
raytrace: trace.o light.o input.o sphere.o polygon.o ray.o \ bound.o triangle.o quad.o (a recipe goes here to make the executable)
If the target is an object code file, the prerequisites typically consist of a source code file and zero or more header files:
trace.o: trace.cpp trace.h rt_attrib.h light.h jitter.h (a recipe could go here to make the object file, but it can be omitted - see below)
The recipe in a makefile rule is simply a command that would normally be typed in on the command line to create the target file from the files in the list of prerequisites:
pxform: pxform.o similar.o affine.o g++ -Wall -Werror -std=c++11 -o pxform pxform.o similar.o affine.o
It is not necessary to spell out the recipe for compiling a C++ source file to object code, because make
can figure it out: it has an implicit rule for updating a .o
file from a correspondingly named .c
, .cc
, or .cpp
file by using the C++ compiler with the -c
option. The makefile rule for an object code target just needs to list the necessary prerequisites:
trace.o: trace.cpp trace.h rt_attrib.h light.h jitter.h
An extremely important point about recipes in makefile rules is that they must be indented, but they must not be indented with spaces. They must be indented with the tab character (\t
). To use spaces is an error and will result in an error message similar to the following:
*** missing separator (did you mean TAB instead of 8 spaces?).
This is a design flaw in the make
program. By inspection, it is difficult to tell if spaces or tabs are being used. One way to tell is by using an editor and moving the cursor around. If the cursor jumps a distance larger than one space, then tabs are being used.
A recipe in a makefile rule is run only if the target is out of date with respect to the dependencies. This is determined by examining the timestamps on the target and the dependencies. If any of the dependencies are newer than the target then the target is recreated by executing the recipe command.
As part of checking the prerequisites of any given rule, the make
program will verify that the prerequisite is not the target of some other rule. If it is, then the other rule is evaluated first. Only when all of the prerequisites are known to be up to date is the comparison made with the target of the current rule.
Some other important points about makefile rules:
The rules are not steps in a program. They are not run from top to bottom. They are a collection and may be run in any order according to the prerequisites that need to be satisfied. The entire collection of rules is analyzed along with the timestamps of the files involved to determine in which specific order to run the recipe commands.
If no target is specified when make
is invoked, then the target of the first rule listed in the file is assumed. This usually means that the rule that makes the project executable from the object code files is listed first. If the project produces only a single executable. then the rule that creates the executable is used. If the project creates multiple executables, then a phony target that has all of the executables as prerequisites is typically listed first.
Listing a header file as part of a recipe command is usually a mistake. Header files do not need to be directly compiled - they are copied into the source files that #include
them during the preprocessor phase of the build process and then the source file is compiled. Directly compiling header file using g++
creates a gigantic "pre-compiled header file" or "PCH file" that ends with the extension .gch
. The file will likely be large enough to use up the entire disk quota on your Unix account and will prevent you from saving other files until it is removed. The solution to this issue is to locate and remove the .gch
file and then fix the makefile recipe command that created it.
A phony target is one that is not really the name of a file; rather it is just a name for a recipe to be executed when you make an explicit request. These can be very useful for maintaining the project. One use of rules with phony targets is to create complex commands that can be easily invoked. These rules have no prerequisites and are invoked by giving the phony target name to make
when it is called.
For example, a common and useful rule in any project has the phony target of clean
.
clean: rm -f my_executable_files_here *.o
This rule, when invoked, will remove all of the object files and any executable files that are listed as part of the command. This rule is invoked by typing make clean
at the command line prompt. The -f
(force) option for rm
will prevent it from issuing error messages if any of the files to be removed are missing.
By convention, rules of this form are placed toward the end of the makefile, after the other rules that actually build the project.
Another use of a phony target is to create a rule for projects with multiple executables. This rule has a phony target and several executable files as prerequisites, but no recipe:
all: program1 program2 program3 program1: program1.o Player.o Team.o g++ -Wall -Werror -std=c++11 -o program1 program1.o Player.o Team.o ... and so on
In this case, the phony target all
depends on multiple executable files. Typing make all
or placing this rule at the top of the rule list for default application will cause make
to create all of the executables.
In makefiles, anything following a #
character is a comment up to the end of the line. Comment characters typically occur at the first column position of a line but do not need to.
Consider the following situation. In a large project, a bug has come up. (Imagine that.) It is not known what the source of the bug is. You have been assigned to debug the problem. This requires going to the makefile and adding the debugging option (-g
) to all of the compiling commands. And after the problem is solved, you must go back and restore those options to their original form.
To help in a situation like that, makefiles allow the use of variables. A makefile variable is assigned a string value in a fashion similar to an assignment in a programming language:
CXX = g++
The symbol CXX
is the variable and its value is g++
. Makefile variable names are case sensitive. By convention, they are all capital letters. Variables are typically set once, at the beginning of the makefile.
There are some standard variable names that are used for common purposes. The name CXX
is used to hold the name of the C++ compiler. The variable CXXFLAGS
is often used to hold common C++ compiler options.
Unlike most programming languages, using the value of makefile variable does not consist of simply giving the variable name. To use a makefile variable, it is necessary to put the name in parantheses and place a dollar sign on the left. For example, the variable CXX
is given a value as described above. To use the variable, it is necessary to write the expression $(CXX)
. To use the variable CXXFLAGS
, the expression $(CXXFLAGS)
must be used:
CXX = g++ CXXFLAGS = -Wall -Werror -std=c++11 ... twister: twister.o rotate.o $(CXX) $(CXXFLAGS) -o twister twister.o rotate.o
Rather than retyping the entire prerequisite list in our recipe, we can use the automatic makefile variable $^
, which expands to the names of all of the prerequisites, with spaces between them:
CXX = g++ CXXFLAGS = -Wall -Werror -std=c++11 ... twister: twister.o rotate.o $(CXX) $(CXXFLAGS) -o twister $^
Many other automatic makefile variables are available in the make
utility; see the GNU Make Manual for further details.
Here is a detailed example of a complete makefile for a relatively small project consisting of several pieces. Note that this example is simplified to some degree compared to what you might encounter in industry.
# Standard compiler variables CXX = g++ CXXFLAGS = -Wall -Werror -std=c++11 -g # Rules start here pxform: pxform.o similar.o translation.o perspective.o incremental.o \ panoramic.o $(CXX) $(CXXFLAGS) -o pxform $^ incremental.o: incremental.cpp incremental.h pxform.h panoramic.o: panoramic.cpp panoramic.h pxform.h perspective.o: perspective.cpp perspective.h pxform.h pxform.o: pxform.cpp panoramic.h incremental.h perspective.h similar.h \ translation.h pxform.h similar.o: similar.cc similar.h pxform.h translation.o: translation.cpp translation.h pxform.h clean: rm -f pxform *.o