1.15. Building A Simple “Hello, World” Application with GNU make

Problem

You want to use GNU make to build a simple “Hello, World” program, such as that in Example 1-4.

Solution

Before you write your first makefile, you’ll need to know a little terminology. A makefile consists of a collection of rules of the form

targets: prerequisitescommand-script

Here targets and prerequisites are space-separated strings, and command-script consists of zero or more lines of text, each of which begins with a Tab character. Targets and prerequisites are usually files names, but sometimes they are simply formal names for actions for make to perform. The command script consists of a sequence of commands to be passed to a shell. Roughly speaking, a rule tells make to generate the collection of targets from the collection of prerequisites by executing the command script.

Tip

Whitespace in makefiles is significant. Lines containing command scripts must begin with a Tab rather than a Space — this is a source of some of the most common beginner errors. In the following examples, lines which begin with a Tab are indicated by an indentation of four characters.

Now you’re ready to begin. Create a text file named makefile in the directory containing your source file. In this file, declare four targets. Call the first target all, and specify the name of the executable you wish to build as its sole prerequisite. It should have no command script. Give the second target the same name as your executable. Specify your application’s source file as its prerequisite, and specify the command line needed to build the executable from the source file as your target’s command script. The third target should be called install. It should have no prerequisites, and should have a command script to copy the executable from the directory containing the makefile to the directory where you want it installed. The last target should be called clean. Like install, it should have no prerequisites. Its command script should remove the executable and the intermediate object file from the current directory. The clean and install targets should both be labeled as phonytargets, using the PHONY attribute.

For example, to build an executable from the source code in Example 1-4 using GCC, your makefile might look as shown in Example 1-14.

Example 1-14. Makefile to build the executable hello with GCC

# This is the default target, which will be built when 
# you invoke make
.PHONY: all
all: hello

# This rule tells make how to build hello from hello.cpp
hello: hello.cpp
    g++ -o hello hello.cpp

# This rule tells make to copy hello to the binaries subdirectory,
# creating it if necessary
.PHONY: install
install:
    mkdir -p binaries
    cp -p hello binaries

# This rule tells make to delete hello and hello.o
.PHONY: clean 
clean:
    rm -f hello

To build an executable from the source code in Example 1-4 using Visual C++, you can use the following makefile shown in Example 1-15.

Example 1-15. Makefile to build the executable hello.exe with Visual C++

#default target
.PHONY: all
all: hello.exe

#rule to build hello.exe
hello.exe: hello.cpp
    cl -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t \ 
        -Fehello hello.cpp 

.PHONY: install
install:
    mkdir -p binaries
    cp -p hello.exe binaries

.PHONY: clean 
clean:
    rm -f hello.exe

Tip

Commands and lists of targets or prerequisites can span more than one line of text in a makefile by using the continuation character \, just as in C++ source files.

To build your executable, set any environment variables required by your command-line tools, change to the directory containing makefile and enter make. To copy your executable to the binaries subdirectory, enter make install. To delete the executable and the intermediate object file from the makefile directory, enter make clean.

Tip

If you have installed the Cygwin environment, described in Recipe Recipe 1.1, you can execute the makefile in Example 1-15 directly from the Windows shell cmd.exe.

Tip

You can also execute this makefile from the Cygwin shell, as follows. From cmd.exe, run vcvars32.bat to set Visual C++’s environment variables. Next, run cygwin.bat to start the Cygwin shell. If you place the Cygwin installation directory in your PATH, you can start the Cygwin shell from cmd.exe simply by entering cygwin. Finally, change to the directory containing the makefile and enter make.

Similarly, you can execute the makefile from the MSYS shell: run vcvars32.bat from cmd.exe, then run msys.bat to start the MSYS shell.

If your toolset provides a script to set environment variables, running a makefile from Cygwin or MSYS is slightly more involved than running it from cmd.exe. It’s necessary for some makefiles, however, since they simply won’t work from cmd.exe.

Discussion

In the next few recipes, you’ll see that GNU make is a powerful tool for building complex projects. But what does it actually do? Here’s how it works. When make is invoked with no arguments, it looks in the current directory for a file named GNUmakefile, makefile or Makefile, and attempts to build the first target it contains, called the default target. If the default target is up to date — meaning that it exists, that all its prerequisites are up to date, and that none of its prerequisites has been modified more recently than it has — make’s job is done. Otherwise, it attempts to generate the default target from its prerequisites by executing its command script. Like the definition of up to date, this process is recursive: for each prerequisite which is not up to date, make searches for a rule having that prerequisite as a target, and starts the whole process again. This continues until the default target is up to date or until an error occurs.

