Make Tutorial

 

Make is a tool that can be used to simplify development of C++ programs.  Make simplifies C++ programming in two important ways:

 

1)      Make can be used to automatically execute the many Linux commands that are needed to compile, link, and test a large C++ program.  Since these commands will be executed hundreds of times during a program’s development, automating these tasks is essential.

 

2)      Make is intelligent enough to only recompile those parts of a program that have been changed since the last time the program was compiled.  As programs grow larger, recompiling the entire program can take a long time.  Recompiling only those parts that have changed since the last compilation significantly decreases the number of files that need to be recompiled, and therefore the time needed to build the program.

Make Files

In order to automate the build process, make needs information about the commands that are needed to compile, link, and test the program under development.  Additionally, make needs information about the files that make up the project and the dependencies between them.  This information is used by make to determine which files have changed and therefore need to be recompiled.  All of this information (Linux commands + File dependencies) is stored in a text file which serves as input to the make program.  This file is called a “make file”.

File Dependencies

The output of the build process is usually an executable file.  For example, the executable file for a computer chess game might be named chess.  Of course, executable files are created by the linker, which links together multiple .o files to create the final executable.  For example, the chess executable might be created by linking together chess.o, board.o, queen.o, and king.o.  In this case the chess executable “depends on” each of the .o files because it is created from them.  The make file syntax for representing this dependency between the chess executable and the .o files is as follows:

 

bin/chess : obj/chess.o obj/board.o obj/queen.o obj/king.o

 

This line means that the .o files must exist before the executable file can be created.  It also means that if one of the .o files changes, the executable file must be recreated (i.e., relinked).  This line in the make file is called a “dependency line” because it defines dependencies between the file on the left side of the colon and each of the files on the right side of the colon.

 

[NOTE: In all of the examples it is assumed that .cpp files are stored in a subdirectory named src, .h files are stored in a subdirectory named inc, .o files are stored in a subdirectory named obj, and executable files are stored in a subdirectory named bin.]

In general, the format of a dependency line is as follows, where <WS*> represents zero or more white space characters and <WS+> represents one or more white space characters:

 

<WS*>target<WS*>:<WS*>dependency<WS+>dependency<WS+>dependency<WS+> …

 

[NOTE: Dependency lines may not begin with a tab character.  As described later, all lines beginning with a tab are interpreted as command lines.  The white space preceding the target name on a dependency line may include tabs, but the first character may not be a tab.]

 

The file listed on the left side of the colon is called the “target” because it is the file that we would eventually like to build.  Each file listed on the right side of the colon is called a “dependency”.  A make file contains many dependency lines which together define all of the dependencies between files in the project.  In fact, a make file can be thought of as a textual representation of a file dependency graph where the targets define the nodes in the graph, and the dependencies define the links between the nodes.

 

As shown above, executable files depend on the .o files that are used to create them.  Furthermore, .o files depend on the .cpp files from which they are created.  For example, the make file for the chess project would include dependency lines similar to the following:

 

obj/chess.o : src/chess.cpp

obj/board.o : src/board.cpp

obj/queen.o : src/queen.cpp

obj/king.o  : src/king.cpp

 

In this case, the .o files are the targets, and the .cpp files are the dependencies.  These lines mean that each .o file is created from its corresponding .cpp file, and that a .o file needs to be recreated whenever its .cpp file is modified.

 

To this point the dependencies between the files in the chess project are not very complicated.  This is primarily because so far we have ignored header (.h) files.  Because .cpp files normally #include one or more header files, each .o file not only depends on its corresponding .cpp file, but also on all of the header files that are #included by its .cpp file.  For example, the dependency line for obj/chess.o might look like this:

 

obj/chess.o : src/chess.cpp inc/board.h inc/queen.h inc/king.h

 

