Programming Workshop 2 (CSCI 1061U)
Faculty of Science, Ontario Tech University
Lets consider a simple program that comprises three files.
main.cpputil.cpputil.hWe now know how to create an executable prog.
Method 1
> g++ -c main.cpp
> g++ -c util.cpp
> g++ main.o util.o -o progFor this simple case, we can reduce typing somewhat as shown below
Method 2
> g++ main.cpp util.cpp -o progConsider the situation where we have a lot more files—Linux, for example, consists of roughly 15,000 files—both methods quickly become infeasible:
We need a better scheme to manage program comprising multiple files.
make utilityMake utility will enable you automatically build executable from your
source code. You describe how you want your executable to be built using
a Makefile, which make utility uses to build the
executable.
MakefileMakefiles allow us to specify three things: 1) targets, 2)
dependencies, and 3) recipies for building the targets from
dependencies. The logic goes as follows. When asked to build a target,
the make utility will see if any of the dependency has
changed since the last time target was built. If a dependency is newer
than the target, the target is built using the recipe. Consider the
following Makefile
Makefile
prog: main.cpp util.cpp util.h
g++ main.cpp util.cpp -o prog
Here prog is the name of the target,
main.cpp util.cpp util.h are the dependencies that are
required to build this target, and
g++ main.cpp util.cpp -o util is the recipe that builds the
target from the dependencies.
See what happens when we call make the first time.
> make
g++ main.cpp util.cpp -o progSince file prog doesn’t exists. The make
utility uses the recipe to build the target prog.
If we execute make again
> make
make: `prog' is up to date.make detected that target prog already
exists and that none of the dependencies have been changed since the
last time prog was built.
Lets change one of the dependencies and build the target again.
> touch util.h
> make
g++ main.cpp util.cpp -o progmake detected that file util.h has been
modified since the last time target prog was built, so it
decided to build the target again.
MakefileWe use make command that will use the above make file to
build the executable as follows
> makeBy default the make command reads the
Makefile sitting in the current folder.
It is also possible to specify a different Makefile using the the
-f switch as follows
> make -f MyOwnMakefileIt is possible for a Makefile to have multiple targets
as seen below
Makefile
prog: main.cpp util.cpp util.h
g++ main.cpp util.cpp -o prog
run: prog
./prog
The above makefile has two targets. The first target builds the
prog. The second target executes the program. Notice that
the second target requires the prog to already exist.
By default make builds the first target encountered in
the makefile. However, it is possible to specify a target explicitly as
follows
> make progor
> make runThe power of make utility comes from the fact that it is able to
build targets recursively. Say we issue builds target
run
> make runSince prog is a dependency of this target, first
make will attempt to build prog. If
prog doesn’t exists, it will be built. If it already
exists, it will be used. See what happens when we call
make run the first time
> make run
g++ main.cpp util.cpp -o prog
./prog
sum = 83
smallest = 34
largest = 1And then we call it again
> g++ main.cpp util.cpp -o prog
./prog
sum = 83
smallest = 34
largest = 1Can you spot the difference?
Makefiles allow us to specify rules to help automate file processing.
We can also use variables instead of hardcoding values. Lets write a
more general purpose makefile that you can easily adapt for your own
program. A line that begins with # indicates a comment.
Together these features allows flexibility, improves readibality, avoids
repetition, and simplifies adding new source files.
Makefile
# Lets specify compiler
CC = g++
CFLAGS = -c
# Source files. We keep header files separate from the cpp files.
# Recall that we will compile cpp files, but we do not compile
# the header files.
HEADER = util.h
CPP = main.cpp util.cpp
# Program name
PROGNAME = prog
# Object files
OBJ = $(CPP:.cpp=.o)
all: $(PROGNAME)
$(PROGNAME): $(OBJ)
$(CC) -o $(PROGNAME) $(OBJ)
%.o: %.cpp $(HEADER)
$(CC) $(CFLAGS) -o $@ $<
# Target clean is phony, since it doesn't
# create a file called clean. Notice that other
# targets create files.
.PHONY: clean
# We will use target clean to delete both the object files
# and the program.
clean:
rm $(PROGNAME) $(OBJ)
Consider the following situation. Folder lib-util
contains source to build the library that is needed by two programs.
Folders alpha and beta contain the source for
the two programs, respectively. The folder structures looks as
follows:
> tree
.
├── Makefile
├── alpha
│ ├── Makefile
│ └── main.cpp
├── beta
│ ├── Makefile
│ ├── main.cpp
│ ├── test1
│ ├── test1.out
│ ├── test2
│ └── test2.out
└── util-lib
├── Makefile
├── util.cpp
└── util.h
3 directories, 13 filesNotice that we have created three makefiles.
Top-level Makefile
alpha: util-lib
make -C alpha
beta: util-lib
make -C beta
util-lib:
make -C util-lib
.PHONY: alpha
.PHONY: beta
.PHONY: util-lib
.PHONY: clean
clean:
make -C alpha clean
make -C beta clean
make -C util-lib clean
This makefile has three targets. Target alpha depends
upon the library in lib-util folder. Target
beta also depends upon the library in lib-util
folder. Target util-lib has no dependencies. The recipe for
building the targets involve calling make command with
-C switch. The -C switch sets the root folder
for make command. This means that
make -C alpha is similar to calling make
command in subfolder alpha. What you see here is the
recursive use of makefiles. The makefiles sitting in the three
subfolders are responsible for bulding their own targets.
Makefile in alpha
CC = g++
CFLAGS = -c
PROGNAME = alpha
CPP = main.cpp
HEADER = util.h
HEADER_INCLUDE_FOLDER = ../util-lib
LIB = util
LIB_FOLDER = ../util-lib
OBJ = $(CPP:.cpp=.o)
$(PROGNAME): $(OBJ) $(LIB_FOLDER)/libutil.a
$(CC) -o $(PROGNAME) $(OBJ) -L $(LIB_FOLDER) -l $(LIB)
%.o: %.cpp
$(CC) $(CFLAGS) -I $(HEADER_INCLUDE_FOLDER) -o $@ $<
$(LIB):
make -C ../util-lib
.PHONY: clean
.PHONY: util
clean:
rm $(PROGNAME)
rm $(OBJ)Makefile in beta
CC = g++
CFLAGS = -c
PROGNAME = beta
CPP = main.cpp
HEADER = util.h
HEADER_INCLUDE_FOLDER = ../util-lib
LIB = util
LIB_FOLDER = ../util-lib
OBJ = $(CPP:.cpp=.o)
$(PROGNAME): $(OBJ) ../util-lib/libutil.a
$(CC) -o $(PROGNAME) $(OBJ) -L $(LIB_FOLDER) -l $(LIB)
%.o: %.cpp
$(CC) $(CFLAGS) -I $(HEADER_INCLUDE_FOLDER) -o $@ $<
$(LIB):
make -C ../util-lib
.PHONY: clean
.PHONY: $(LIB)
.PHONY: test1
.PHONY: test2
.PHONY: tests
clean:
rm $(PROGNAME)
rm $(OBJ)
test1: $(PROGNAME)
$(PROGNAME) < test1 > foo
diff test1.out foo
test2: $(PROGNAME)
$(PROGNAME) < test2 > foo
diff test2.out foo
tests: test1 test2
Makefile in util-lib
# Lets specify compiler
CC = g++
CFLAGS = -c
AR = ar
ARFLAGS = -rvs
# Source files. We keep header files separate from the cpp files.
# Recall that we will compile cpp files, but we do not compile
# the header files.
HEADER = util.h
CPP = util.cpp
# Program name
LIBNAME = libutil.a
# Object files
OBJ = $(CPP:.cpp=.o)
all: $(LIBNAME)
$(LIBNAME): $(OBJ)
$(AR) $(ARFLAGS) $(LIBNAME) $(OBJ)
%.o: %.cpp $(HEADER)
$(CC) $(CFLAGS) -o $@ $<
# Target clean is phony, since it doesn't
# create a file called clean. Notice that other
# targets --- all and message --- create file
# message.
.PHONY: clean
# We will use target clean to delete both the object files
# and the program.
clean:
rm $(LIBNAME) $(OBJ)
See what happens when we call make command.
> make
make -C util-lib
g++ -c -o util.o util.cpp
ar -rvs libutil.a util.o
ar: creating archive libutil.a
a - util.o
make -C alpha
g++ -c -I ../util-lib -o main.o main.cpp
g++ -o alpha main.o -L ../util-lib -l utilNotice that when building target alpha, target
util-lib is also build. Target alpha depends
upon util-lib.
Now lets see what happens when we build target beta.
make -C util-lib
make[1]: Nothing to be done for `all'.
make -C beta
g++ -c -I ../util-lib -o main.o main.cpp
g++ -o beta main.o -L ../util-lib -l utilNotice that in this case the util library wasn’t recreated.
man make command.