It follows from the above description that a target having no prerequisites is up to date if and only if it corresponds to a file on the filesystem. Therefore, a target corresponding to a non-existent file is never up to date, and can be used to force a command script to be executed unconditionally. Such targets are called phony targets.

Tip

By labeling a target with the .PHONY attribute, as in Example 1-14 and Example 1-15, you can tell make that the target does not correspond to a file, and so should always be always rebuilt.

Conversely, a prerequisite corresponding to an existing file is always up to date, provided it doesn’t appear as the target of a rule.

Now let’s look at what happens when we execute the makefile in Example 1-14. The phony target all is always out of date: its only purpose is to tell make to build hello.exe. In such a simple makefile, there’s no need for an all target; in more complex examples, the all target may have several prerequisites. The rule with target hello tells make to build hello, if necessary, by invoking g++. Assuming that the current directory is empty except for makefile and hello.cpp, the target hello is not up to date. The prerequisite is up to date, however, because the file hello.cpp exists, and because hello.cpp does not appear as the target of any rule. Consequently, make invokes g++ to compile and link hello.cpp, producing the file hello. The prerequisite to the all target is now up to date, so make builds the all target — by executing an empty command script — and exits.

When you invoke make with a command-line argument corresponding to a target, make attempts to build that target. Therefore executing make install causes the following commands to be executed:

    mkdir -p binaries
    cp -p hello binaries

The first command creates the directory binaries, if it doesn’t already exist; the second command copies hello to that directory. Similarly, make clean invokes the command

rm -f hello

which deletes the hello.

Tip

If you’re using Windows, the mkdir, cp, and rm commands used by the install and clean targets refer to the GNU tools distributed with Cygwin or MSYS.

Once you understand how make analyzes dependencies, Example 1-14 may seem pretty simple. In fact, however, it’s considerably more complicated than it needs to be; looking at the various ways it can be simplified is a good way to learn some of the rudiments of makefiles.

Make variables

GNU make supports variables whose values are strings. The most common use of variables in makefiles is as symbolic constants; instead of hard-coding the name of a file or a shell command in several locations within a makefile, you can assign the file or command name to a variable and use the variable instead. This leads to simpler and easier to maintain makefiles. For example, you can rewrite the makefile from Example 1-14 using make variables, as shown in Example 1-16.

Example 1-16. Makefile to build the executable hello with GCC, modified to use make variables

# Specify the target file and the install directory
OUTPUTFILE=hello
INSTALLDIR=binaries

# Default target
.PHONY: all
all: $(OUTPUTFILE)

# Build hello from hello.cpp
$(OUTPUTFILE): hello.cpp
    g++ -o hello hello.cpp

# Copy hello to the binaries subdirectory
.PHONY: install
install:
    mkdir -p $(INSTALLDIR)
    cp -p $(OUTPUTFILE) $(INSTALLDIR)

# Delete hello
.PHONY: clean 
clean:
    rm -f $(OUTPUTFILE)

Here I’ve introduced two make variables, OUTPUTFILE and INSTALLDIR. As you can see, make variables can be assigned values using the assignment operator =, and they can be evaluated by enclosing them in parentheses and prefixing a dollar sign.

You can also set the value of a make variable on the command line, using the syntax make X=Y. In addition, when make starts up, each environment variable is used to initialize a make variable with the same name and value. Values specified on the command line take precedence over values inherited from the environment; values specified in the makefile itself take precedence over values specified on the command line.

GNU make also supports automatic variables that take special values when evaluated in a command script. Most importantly, the variable $@ represents the filename of the target, the variable $< represents the filename of the first prerequisite, and the variable $^ represents the sequence of prerequisites, separated by spaces. Using these variables, we can further simplify the makefile from Example 1-16, as shown in Example 1-17.

Example 1-17. Makefile to build the executable hello with GCC, modified to use automatic variables

# Specify the target file and the install directory
OUTPUTFILE=hello
INSTALLDIR=binaries

# Default target
.PHONY: all
all: $(OUTPUTFILE)

