0% found this document useful (0 votes)
113 views15 pages

Makefiles: Department of Computer Science COS121 Lecture Notes

The document discusses makefiles, which are data files used by the make program to intelligently compile and link C++ programs consisting of multiple source files. It explains that makefiles specify the details needed by make to compile and link the system. The document covers compiling and linking a single file, using header files, compiling and linking multiple files, separating compilation from linking, and recompiling after changes.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
113 views15 pages

Makefiles: Department of Computer Science COS121 Lecture Notes

The document discusses makefiles, which are data files used by the make program to intelligently compile and link C++ programs consisting of multiple source files. It explains that makefiles specify the details needed by make to compile and link the system. The document covers compiling and linking a single file, using header files, compiling and linking multiple files, separating compilation from linking, and recompiling after changes.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 15

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.

2 Command line compiling and linking

2.1 Compiling of a single C++ file


We assume that you use a text editor such as SciTE to write C++ programs and
use the console terminal program of a linux operating system to type command-line
instructions to compile and run these programs. When writing a C++ program, the
source code has to be compiled before it can be executed. When the whole system
is included in a single .C file, the process is trivial. You simply have to invoke the
GCC compiler with the g++ command and specify the .C file name as input file.
For example if your source code file is named HelloWorld.C, you can compile the
program with the following command:

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:

g++ HelloWorld.C -o HelloWorld

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.

2.2 Header files


When writing code in terms of classes (using the object oriented programming
paradigm), it is customary to save the code of a given class into two files:

1. Header file: This file commonly contains forward declarations (prototypes /


signatures) of classes, member functions, instance variables, and other identi-
fiers. A header file is normally saved with .h as its file extension.

2. Implementation : This file contains the complete definitions of the member


functions that are listed in the header file. The implementation file is normally
saved with .C as its file extension.

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:

g++ Account.C -o Account

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

/* The forward declarations of classes, functions,


variables, and other identifiers comes here */

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

2.3 Compiling multiple source files


When your program becomes very large, it makes sense to divide your source code
into separate easily-manageable .C files. It can be compiled issuing a single com-
mand. For example If a system is made up of two .C files named Bike.C and
Tricycle.C respectively and a single common .h file named Wheel.h. The com-
mand to compile all, assuming Wheel.h is properly included in both .C files, is as
follows:

g++ Bike.C Tricycle.C

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:

g++ -c Bike.C Tricycle.C

When executing this command, the compiler will stop after the assembler stage,
leaving you with the two .o files.

2.5 Linking compiled code


The compiler can use either .C or .o files when issued (without the -c option) to
create the required executable file. The following command can thus be issued to
link the compiled code that was created in the previous section:

g++ Bike.o Tricycle.o

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:

g++ Bike.o Tricycle.o -o GoRide

Suppose you have written a system consisting of the following files

C++ file Includes


Bike.C Wheel.h, Bike.h
Tricycle.C Wheel.h, Tricycle.h
main.C Bike.h, Tricycle.h

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.

g++ -c Bike.C Tricycle.C main.C


g++ Bike.o Tricycle.o main.o -o GoRide
1
Note that the names given to files are arbitrary and should be chosen to be descriptive of your
system

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:

g++ -c Tricycle.C main.C


g++ Bike.o Tricycle.o main.o -o GoRide

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.

2.7 Wild card characters


When specifying file names (or paths), the asterisk character * substitutes for any
zero or more characters, and the question mark ? substitutes for any one character.
Ranges of characters enclosed in square brackets ([ and ]) substitute for all the
characters in their ranges; for example, [A-Za-z] substitutes for any single capitalized
or lowercase letter. For example the following command will compile all the .C files
in the current directory that has a k as the third character in the filename:

g++ -c ??k*.C

By using naming conventions, for example starting a specific subset of filenames


with a common character, can enable you to refer to these files in terms of a wild
card pattern.

2.8 Compiler flags


When compiling you can issue the compile command with options. One of the
options (-o) was used in the example in Section 2.1 to specify the output file. A
host of other options, known as compiler flags, are available for most compilers.
However, they are not standardised for all compilers. Table 1 lists some useful flags
that are available for the GCC compiler that is used on campus.

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.

g++ -Werror -g HelloWorld.cpp -o MyWorld

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.

3.1 Entries in a makefile


Each entry in the Makefile uses the following format:

