Managing Projects Using Makefiles



Introduction


     As you begin to work on larger projects you will find it necessary to have a means to organize the compilation of your source code. Having to compile multiple files from the command line can become a tedious and time consuming task. The GNU Make program will make this process much easier to deal with. If you have ever compiled or installed software on your linux system the chances are you have run ‘make’ to compile it for you.
     When you run ‘make’ it looks for a file named ‘Makefile’, this file holds a set of rules for compiling the project. The files you are compiling are automatically examined to detect changes, if a change has been made or a certain file is not present it will be compiled, otherwise it’ll be left unchanged. This is very useful when you start making very small changes to a large program, you only need to recompile the files you made changes too and then link them together.
     This tutorial will go over the basic steps for creating your own Makefiles and getting a handle on compiling your multifile projects.

Basic Makefile


 

# Basic Makefile

# all is the default target and is called when just ‘make’ with
# no options is run.

all: myprog

# define myprog and the files it requires to compile. If main.o or
# file.o are not up to date they will be called upon to be updated before
# myprog is compiled together. Note that tabs are required

myprog: main.o file.o
g++ -o myprog main.o file.o

# to compile main.o the file main.cpp is checked for updates. If main.cpp
# is not present an error will be presented. If main.cpp has not changed
# since the last build it will not be recompiled.

main.o: main.cpp
g++ -Wall -c -o main.o main.cpp

file.o: file.cpp
g++ -Wall -c -o file.o file.cpp
 

     Using this Makefile you can compile your program in one simple step. The files ‘main.cpp’, ‘file.cpp’, and ‘file.h’ must exist or an error will occur. Files are checked up to date by comparing their last time modified with its dependencies. So when main.o is checked if it is older then main.cpp it will be compiled, otherwise nothing will happen. This rule is applied to all of the compile directives. You should also note the gaps before the compile line are tab's and are required so it doesn't get confused with other dependencies.

When ‘make’ is run for the first time you should see the following happen.

 

$ make
g++ -Wall -c -o main.o main.cpp
g++ -Wall -c -o file.o file.cpp
g++ -o myprog main.o file.o
 

Now run make again.

 

$ make
make: Nothing to be done for `all'.
 

     If you make a change to file.cpp and run ‘make’ again, file.o will be older then file.cpp so it will be compiled, and myprog will be older then file.o so it will be compiled.

 

$ touch file.cpp
$ make
g++ -Wall -c -o file.o file.cpp
g++ -o myprog main.o file.o
 

Variables


     Sometimes you need to make changes to the options you pass to the compiler, like adding or removing -DDEBUG to define debugging for your program. It would be silly to hand edit every single compile directive to make changes. This is where variables to define your compiler and compiler options will make your life simplier.

     The following will assign the value "gcc" to the variable CC, append " -o" to it and then display the value. All variables must first be defined before you can access them. $ or $(VARIABLE) is the notation used to access a variable.

 

CC = gcc
CC += -o

all:
      @echo $
 

     A typical Makefile will define your compiler name, compiler flags, and compile line in variables. Below is a modified version of the first Makefile.

 

# Basic Makefile with variables

CC = g++
CFLAGS = -Wall -O2
COMPILE = $(CC) $(CFLAGS) -c

all: myprog

myprog: main.o file.o
$(CC) -o myprog main.o file.o

main.o: main.cpp
$(COMPILE) -o main.o main.cpp

file.o: file.cpp
$(COMPILE) -o file.o file.cpp
 


     This will allow you to change your options without having to modify every single line. We can also take this a step further, you will notice that main.o and file.o are compiled in the same manner with just different filenames. Makefiles enable you to use wildcards to match file names and compile multiple files with one definition. Consider the following.

 

# Automated Makefile

CC = g++
CFLAGS = -Wall -O2
COMPILE = $(CC) $(CFLAGS) -c
OBJFILES := $(patsubst %.cpp,%.o,$(wildcard *.cpp))

all: myprog

myprog: $(OBJFILES)
$(CC) -o myprog $(OBJFILES)

%.o: %.cpp
$(COMPILE) -o $@ $<
 

     This Makefile will compile any .cpp file in the current directory. The first change you see from the previous example is the OBJFILES variable. This variable is filled with .o targets using the wildcard command and patsubst. The $(wildcard *.cpp) will retrieve any .cpp file in the current directory and store it in a variable. The patsubstr functions is used to convert a file from one format to another. In this case each .cpp file is converted into a .o extension and then stored into OBJFILES. This variable is then used to compile each .cpp file.
     Instead of having main.o: main.cpp we now have %.o: %.cpp. Any target that has a .o extension will goto here. In the compile line you will see two new variables $@ and $<. $@ will match the target and the $< will match the dependency, so basically $@ will be replace with main.o and $< will be replaced with main.cpp.