Makefiles: Department of Computer Science COS121 Lecture Notes
Makefiles: Department of Computer Science COS121 Lecture Notes
Makefiles
1 Introduction
Makefiles are data files that are used by the make program to intelligently compile
and link a system consisting of multiple C++ source files. It is the responsibility
of the designer of the system to specify the detail needed by the make program to
successfully compile and link the system. In order to be able to write your own
makefiles, you need to understand how systems are compiled and linked without the
aid of the make program. This is discussed in Section 2. In Section 3 we discuss how
the make program automates this process. The content and structure of makefile
entries are explained. In Section 4 we discuss how macros and rules can be used to
write generic makefile entries. Understanding this enables you to write smaller and
versatile makefiles that can easily be adapted to describe other systems. Finally
we conclude with two small sections to mention common errors that are made by
novices when creating there own makefiles and to introduce a few challenges to
students who like to deepen their understanding through practical experience.
g++ HelloWorld.C
This command will create the executable file with the default name (a.out). This
program can then be executed with the following command:
./a.out
1
Although only a single command is issued to perform this task, there are three steps
to obtain the final executable program, as shown:
1. Compiler stage: All C++ language code in the .C file is converted into a
lower-level language called Assembly language;
2. Assembler stage: The assembly language code made by the previous stage
is then converted into object code which are fragments of code which the
computer understands directly. An object code file normally ends with .o.
3. Linker stage: The final stage in compiling a program involves linking the object
code to code libraries which contain certain “built-in” functions. This stage
produces an executable program, which is named a.out by default.
You can also specify the output file name by using the -o option followed by the name
of the output file. For example if your source code file is named HelloWorld.C,
you can compile the program with the following command:
This command will create the executable file with the specified name HelloWorld.
This program can then be executed with the following command:
./HelloWorld
Note that you can call the executable whatever you want to. It need not have the
same name as the .C file.
When the code for such class need to be compiled, it has to be made into a single
input document for the compiler. This is done by including the header file into the
.C file. This is specified with a compiler #include command. For example if a
header file is named Account.h, it can be included in any .C file that make use of
the classes, functions or variables declared in it by simply adding the following line
of code in the .C file:
2
#include "Account.h"
When a .C file is compiled, all the the header files inlcuded in it is also compiled.
For eaxmple if a file named Account.C includes the file Account.h with the above
command, both files will be compiled when issuing the following command on the
command line:
Multiple header files can be included in a single .C file. A single header file may also
be included in multiple .C files. To avoid illegal multiple declarations when a given
header file is included in different .C files in the same compilation, it is customary
to guard the content of header files as a defined block (with a unique name) and
to allow the inclusion only on condition that the defined block does not exsist. For
example the following illustrates how the content of a header file can be guarded:
#ifndef H_GUARD
#define H_GUARD
#endif
Firstly the content of the header file is given a unique name with the command
#define H GUARD. To simplify naming, this name is usually chosen to correlate
with the header file name. Secondly the content of the file is placed in a conditional
block starting with #ifndef U GUARD and ending with #endif. This conditional
block specifies that the content should only be considered if the named definition
does not exist. Because the named definition is inside this block it will be compiled
only the first time it is encountered.
Note that the first two steps taken in compiling the files are identical to the previous
procedure for a single .C file. However, the two compiled files are linked together at
the Linker stage to create one executable program. Because the name of the output
file was not specified, the output file will be named a.out in this case.
3
2.4 Compiling without linking
The steps taken in creating the executable program can be divided into steps, sep-
arating the compiling process from the linking process. Firstly all .C files can be
compiled and stored as .o files and in a final step the .o files can be linked to create
the executable program.
You can use the -c option with g++ to create the corresponding object (.o) file from
a .C file. For example, the command to compile both the above mentioned .C files,
without linking them is the following:
When executing this command, the compiler will stop after the assembler stage,
leaving you with the two .o files.
If you would like to name the executable file something else than a.out, you can
specify it with the -o option. For example if you would like to name the executable
file of the above mentioned system GoRide, you can issue the following command:
When issuing the following commands, the intermediate .o files for this system will
be created on disk, wherafter they are linked and a single executable file called
GoRide1 is created.
4
2.6 Re-compiling after a small change
If you gave changed one of the source files in the abovementioned system it is not
neccesary to re-compile all. Only the files that are changed as a result of changing a
source file need to be recompiled.For example, suppose only the Bike.C was changed.
Knowing how the system is designed, you will realise that Tricycle.o and main.o
are not affected by this change. A complete re-build of GoRide incorporating the
change can thus be achieved by issuing the following commands:
g++ -c Bike.C
g++ Bike.o Tricycle.o main.o -o GoRide
Similarly if you have changed Tricycle.h the re-build would require the following
commands:
We have to admit that in this small system it would not make a big difference if
you ommit the creation of one or two .o files. However, when developing very large
systems containing scores, or even hundreds, of files, it often saves a lot of time if
only the appropriate files are updated.
g++ -c ??k*.C
5
Table 1: Useful g++ flags
Flag Usage
-o To specify the output filename
-w Disable all warning messages.
Note that the the use of -w to suppress the compiler warn-
ings is against our coding standards, because for better
reliability one should take compiler warnings serious, and
not ignore them as if they don’t exist!
-Wall Enable most compiler warnings
-Werror Treat compiler warnings as errors
Note that the use of this flag really show that you are
serious about compiler warnings because you actually want
to turn them into errors.
-pedantic Issue all the warnings demanded by ISO C++; reject all
programs that use forbidden extensions.
-pedantic-errors Like -pedantic, except that errors are produced rather
than warnings.
-g turns on debugging. This makes your code ready to run
under gdb.
-O turns on optimization, you may also specify levels (-O2).
-E outputs the preproccessor output to the screen ( stdout ).
-static On systems that support dynamic linking, this prevents
linking with the shared libraries
-c compiles it down to an object file, known as a ’.o’. You can
link together multiple object files into an executable. This
is used in multiple file projects to reduce compile time.
-MM outputs the Makefile dependancies for the source file(s)
listed.
Any number of these flags can be inserted in the compile command. For example, the
following command will compile the HelloWorld.cpp program that was mentioned
in Section 2.1 to create an executable file called MyWorld. Furthermore, it will treat
all warnings as errors and also include additional debugging information in this
executable.
6
3 Automating the build process
A program called make was developed to build source codes automatically. Apart
from automating the build process, this program was designed to only build source
code that has been changed since the last build. The make program gets the inter-
dependency of the source files in the system from a text file called Makefile or
makefile which should reside in the same directory as the source files.
Make checks the modification times of the files, and whenever a file becomes newer
than something that depends on it, it runs the compiler accordingly.
A target given in the Makefile is a file which will be created or updated when any
of its source files are modified. The list of source files is also called the dependency
list of the entry. One or more commands can be listed. The command(s) given in
the subsequent line(s) are executed in order to create the target file. The → in the
above format represent a tab character. Each command must be preceded
by →. The → is used by make to distinguish between commands and dependency
rules.
make uses the entries in the makefile to determine which command(s) should be
issued when a given file has been changed. For example, in the above example when
Bike.C is changed it becomes newer than Bike.o that depends on it. The make
program must then issue a command to create a new Bike.o. The information
needed by make to know when Bike.o needs to be updated is listed as follows:
The above entry states that whenever Bike.o is older than Bike.C, Bike.h or
Wheel.h, the command g++ -c Bike.C should be issued.
As a result of execution of the above mentioned command, Bike.o then becomes
newer than GoRide, that in turn depends on Bike.o. Once again the make
program must issue a command to create a new version of GoRide. The files on
which GoRide depend, and the command to be issued when any of these files are
updated is listed in the makefile as follows:
7
The following is a complete listing of an example makefile for the above mentioned
system:
make
If you do not wish to call your makefile one of these names, you can give it any name
you desire and specify the name of the makefile use the -f option when invoking the
make program. For eaxmple if you named your makefile MyMakefile you can
specify that the make program must use it with the following command:
make -f MyMakefile
The order in which entries are listed is important. If you simply type make and
then return, make will attempt to create or update the top entry listed. If any of
the files listed in the dependency list of this entry does not exist or needs to be
updated, the commands to create (or re-create) them is issued before the command
for this dependency is issued. Therefore the first dependency listed, should be the
command to link all compiled units into a single executable file.
You can also use the make program to create a specific target by passing the required
target as a command line parameter to the make program. For example if you
would like to create Tricycle.o using the above makefile with make you can type
the following command:
make Tricycle.o
8
Figure 1: Borrower-Loan Class diagram
It does not make sense to use wild card characters in dependency lists. It will defeat
the purpose of the dependency list.
Student.o: Student.C \
Student.h \
Borrower.h \
Book.h \
Loan.h \
Date.h
g++ -c Student.C
9
If you have a command that continues on the next line with a backslash (followed
immediately by a newline), then the continuation line does not need to start with a
TAB. However, it is more readable if you use indentation on continuation lines.
Note that Book.o and Date.o may appear in nay order because thy are indepen-
dent of one another. However, both of them have to be listed before Loan.o because
Loan.o depends on them. Likewise, Loan.o should be before Borrower.o, which
should in turn be before Student.o and Lecturer.o. The last two objects are inde-
pendent of one another and may therefore be listed in any order.
Because the order in which files are named in a link command is signifiant, using
wild card characters in a linking instruction may lead to unwanted results.
target:
→ command
→ command
...
One of the most common custom commands that are found in makefiles is the clean
command. This command is used to issue a command to delete all interim files and
other redundant files the current directory. This is sometimes useful to force a full
compilation with the next issue of a make command.
10
On linux a file is deleted (removed) with the rm command. The following is a
typical definition of a custom command named clean:
clean:
rm -f *.o *~
This command uses * as a wild card character to specify that all files with .o exten-
sion should be deleted. In the same way *~ indicates that all files extension ending
with ~ are also included in the list of files to be deleted. These are typical automatic
backup files. The -f option supresses the error message that might be generated, for
example when the command is issued while no such file exists.
Custom commands are executed by passing the required command as a command
line parameter to the make program. For example if you would like to execute the
above clean command (assuming it is included in the makefile) you can type the
following command:
make clean
3.7 Comments
As with any source, the ‘source’ of the makefile can, and should, also be commented
to explain it to possible readers. Any text preceded by the # character is ignored by
the make program. Therefore, you can include complete comment lines by starting a
line with #, or comments to the right of a statement in the makefile. The following
listing of our example makefile include some comments:
# Custom command:
clean:
rm -f GoRide *.o *~ # deleting executable, .o’s and backups
11
4 Making the makefile smaller
4.1 Introduction
The make program has many features. Most important of these features is the
macro feature which is discussed in the following sub-section. Make can also use
rules which you can take advantage of to make your Makefile smaller. The creation
and use of rules is also discussed in this section.
4.2 Macros
The make program allows you to use macros, which are similar to variables, to store
names of files. For example you can define a name for the list of object files as
follows:
The make program automatically expand a macro when it runs. Whenever the
macro name appears within round brackets and preceded by a dollar sign, it will
be replaced by its defined content. The following is a listing of our sample Makefile
again, using the above mentioned macro.
Note that the name that is given to a macro is chosen, like any other variable name,
by the programmer. You may name it whatever you like. However, when selecting
macro names you should apply the same rules as you would for variables in your
programs. Most importantly they should be descriptive of what they represent.
12
4.3 Special macros
In addition to those macros which you can create yourself, there are a few built-in
macros which are used internally by the make program. Some of them are listed
below:
4.4 Rules
The real power from makefiles comes when you want to add your own rules for files.
The following is an example of a rule that specifies that any .o file in the current
folder depends on its corresponding .C file and can be created by compiling the .C
file:
%.o: %.C
g++ $< -Wall -o $@
This compiling is specified to be done with warnings enabled. $< expands to the
first dependency (a .C source file). $@ expands to the target (the corresponding .o
file). This single rule can be used instead of a specific rule for each of the object
files in the system.
Rules need not be for compiling only. For example, if we have a program called
dingamaging that takes a .dmg file and automatically generates a .C file, we can
automate the generation of .C files from .dmg files by adding the following rule to
the makefile:
%.C: %.dmg
dingamaging $< -o $@
If we have added the above mentioned rule to our makefile and have a file named
Bike.dmg in the current folder that is newer than the Bike.C file in the folder.
The make program will generate a new Bike.C file, which in turn will trigger the
recompilation of subsequent files depending on Bike.C.
In our final version of our example makefile we have expanded our use of macros. We
redefine some of the pre-defined macros, for example CC and CFLAGS to contain
detail specific to our needs. We also include two custom commands.
Note that the detail needed in the dependency list was drastically reduced by the
presence of the compilation rule. Because the rule specify that each .o file is depen-
dant on its corresponding .C file, we no longer need to specify the .C files in the list
13
of dependancies. We only need to list all the .h files that is included in the .C file to
ensure proper partial recompilation if one of the .h files are changed. The rule also
specifies how the .o files can be created. Therefore, this detail is not needed where
the dependencies are listed. The make program will know when to create a specific
.o file through the dependency list without the command, and will know how to
create it through the specified compilation rule.
CC = g++
CFLAGS = -Wall -Werror -static
TARGET = GoRide
OBJECTS = Bike.o Tricycle.o main.o
# Dependencies:
Bike.o: Bike.h Wheel.h
Tricycle.o: Tricycle.h Wheel.h
main.o: Bike.h Tricycle.h
# Compilation rule:
%.o: %.C
$(CC) $< $(FLAGS) -o $@
# Custom commands:
clean:
rm -f $(TARGET) $(OBJS) *~ # deleting executable, .o’s and backups
run:
./$(TARGET) # executing the program
5.1 Syntax
• Failing to put a TAB at the beginning of commands. This causes the com-
mands not to run.
• To put a TAB at the beginning of a blank line. This causes the make utility
to complain that there is a “blank” command.
• Not hitting return just after the backslash, should you choose to use it. If the
character directly before a newline is not the backslash the text on the next
line is not interpreted as being a continuation of the previous line. It would
be the same as not having the continuation character at all.
14
5.2 Dependencies
• Not including all dependencies.
To create an .o file by compiling the corresponding .C file, the .o file is depen-
dent on the .C file as well as all the header files that are either directly or
indirectly included in the .C file.
6 Challenges
The following are practical assignments you can do to convince yourself that you
understand the work. These are listed in order of increasing difficulty.
1. Write a custom rule to tar the .C and .h files in the current folder.
2. Write a custom command that will print all .C files that have changed since
the last build.
3. Write a makefile that use a rule to generate the dependency list of the .C files
in the current folder and include it automatically in the makefile2
2
You will have to find out about including files in a makefile
15