In this case, the .h files are listed as dependencies because the src/chess.cpp file #includes them, thus making obj/chess.o dependent on them.  This means that if any of the header files is changed, then obj/chess.o will need to be recreated because it is now out-of-date.  Things can become even more complicated if the header files in turn #include other header files, which #include other header files, and so on.  In this case, the dependency line for obj/chess.o would need to list as dependencies ALL header files that are #included either directly or indirectly by src/chess.cpp.  Needless to say, this dependency list could be rather long.

 

The source files for a program (i.e., .cpp and .h files) do not have dependencies because they are created manually by programmers rather than as output from a compiler, linker, or some other program.  Therefore, there is no need to include dependency lines for source files in a make file.  Source files will appear on the right sides of dependency lines for other targets (e.g., .o files), but they will never be targets themselves.

Commands

Creating dependency lines to represent all of the inter-file dependencies in the project allows make to determine when files are out-of-date and need to be rebuilt.  However, dependency lines do not tell make how to rebuild a target once it has been determined that the target is out-of-date.  For this reason, make files must also include the Linux commands that make can execute whenever it decides that a target needs to be rebuilt.  The commands necessary to rebuild a target are listed directly below the target’s dependency line, as follows:

 

bin/chess : obj/chess.o obj/board.o obj/queen.o obj/king.o

      g++ -o bin/chess obj/chess.o obj/board.o obj/queen.o obj/king.o

      chmod ugo+x bin/chess

 

 

obj/chess.o : src/chess.cpp inc/board.h inc/queen.h inc/king.h

      g++ -c -o obj/chess.o –I inc src/chess.cpp

 

### other targets omitted for brevity

 

In this example, the command lines listed under the dependency lines tell make how to recreate bin/chess and obj/chess.o.  bin/chess can be recreated by running g++ followed by chmod.  obj/chess.o can be recreated by running g++.

 

In general, a dependency line is followed by zero or more command lines that describe how to rebuild the target.  Each command line must begin with a tab character (i.e., ’\t’) to distinguish it from other kinds of lines that may appear in a make file (dependency lines, variable definitions, etc.).

 

Make will execute the commands for a target whenever the target is out-of-date.  Make simply executes each command for the target in the order they are specified in the make file.  Each command is printed to standard output before it is executed so that the user can see the progress of the make file as it runs.  Make also checks the value returned by each command to make sure that it completed successfully (i.e., returned 0 from main).  If a command fails, make stops executing immediately after printing an appropriate error message.  Make assumes that the build has failed and that the user needs to fix the problem before trying again.

 

Sometimes it is undesirable for make to print a command to standard output when it is executed.  Echoing of commands can be turned off by placing @ at the beginning of the command line that should not be echoed.  In the following example, neither the g++ command nor the chmod command will be echoed to the screen.

 

bin/chess : obj/chess.o obj/board.o obj/queen.o obj/king.o

      @ g++ -o bin/chess obj/chess.o obj/board.o obj/queen.o obj/king.o

      @ chmod ugo+x bin/chess

 

Similarly, sometimes it is undesirable for make to terminate when a command fails.  By placing at the beginning of a command line, make will not terminate if that command fails.  In the following example, make will continue to execute even if the g++ command or chmod command fails:

 

bin/chess : obj/chess.o obj/board.o obj/queen.o obj/king.o

      - g++ -o bin/chess obj/chess.o obj/board.o obj/queen.o obj/king.o

      - chmod ugo+x bin/chess

 

It is also legal to specify both @ and on the same command line, as follows:

 

bin/chess : obj/chess.o obj/board.o obj/queen.o obj/king.o

      @- g++ -o bin/chess obj/chess.o obj/board.o obj/queen.o obj/king.o

      -@ chmod ugo+x bin/chess

 

Targets with Multiple Dependency Lines

It is legal for a make file to contain multiple dependency lines for the same target.  In this case, the set of dependencies for the target is the union of all dependencies from all dependency lines for the target.  For example, the following two make file fragments are logically equivalent:

 

