Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Make: an essential tool for separate compilation

When using separate compilation (breaking code into a number of translation units), you need some way to automatically compile each file and to tell the linker to build all the pieces – along with the appropriate libraries and startup code – into an executable file. Most compilers allow you to do this with a single command-line statement. For the Gnu C++ compiler, for example, you might say

g++ SourceFile1.cpp SourceFile2.cpp

The problem with this approach is that the compiler will first compile each individual file, regardless of whether that file needs to be rebuilt or not. With many files in a project, it can become prohibitive to recompile everything if you’ve only changed a single file.

The solution to this problem, developed on Unix but available everywhere in some form, is a program called make. The make utility manages all the individual files in a project by following the instructions in a text file called a makefile. When you edit some of the files in a project and type make, the make program follows the guidelines in the makefile to compare the dates on the source code files to the dates on the corresponding target files, and if a source code file date is more recent than its target file, make invokes the compiler on the source code file. make only recompiles the source code files that were changed, and any other source-code files that are affected by the modified files. By using make, you don’t have to re-compile all the files in your project every time you make a change, nor do you have to check to see that everything was built properly. The makefile contains all the commands to put your project together. Learning to use make will save you a lot of time and frustration. You’ll also discover that make is the typical way that you install new software on a Linux/Unix machine (although those makefiles tend to be far more complicated than the ones presented in this book, and you’ll often automatically generate a makefile for your particular machine as part of the installation process).

Because make is available in some form for virtually all C++ compilers (and even if it isn’t, you can use freely-available makes with any compiler), it will be the tool used throughout this book. However, compiler vendors have also created their own project building tools. These tools ask you which files are in your project, and determine all the relationships themselves. These tools use something similar to a makefile, generally called a project file , but the programming environment maintains this file so you don’t have to worry about it. The configuration and use of project files varies from one development environment to another, so you must find the appropriate documentation on how to use them (although project file tools provided by compiler vendors are usually so simple to use that you can learn them by playing around – my favorite form of education).

The makefiles used within this book should work even if you are also using a specific vendor’s project-building tool.

Make activities

When you type make (or whatever the name of your “make” program happens to be), the make program looks in the current directory for a file named makefile, which you’ve created if it’s your project. This file lists dependencies between source code files. make looks at the dates on files. If a dependent file has an older date than a file it depends on, make executes the rule given after the dependency.

All comments in makefiles start with a # and continue to the end of the line.

As a simple example, the makefile for a program called “hello” might contain:

# A comment
hello.exe: hello.cpp
mycompiler hello.cpp

This says that hello.exe (the target) depends on hello.cpp. When hello.cpp has a newer date than hello.exe, make executes the “rule” mycompiler hello.cpp . There may be multiple dependencies and multiple rules. Many make programs require that all the rules begin with a tab. Other than that, whitespace is generally ignored so you can format for readability.

The rules are not restricted to being calls to the compiler; you can call any program you’d like from within make. By creating groups of interdependent dependency-rule sets, you can modify your source code files, type make and be certain that all the affected files will be rebuilt correctly.

Macros

A makefile may contain macros (note that these are completely different from C/C++ preprocessor macros). Macros allow convenient string replacement. The makefiles in this book use a macro to invoke the C++ compiler. For example,

CPP = mycompiler
hello.exe: hello.cpp
$(CPP) hello.cpp

The = is used to identify CPP as a macro, and the $ and parentheses expand the macro. In this case, the expansion means that the macro call $(CPP) will be replaced with the string mycompiler. With the above macro, if you want to change to a different compiler called cpp, you just change the macro to:

CPP = cpp

You can also add compiler flags, etc., to the macro, or use separate macros to add compiler flags.

Suffix Rules

It becomes tedious to tell make how to invoke the compiler for every single cpp file in your project, when you know it’s the same basic process each time. Since make is designed to be a time-saver, it also has a way to abbreviate actions, as long as they depend on file name suffixes. These abbreviations are called suffix rules . A suffix rule is the way to teach make how to convert a file with one type of extension ( .cpp, for example) into a file with another type of extension ( .obj or .exe). Once you teach make the rules for producing one kind of file from another, all you have to do is tell make which files depend on which other files. When make finds a file with a date earlier than the file it depends on, it uses the rule to create a new file.

The suffix rule tells make that it doesn’t need explicit rules to build everything, but instead it can figure out how to build things based on their file extension. In this case it says: “to build a file that ends in exe from one that ends in cpp, invoke the following command.” Here’s what it looks like for the above example:

CPP = mycompiler
.SUFFIXES: .exe .cpp
.cpp.exe:
$(CPP) $<

The .SUFFIXES directive tells make that it should watch out for any of the following file-name extensions because they have special meaning. Next you see the suffix rule .cpp.exe which says: “here’s how to convert any file with an extension of cpp to one with an extension of exe” (when the cpp file is more recent than the exe file). As before, the $(CPP) macro is used, but then you see something new: $<. Because this begins with a ‘ $’ it’s a macro, but this is one of make’s special built-in macros. The $< can only be used in suffix rules, and it means “whatever prerequisite triggered the rule” (sometimes called the dependent), which in this case translates to: “the cpp file that needs to be compiled.”

Once the suffix rules have been set up, you can simply say, for example, “ make Union.exe ,” and the suffix rule will kick in, even though there’s no mention of “Union” anywhere in the makefile.

Default targets

After the macros and suffix rules, make looks for the first “target” in a file, and builds that, unless you specify differently. So for the following makefile:

CPP = mycompiler
.SUFFIXES: .exe .cpp
.cpp.exe:
        $(CPP) $<
target1.exe:
target2.exe:

