Quick introduction to GNU Make
Norbert Pozar
This is a short, quick introduction to using GNU Make to automatically build your C/C++ project’s executable.
Jump directly to the makefile if you are not interested in the explanation.
I am assuming that you are using Linux or Mac OS X. On Windows things
are a little bit more complicated. For one thing, GNU Make is not
installed by default, so you will have to install it yourself. It can be
found on the gnuwin32
website. All commands described below assume that you are using the
bash
shell, which is the default shell on Linux/OS X. On
Windows, again, one can install it through Cygwin… Cygwin will also
supply the GCC toolchain.
Directory structure of the project
I prefer separating source files and binary files into their own directories. This is usually also the way how IDEs like Eclipse organize the project files.
One standard structure is:
/project/home/dir/
: This is the home directory of your project. This directory should contain:makefile
: The makefile that will be described belowsrc/
: The subdirectory that will contain all you source files (*.h
,*.hpp
,*.c
,*.cpp
, etc.)bin/
: This is the subdirectory where the compiler will create the binary files and the final executable.
Simple C++ project
Make uses makefiles
(a file at the root directory of your project called
makefile
) to build your project. The makefile specifies
rules that Make will follow when generating intermediate files.
So let’s assume that you have the directory
structure as described above. To create this for testing in a
directory ~/testproj
, run the following commands in the
terminal (lines starting with $
specify which command you
should run in your bash
shell, lines without $
give you the output of the previous command):
$ mkdir ~/testproj && cd ~/testproj && mkdir src bin && touch makefile
$ ls
bin makefile src
Let us add some empty source files:
$ cd src && touch main.cpp other.cpp other.h extra.cpp extra.h && ls
extra.cpp extra.h main.cpp other.cpp other.h
Let’s also add some default main file, so we can actually build the
project. Open src/main.cpp
and enter the following
commands:
A quick way to do this from the command line is running the following code:
$ cd ~/testproj
$ echo -e "#include <iostream>\nint main() {\nstd::cout << \"Hello\" << std::endl;\nreturn 0;\n}\n" > src/main.cpp
Now we can build the project. Suppose that the name of the binary
file will be myprogram
. And as I mentioned above, we want
to store the binary in the subdirectory bin
. Using
g++
we can run:
$ g++ src/extra.cpp src/main.cpp src/other.cpp -o bin/myprogram
Now it’s time to test the program:
$ bin/myprogram
Hello
Simple makefile for a simple project
Running g++
this way is quite fragile and tedious, you
have to remember to put in all the source files, and all the various
options that you might have. This is where Make comes in handy.
Open the makefile
that we have just created in the first step of creating the simple project, and enter the following text
(make sure that the whitespace at the beginning of line
2 is a single TAB character):
You can use the following shell command to insert the text (character
\
at the end of line 2 signifies continuation of the
command, so also enter line 3 as a command after entering line 2):
$ cd ~/testproj
$ echo -e "bin/myprogram: src/extra.cpp src/main.cpp\
src/other.cpp\n\tg++ \$^ -o \$@" > makefile
Now remove the compiled program:
$ rm bin/*
And run make by invoking the make
command (make sure
that you are in the root of your project directory, that is,
~/testproj
):
$ make
g++ src/extra.cpp src/main.cpp src/other.cpp -o bin/myprogram
It’s that simple. Now if you run make
again, you get the
following:
$ make
make: `bin/myprogram' is up to date.
This is one of the big advantages of make: it only rebuilds the
program if there has been a change in one of your source files since it
was built last time. This simple makefile will not,
however, rebuild the code if there is change in the header files
extra.h
and other.h
, because make
does not know about them. You can force make
to rebuild
everything by running:
$ make -B
g++ src/extra.cpp src/main.cpp src/other.cpp -o bin/myprogram
Explanation
The simple makefile contains two
lines. The first line tells make that the building program
bin/myprogram
depends on three files:
src/extra.cpp
, src/main.cpp
and
src/other.cpp
. That means that whenever one of these files
changes, make will try to rebuild bin/myprogram
. When
writing makefiles, bin/myprogram
is called the
target, and the source files are the
prerequisites of building the target.
The second line then explains how to build
bin/myprogram
. Here it is as simple as running
g++
with the names of the source files (make will replace
$^
with their names automatically), and the name of the
output file (make will replace $@
by
bin/myprogram
). They are called automatic
variables. The second line must start with a single TAB character to
work properly.
Improved makefile
Lets try to write a more general makefile for our project. Copy the
following source (the makefile is available at
this link) into your makefile
(again, make
sure that lines 23 and 29 start with a single TAB
character):
# we can define variables in a makefile
# variable CC will specify the compiler; feel free to use clang++
CC:=g++
# this variable contains any extra compiler options that we might
# want to add, like -O3, -march=native, etc.
# -O3 tells g++ to optimize the code for speed
CC_OPTS:=-O3
# this variable will contain the names of all cpp source files
SRCS:=$(wildcard src/*.cpp)
# variable with all header files
HEADERS:=$(wildcard src/*.h)
# this will contain the names of all intermediate object files
OBJECTS:=$(patsubst src/%.cpp,bin/%.o,$(SRCS))
# this rule is fancier now
# $< are the names of all prerequisites (the object files)
# $@ is the name of the target (bin/myprogram in this case)
bin/myprogram: $(OBJECTS)
$(CC) $^ $(CC_OPTS) -o $@ # must start with TAB character
# but now we have to tell make how to build the object files
# -c option tells g++ to only compile one source file at a tile
# $< is the name of the first prerequisite (the cpp file in this case)
bin/%.o: src/%.cpp $(HEADERS)
$(CC) $< $(CC_OPTS) -c -o $@ # must start with TAB character
Try building your project by running make
:
$ cd ~/testproj
$ rm bin/*
$ make
g++ src/extra.cpp -O3 -c -o bin/extra.o
g++ src/main.cpp -O3 -c -o bin/main.o
g++ src/other.cpp -O3 -c -o bin/other.o
g++ bin/extra.o bin/main.o bin/other.o -O3 -o bin/myprogram
$ bin/myprogram
Hello
Seems like everything is working fine. Now running make
again will just print that bin/myprogram
is up to date.
Explanation of the improved makefile
The improved makefile is much more
convenient. It automatically searches the subdirectory src
for all *.cpp
source files and all *.h
header
files. It will instruct g++
to build the program from these
files.
This time, however, it uses an intermediate step during which it
compiles each cpp
source file into an object file
bin/*.o
. This speeds up the compilation when you have many
source files in your project, and change only a few of them at a given
time. Make will automatically rebuilt only the updated files.
Character #
starts a comment. Everything after it all
the way to the end of the line is ignored by Make.
You can define variables in a makefile using the code
NAME:=value
. The value can be then used anywhere by writing
$(NAME)
. This is often quite useful.
Command $(wildcard src/*.cpp)
produces a space separated
list of all cpp
files in subdirectory src
.
Command $(patsubst src/%.cpp,bin/%.o,$(SRCS))
converts
the names of the cpp
source files into the names of object
files. For example, src/main.cpp
will become
bin/main.o
.
GCC and optimization
When running g++
with no extra
options, g++
will not optimize the code for speed. This
is useful for debugging because the structure of the program is exactly
preserved. However, when doing numerical computations, you usually want
the code to run as fast as possible. To tell g++
to
optimize the code for speed, add -O3
command line option. The behavior of the program will not change
(well, it should not), but the speed might improve significantly.