### fragment 1

 

obj/chess.o : src/chess.cpp inc/board.h inc/queen.h inc/king.h

      g++ -c -o obj/chess.o –I inc src/chess.cpp

 

 

### fragment 2

 

obj/chess.o : src/chess.cpp

      g++ -c -o obj/chess.o –I inc src/chess.cpp

 

obj/chess.o : inc/board.h

 

obj/chess.o : inc/queen.h

 

obj/chess.o : inc/king.h

 

 

While a target may have multiple dependency lines, only one of the dependency lines for a target may be followed by a list of commands.  If multiple dependency lines for the same target have command lists, the make file is invalid and an error message is displayed.

Variables

In order to avoid repetition of file names, and to support writing of self-documenting make files, make supports variable definitions and variable references in a manner similar to most programming languages.  Variables may be defined as follows:

 

EXE_FILE = bin/chess

OBJ_FILES=obj/chess.o obj/board.o obj/queen.o obj/king.o

 

Variable definitions may appear before, after, or in-between the make file’s target definitions.  However, each variable definition must appear on a line by itself.  Once a variable has been defined, it can be referenced using the $(NAME) syntax.  Make replaces each variable reference with the value of the referenced variable before executing the make file.

 

$(EXE_FILE) : $(OBJ_FILES)

      g++ -o $(EXE_FILE) $(OBJ_FILES)

      chmod ugo+x $(EXE_FILE)

 

Compare this with the version that does not use variables:

 

bin/chess : obj/chess.o obj/board.o obj/queen.o obj/king.o

      g++ -o bin/chess obj/chess.o obj/board.o obj/queen.o obj/king.o

      chmod ugo+x bin/chess

 

A variable may be referenced anywhere in the make file after the variable’s definition.  This includes on the right side of later variable definitions, on dependency lines, and on command lines.  A variable may also be defined multiple times.  A variable reference should use the most recent definition of the variable that appears before the reference.  All undefined variables are assumed to have empty string as their value.  Referencing an undefined variable is not an error; make just uses empty string as the variable’s value.

 

In general, the syntax of a variable definition line is as follows, where <WS*> represents zero or more white space characters:

 

<WS*>var-name<WS*>=<WS*>var-value<WS*>

 

Note that all leading and trailing white space around the variable’s value is discarded and is not considered to be part of the variable’s value (although the value can contain white space somewhere in the middle).

Comments

Make files may also include comments.  Comments begin with a # character.  On any line read from the make file, all characters beginning with the first # up to the end of the line are ignored by make.  For example,

 

# build the executable

$(EXE_FILE) : $(OBJ_FILES)          # executable depends on object files

      g++ -o $(EXE_FILE) $(OBJ_FILES)     # link executable

      chmod ugo+x $(EXE_FILE)             # turn on execute permissions

Line Continuations

Sometimes lines in make files become very long.  In such cases it may be desirable to “wrap” a single logical line across multiple physical lines.  Consider the following example:

 

bin/chess : obj/chess.o obj/board.o \

            obj/queen.o obj/king.o

      g++ -o bin/chess \

         obj/chess.o \

         obj/board.o \

         obj/queen.o  \

         obj/king.o

      chmod ugo+x bin/chess

 

Whenever the last non-white space character on a line is a back-slash (i.e., ’\’), the next line in the make file treated as a continuation of the line ending with a back-slash.  Using this line continuation mechanism, one logical line may be continued over any number of physical lines.

 

Note that a back-slash at the end of a line is solely for the purpose of continuing the line and is not actually part of the line per se (i.e., it should be thrown away after it has been used to continue the line).

Empty Lines

Empty lines are lines that contain only white space characters.  Empty lines may be included anywhere in a make file to enhance its layout and readability.  Beyond readability, empty lines are not meaningful.  A make file should work the same if empty lines are taken out or if extra ones are inserted.  Empty lines may even appear in the list of commands following a dependency line.  The appearance of an empty line in a command list does not terminate the list.  Empty lines do not affect the meaning of a make file and are simply ignored.

