Home CPSC 340

Makefiles

Motivation

So far we have mostly worked with programs that have consisted of a single file. Most programs, however, are split into multiple files. In order to compile multiple files together, we could pass them all to clang++ like so:

$ clang++ a.cpp b.cpp c.cpp -o program

However, if we want to change one of the files, then recompiling with this method is not efficient because it will compile all three files every time. For small programs, this is not a problem, but large programs can take minutes or even hours to compile. To get around this, we could compile the .cpp files into object files and link them at the end:

$ clang++ -c a.cpp
$ clang++ -c b.cpp
$ clang++ -c c.cpp
$ clang++ a.o b.o c.o -o program

Then if we change one of the files, we will only need to recompile that one file, and relink the executable. The problem with this is that it's tough to remember which files we changed and need to recompile - especially difficult if we change a header file. This is the problem makefiles are designed to solve.


General Idea

A makefile is a file that describes the dependencies between the source files of your program. It also contains the commands that are needed to compile your code into an executable. The make command reads the makefile and your programs source code to build your program.

make looks at the timestamp of your program in order to determine which files are out of date. Using the dependency information in the makefile, it only recompiles the files that need to be recompiled.


Form of a Makefile

A makefile must be named either "makefile" or "Makefile". It consists of a number of rules that look like this:


filename: dependency1 dependency2 dependency3
    command to produce filename
The space to the left of the command must be a tab character, not spaces.

Example

In order to build the Circle example we could use the following makefile:

# make the main program
circle: circle.o main.o
  clang++ circle.o main.o -o circle
  @echo "All done"

# make the circle object file
circle.o: circle.cpp circle.h
  clang++ -c circle.cpp

# make the main object file
main.o: main.cpp circle.h
  clang++ -c main.cpp

We can use this makefile to compile this program by typing "make" on the command line. By default, make will try to build the first target in this case "circle". If we wanted to build another one, we could specify it for example "make main.o".


Macros

Macros are constant values for makefiles, they allow you to specify a setting or option at the top of the makefile and have it be used everywhere. A macro definition consists of the macro name on the left, and equals sign and the value on the right. Macros are referenced with a dollar sign followed by the macro name in parenthesis:

# macros
FLAGS = -W -Wall

# make the main program
circle: circle.o main.o
  clang++ $(FLAGS) circle.o main.o -o circle
  @echo "All done"

# make the circle object file
circle.o: circle.cpp circle.h
  clang++ $(FLAGS) -c circle.cpp

# make the main object file
main.o: main.cpp circle.h
  clang++ $(FLAGS) -c main.cpp

Other Tasks

Makefiles can be used to perform other tasks besides compiling. One that is used frequently is deleting the binary executable and object files. This can be done by making a target that is not a file name, and has no dependencies. When this target is invoked, make will just run the command(s) under the target. We can make a "clean" target to remove unneeded files like so:

clean:
  rm -f circle *.o

Then on the command line, we can run:

make clean

To clean up the binary files.


Tarballs

When dealing with projects consisting of multiple files, it's handy to archive them together in one package. The way this is typically done on UNIX is to create a .tar file or "tarball" with the tar command. To create a tarball:
$ tar -cvf filename.tar file1 file2 ...

The "c" option stands for create, the "v" option stands for verbose (which provides extra output) and the "f" option stands for filename.

In order to extract a tarball, use the "x" option instead of "c":

$ tar -xvf filename.tar

Tar can be used to package multiple files in a project together (to email or create backups for example).

Sometimes, people put in a rule to create a tar ball under a "dist" target:


dist:
  tar -cvf circle.tar.gz main.cpp circle.cpp circle.h

Copyright © 2018 Ian Finlayson | Licensed under a Creative Commons Attribution 4.0 International License.