If you just type ‘ make’, target1.exe will be built (using the default suffix rule) because that’s the first target that make encounters. To build target2.exe you’d have to explicitly say ‘ make target2.exe ’. This becomes tedious, so you normally create a default “dummy” target which depends on all the rest of the targets, like this:

CPP = mycompiler
.SUFFIXES: .exe .cpp
.cpp.exe:
        $(CPP) $<
all: target1.exe target2.exe

Here, ‘ all’ does not exist, and there’s no file called ‘ all’, so every time you type make, the program sees ‘ all’ as the first target in the list (and thus the default target), then it sees that ‘ all’ does not exist so it had better make it by checking all the dependencies. So it looks at target1.exe and (using the suffix rule) sees whether (1) target1.exe exists and (2) whether target1.cpp is more recent than target1.exe, and if so runs the suffix rule (if you provide an explicit rule for a particular target, that rule is used instead). Then it moves on to the next file in the default target list. Thus, by creating a default target list (typically called ‘ all’ by convention, but you can call it anything) you can cause every executable in your project to be made simply by typing ‘ make’. In addition, you can have other non-default target lists that do other things – for example, you could set it up so that typing ‘ make debug ’ rebuilds all your files with debugging wired in.

Makefiles in this book

Using the program ExtractCode.cpp which is shown in Chapter XX, all the code listings in this book are automatically extracted from the ASCII text version of this book and placed in subdirectories according to their chapters. In addition, ExtractCode.cpp creates several makefiles in each subdirectory (with different names) so that you can simply move into that subdirectory and type make -f mycompiler.makefile (substituting the name of your compiler for ‘ mycompiler’, the ‘ -f’ flag says “use what follows as the makefile”). Finally, ExtractCode.cpp creates a “master” makefile in the root directory where the book’s files have been expanded, and this makefile descends into each subdirectory and calls make with the appropriate makefile. This way you can compile all the code in the book by invoking a single make command, and the process will stop whenever your compiler is unable to handle a particular file (note that a Standard C++ conforming compiler should be able to compile all the files in this book). Because implementations of make vary from system to system, only the most basic, common features are used in the generated makefiles.

An example makefile

As mentioned, the code-extraction tool ExtractCode.cpp automatically generates makefiles for each chapter. Because of this, the makefiles for each chapter will not be placed in the book (all the makefiles are packaged with the source code, which is freely available at http://www.BruceEckel.com). However, it’s useful to see an example of a makefile. What follows is a very shortened version of the one that was automatically generated for this chapter by the book’s extraction tool. You’ll find more than one makefile in each subdirectory (they have different names – you invoke a specific one with ‘ make -f ’). This one is for Gnu C++:

CPP = g++
OFLAG = -o
.SUFFIXES : .o .cpp .c
.cpp.o :
	$(CPP) $(CPPFLAGS) -c $<
.c.o :
	$(CPP) $(CPPFLAGS) -c $<

all: \
	Return \
	Declare \
	Ifthen \
	Guess \
	Guess2
# Rest of the files for this chapter not shown

Return: Return.o 
	$(CPP) $(OFLAG)Return Return.o 

Declare: Declare.o 
	$(CPP) $(OFLAG)Declare Declare.o 

Ifthen: Ifthen.o 
	$(CPP) $(OFLAG)Ifthen Ifthen.o 

Guess: Guess.o 
	$(CPP) $(OFLAG)Guess Guess.o 

Guess2: Guess2.o 
	$(CPP) $(OFLAG)Guess2 Guess2.o 

Return.o: Return.cpp 
Declare.o: Declare.cpp 
Ifthen.o: Ifthen.cpp 
Guess.o: Guess.cpp 
Guess2.o: Guess2.cpp

The macro CPP is set to the name of the compiler. To use a different compiler, you can either edit the makefile or change the value of the macro on the command line, like this:

make CPP=cpp

Note, however, that ExtractCode.cpp has an automatic scheme to automatically build makefiles for additional compilers.

The second macro OFLAG is the flag that’s used to indicate the name of the output file. Although many compilers automatically assume the output file has the same base name as the input file, others don’t (such as Linux/Unix compilers, which default to creating a file called a.out).

You can see there are two suffix rules here, one for cpp files and one for .c files (in case any C source code needs to be compiled). The default target is all, and each line for this target is “continued” by using the backslash, up until Guess2, which is the last one in the list and thus has no backslash. There are many more files in this chapter, but only these are shown here for the sake of brevity.

The suffix rules take care of creating object files (with a .o extension) from cpp files, but in general you need to explicitly state rules for creating the executable, because normally an executable is created by linking many different object files and make cannot guess what those are. Also, in this case (Linux/Unix) there is no standard extension for executables so a suffix rule won’t work for these simple situation. Thus, you see all the rules for building the final executables explicitly stated.

This makefile takes the absolute safest route of using as few make features as possible – it only uses the basic make concepts of targets and dependencies, as well as macros. This way it is virtually assured of working with as many make programs as possible. It tends to produce a larger makefile, but that’s not so bad since it’s automatically generated by ExtractCode.cpp.

There are lots of other make features that this book will not use, as well as newer and cleverer versions and variations of make with advanced shortcuts that can save a lot of time. Your local documentation may describe the further features of your particular make, and you can learn more about make from Managing Projects with Make by Oram & Talbott (O’Reilly, 1993). Also, if your compiler vendor does not supply a make or they use a non-standard make, you can find Gnu make for virtually any platform in existence by searching the Internet for Gnu archives (of which there are many).

Contents | Prev | Next


Contact: webmaster@codeguru.com
CodeGuru - the website for developers.
[an error occurred while processing this directive]