Make Execution

This section describes the runtime behavior of make, including the algorithm for executing a make file.

Command-Line Options

The command-line syntax for make is:

 

make [-f makefilename] target target target

 

The –f option is used to specify the name of the make file.  If the –f option is present, make loads the make file from the specified file name.  If the –f option is not present, make looks for a file named makefile in the current directory.  If a file named makefile is not found, make looks for a file named Makefile.  If no make file can be found, make displays an error message and terminates.

 

Following the –f option, the make command-line contains zero or more target names.  These are the names of the targets that the user wants to rebuild.  Make will do the minimum amount of work necessary to bring the specified targets up-to-date.  As an example of the make command-line, the author of the chess program might run the following make command:

 

make bin/chess bin/chess-tests

 

In this case, the user wants to build the chess executable program, plus a program that contains some automated test cases for the chess program.  The targets specified on the command-line are built in the order of appearance.  In this example, bin/chess would be built first, and bin/chess-tests would be built second.  If no targets are specified on the command-line, make builds the first target in the make file.

Core Algorithm

For each target to be built, make executes the following algorithm:

 

1)      If the target has already been built during this run, there is nothing more to do and the algorithm terminates.  If the target has not already been built during this run, proceed with the following steps.

2)      Build each of the target’s dependencies.  For a dependency that is also a target in the make file, recursively invoke this algorithm to build the dependency.  For a dependency that is a source file (i.e., a non-target), there is nothing to do because the file is already up-to-date.  Order is important when building the target’s dependencies.  The target’s dependencies are built in the order they are listed in the make file.

3)      After building the target’s dependencies, execute the command list for the target if any of the following conditions is true:

a.       The target file does not exist

b.      The target does not have any dependencies (i.e., no dependencies are listed on the right side of the colon in the make file)

c.       The target depends on a pseudo-target (i.e., one or more of its dependencies are pseudo-targets)

d.      The target file exists, but is older than at least one of the files it depends on, based on the last-modified timestamps stored by the file system

 

By default, each command that is executed by make is echoed to standard output before it is executed, and make terminates with an appropriate error message if any command fails.  This behavior can be modified for individual commands using the @ and command modifiers in the make file.

Pseudo Targets

Target names in make files are usually just file names, possibly preceded by absolute or relative paths.  While this is usually the case, in general a target name can be any character string chosen by the make file author, and need not correspond to a real file name at all.  A target that does not correspond to a real file name is called a “pseudo target”.  The following example shows one possible use of pseudo targets.

 

EXE_FILE = bin/chess

OBJ_FILES=obj/chess.o obj/board.o obj/queen.o obj/king.o

 

exe  :  $(EXE_FILE)

 

$(EXE_FILE) : $(OBJ_FILES)

      g++ -o $(EXE_FILE) $(OBJ_FILES)

      chmod ugo+x $(EXE_FILE)

 

In this case the exe pseudo target can be used to build the chess executable file.  We already had a target for building the chess executable named bin/chess, but typing in file names as targets on the command-line can be inconvenient, especially if the file names are long.  One use for pseudo targets to provide shorter, easier-to-type target names.  Typing

 

make exe

 

is preferable to typing

 

make bin/chess

 

The make file never actually creates a file named exe, which makes exe a pseudo target.  Building the exe target is just a way to trick make into building exe’s dependencies (in this case, bin/chess).

 

Pseudo targets are also used to execute lists of commands.  Consider the following example:

 

clean  :

      echo ’Removing generated files’