target: source file(s)


→ command
...

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:

Bike.o: Bike.C Bike.h Wheel.h


g++ -c Bike.C

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:

GoRide: Bike.o Tricycle.o main.o


g++ Bike.o Tricycle.o main.o -o GoRide

7
The following is a complete listing of an example makefile for the above mentioned
system:

GoRide: Bike.o Tricycle.o main.o


g++ Bike.o Tricycle.o main.o -o GoRide

Bike.o: Bike.C Bike.h Wheel.h


g++ -c Bike.C

Tricycle.o: Tricycle.C Tricycle.h Wheel.h


g++ -c Tricyle.C

main.o: main.C Bike.h Tricycle.h


g++ -c main.C

3.2 Using the makefile with make


Once you have created your makefile and your corresponding source files, you are
ready to use make. If you have named your makefile either Makefile or makefile,
make will recognize it and use it if you issue the following command at the command
prompt:

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

3.3 Dependency lists


To create the .o files of a system that contains a large number of files, it is important
to include all the files that each .C depends on, in its dependancy list. A particular
.C file depends on its own .h file as well as all the .h files that are directly or indirectly
included in the .C file. For example if you are given the system that contains the
classes shown in the UML class diagram of figure 1, and the C++ definition of each
of the classes in the diagram is stored in an .h file with the same name as the class,
then the makefile entry to create Student.o should be:
Student . o : Student .C Student . h Borrower . h Book . h Loan . h Date . h
g++ −c Student .C

It does not make sense to use wild card characters in dependency lists. It will defeat
the purpose of the dependency list.

3.4 Continuation Line


If you don’t like too big lines in your makefile then you can break your line using a
backslash “\”. For example is the following entry is equivalent to the one above.

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.

3.5 Linking order


When having to link a large number of .o files, it is important to list them in the
correct order for the link command. For example if fileA.C directly or indirectly
includes fileB.h, then fileB.o should appear before fileA.o in the list of.o files to link.
For example If we assume that a C++ file containing the main function, called
LibSys.C, use the classes shown in the UML class diagram of figure 1, the following
makefile entry will link the .o files to create an executable file called LibSys:
LibSys : Book . o Date . o Loan . o Borrower . o Student . o L e c t u r e r . o
g++ Book . o Date . o Loan . o Borrower . o \
Student . o L e c t u r e r . o LibSys .C −o LibSys

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.

3.6 Adding custom commands


You can define any other command-line commands that you frequently use in your
makefile. Do this by specifying it as a target without any dependencies and listing
the command(s) that you would like to execute when you invoke the custum com-
mand below it, preceded by the usual tab charactes. The following shows the format
for such custom command:

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:

# Linking the object code of the complete system:


GoRide: Bike.o Tricycle.o main.o
g++ Bike.o Tricycle.o main.o -o GoRide

# Commands for partial compilation of c++ source files:


Bike.o: Bike.C Bike.h Wheel.h
g++ -c Bike.C

Tricycle.o: Tricycle.C Tricycle.h Wheel.h


g++ -c Tricyle.C

main.o: main.C Bike.h Tricycle.h


g++ -c main.C

# 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:

OBJECTS = Bike.o Tricycle.o main.o

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.

OBJECTS = Bike.o Tricycle.o main.o

# Linking the object code of the complete system:


GoRide: $(OBJECTS)
g++ $(OBJECTS) -o GoRide

# Commands for partial compilation of c++ source files:


Bike.o: Bike.C Bike.h Wheel.h
g++ -c Bike.C

Tricycle.o: Tricycle.C Tricycle.h Wheel.h


g++ -c Tricyle.C

main.o: main.C Bike.h Tricycle.h


g++ -c main.C

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:

CC Contains the current compiler. Defaults to cc


CFLAGS Special options which are added to the built-in compile rule
$@ Full name of the current target.
$? A list of files for current dependency which are out-of-date.
$< The source file of the current (single) dependency.

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

# Linking all the object code:


all: $(OBJECTS)
$(CC) $(FLAGS) $(OBJECTS) -o $(TARGET)

# 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 Common errors in makefiles

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.

5.3 Order of linking


• Listing the files in the final link command in a wrong order.
If Aaa.C directly or indirectly includes Bbb.h, then Bbb.o should appear before
Aaa.o in a compil e or link command that includes these .o files in its input
file list.

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

You might also like