# Build hello from hello.cpp
$(OUTPUTFILE): hello.cpp
    g++ -o $@ $<

# Install and clean targets as in Example 1-16

Within the command script g++ -o $@ $<, the variable $@ expands to hello and the variable $< expands to hello.cpp. Therefore the makefile in Example 1-17 is equivalent to that in Example 1-16, but involves less code duplication.

Implicit Rules

The makefile in Example 1-17 can still be radically simplified. In fact, the command script associated with the target hello is superfluous, as you can demonstrate by executing the makefile in Example 1-18.

Example 1-18. Makefile to build the executable hello with GCC, modified to use implicit rules

# Specify the target file and the install directory
OUTPUTFILE=hello
INSTALLDIR=binaries

# Default target
.PHONY: all
all: $(OUTPUTFILE)

# Tell make to rebuild hello whenever hello.cpp is modified
$(OUTPUTFILE): hello.cpp

# Install and clean targets as in Example 1-16

How does make know how to build the executable hello from the source file hello.cpp, without being told? The answer is that make maintains an internal database of implicit rules representing operations commonly performed when building C and C++ applications. For example, the implicit rule to generate an executable file from a .cpp file looks like Example 1-19.

Example 1-19. A pattern rule from make’s internal database

%: %.cpp
#  commands to execute (built-in):
    $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@

Rules with first lines of the form %xxx:%yyy are known as pattern rules; the % character act as a wildcard. When no ordinary rule matches an out-of-date prerequisite, make searches the available pattern rules. For each pattern rule, make tries to find a string which when substituted for the wildcard character in the target portion of the pattern rule yields the out-of-date prerequisite. If make finds such a string, make substitutes it for the wildcard character in both the target and prerequisite portions of the pattern rule to produce a new rule. make then attempts to build the out-of-date prerequisite using the new rule.

Tip

You can use make -p to print GNU make’s database of implicit rules.

For example, when the makefile in Example 1-18 is first executed, the prerequisite hello of the default target all is out of date. Although hello does appear as a target in the rule $(OUTPUTFILE): hello.cpp, this rule has no command script, and so is useless for building the file hello.make therefore searches its internal database, and finds the rule shown in Example 1-19. By substituting the string hello for the wildcard character in the rule in Example 1-19, make generates the following rule, with hello as its target:

hello: hello.cpp
    $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@

So far, so good — but clearly there’s more to the story. Looking once again through make’s internal database reveals that the variable LINK.cpp expands, by default, to $(LINK.cc). LINK.cc, in turn, expands by default to

$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)

Finally, the variable CXX expands by default to g++, and the other four variables—$(CXXFLAGS), $(CPPFLAGS), $(LDFLAGS), and $(TARGET_ARCH)—expand to empty strings. When all these substitutions are carried out, we’re left with the following rule, which by now may look familiar.

hello: hello.cpp
    g++ $^ -o $@

Tip

Confused? I don’t blame you. If you study the above explanation and spend some time examining make’s internal database, implicit rules will start to make sense.

Customization points

Now that you see how the pattern rule in Example 1-19 causes make to build the executable hello from the source file hello.cpp, you might well wonder why it was necessary to go through so many intermediate steps. Why not simply add the rule

%: %.cpp
    g++ $^ -o $@

to make’s internal database, instead of the complex rule in Example 1-19? The answer is that the intermediate variables such as $(CXX), $(CXXFLAGS), $(CPPFLAGS), and $(LDFLAGS), serve as user customization points. For example, you can specify additional flags to be passed to the linker by specifying a value for LDFLAGS on the command line, in a makefile, or by setting an environment variable. The variables CPPFLAGS and CXXFLAGSplay a similar role for C++ preprocessor and compiler options, respectively. You can even specify a compiler other than GCC by setting the variable CXX. For example, to build hello with Intel for Linux using the makefile in Example 1-18, you can enter make CXX=icpc from the command line—assuming you’ve set the environment variables required by the Intel compiler.

VPATH and the vpath directive

In Example 1-18, make is able to apply the correct pattern rule because the .cpp file resides in the directory where the output file is to created. If your source files are in a different directory, you can use the VPATH variable to tell make where to search for targets or prerequisites:

VPATH = <path-to-cpp-files>

You can also use a vpath directive to tell make to look in a certain location for particular types of files:

# look for .exp files in ../lib
vpath %. exp../lib

Get C++ Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.