rm obj/*

      rm bin/*

      rm testoutput/*

 

In this case the clean target can be used to remove all files that have been generated by previous compilations.   The target name is clean, which does not correspond to a real file.  This target name simply provides a way for the user to execute this list of commands, as demonstrated below:

 

make clean

 

Because there is no file named clean, make will always execute clean’s command list when the clean target is built.

 

Pseudo targets may also appear as dependencies on the right side of the colon in dependency lines.  The meaning is the same as with regular dependencies: the pseudo target must be built before the target on the left side of the colon can be built.  In the following example, the all pseudo target depends on the clean and exe pseudo targets.  When the all target is built, it causes the clean and exe pseudo targets to be built.  The clean target removes all of the old files from previous compilations, and the exe target causes the executable program to be rebuilt.  Therefore, the all pseudo target does a complete rebuild of everything from scratch.

 

EXE_FILE = bin/chess

OBJ_FILES=obj/chess.o obj/board.o obj/queen.o obj/king.o

 

all : clean exe

 

exe : $(EXE_FILE)

 

$(EXE_FILE) : $(OBJ_FILES)

      g++ -o $(EXE_FILE) $(OBJ_FILES)

      chmod ugo+x $(EXE_FILE)

 

clean  :

      echo ’Removing generated files’

rm obj/*

      rm bin/*

      rm testoutput/*

 

Circular File Dependencies

The file dependencies in a make file should not be circular.  This means that a target cannot depend on itself, either directly or indirectly.  The following is an example of a circular file dependency:

 

Fred  :  Barney

Barney  :  Betty  Wilma

Betty  :

Wilma  :  Fred

 

This make file contains a circular dependency because: Fred depends on Barney who depends on Wilma who depends on Fred.  Since Fred indirectly depends on himself, this make file is invalid.  Whenever make encounters a circular file dependency, it prints out an error message indicating that a circular dependency was found, and which targets participate in the circular dependency.  For example,

 

Error: make file contains a circular dependency

      Fred à

      Barney à

      Wilma à

      Fred

Example Make File

Here is an example of a complete make file.

 

 

EXE_FILE = bin/chess

OBJ_FILES=obj/chess.o obj/board.o obj/queen.o obj/king.o

TEST_EXE_FILE = bin/chess-tests

TEST_OBJ_FILES=obj/chess-tests.o obj/board.o obj/queen.o obj/king.o

 

 

# rebuild everything from scratch

all : clean exe testexe

 

 

# build chess executable

exe : $(EXE_FILE)

 

 

$(EXE_FILE) : $(OBJ_FILES)

      g++ -o $(EXE_FILE) $(OBJ_FILES)

      chmod ugo+x $(EXE_FILE)

 

 

# build test executable

testexe : $(TEST_EXE_FILE)

 

 

$(TEST_EXE_FILE) : $(TEST_OBJ_FILES)

      g++ -o $(TEST_EXE_FILE) $(TEST_OBJ_FILES)

      chmod ugo+x $(TEST_EXE_FILE)

 

 

obj/chess.o : src/chess.cpp inc/chess.h inc/board.h \

              inc/queen.h inc/king.h

      g++ -c –o obj/chess.o –I inc src/chess.cpp

 

obj/board.o : src/board.cpp inc/board.h

      g++ -c –o obj/board.o –I inc src/board.cpp

 

obj/queen.o : src/queen.cpp inc/queen.h inc/board.h

      g++ -c –o obj/queen.o –I inc src/queen.cpp

 

obj/king.o : src/king.cpp inc/king.h inc/board.h

      g++ -c –o obj/king.o –I inc src/king.cpp

 

obj/chess-tests.o : src/chess-tests.cpp inc/board.h \

                    inc/queen.h inc/king.h

      g++ -c –o obj/chess-tests.o –I inc src/chess-tests.cpp

 

 

# compile and run chess program

run  :  exe

      $(EXE_FILE) &

 

 

# compile and run test program

runtests  :  testexe

      $(TEST_EXE_FILE) &

     

 

# remove all generated files

clean  :

      - echo ’Removing generated files’

- rm obj/*

      - rm bin/*

      - rm testoutput/*