make and Makefiles


In 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.

Options

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.

Makefile Rules

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:

Phony Targets

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.

Comments

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.

Makefile Variables

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 -ansi -pedantic -std=c++14

...

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 -ansi -pedantic -std=c++14
    
...
    
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.

Example

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 -ansi -pedantic -std=c++14

# 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