make is a tool used on all the CS 24 programming projects to build executables and run tests.
make program reads the
Makefile in the current directory to understand how files can be built from other files.
Knowing how a
Makefile works will help you understand how your code is compiled and how the tests work, which will let you investigate test failures and change compiler settings.
We will focus on GNU Make, but there are numerous build programs based on the same concepts.
We will walk through the
Makefile for project01 (JVM), which will demonstrate several of
By the end, you should feel comfortable reading the
Makefiles for most of the CS 24 projects.
Sources, targets, and build commands
At its core, a
Makefile consists of “rules” for all files that can be built.
Makefiles are written in a unique sort of programming language, which takes some practice to read.
Each rule has three parts:
- The name of the file to build (this is called the “target”)
- The files that the target is built from (these are called the “sources”).
These can be files written manually, or other targets in the
- The command needed to build the target from the sources (this is called the “recipe”). (Technically, the recipe consists of zero or more commands to run sequentially.) Commands have to be indented with a tab character.
For example, here is a build rule from the JVM
jvm: jvm.o read_class.o $(CC) $(CFLAGS) $^ -o $@
The pieces that start with
$ are variables, which we’ll discuss later.
After substituting in the values of these variables, this rule becomes:
jvm: jvm.o read_class.o clang-with-asan -Wall -Wextra -Werror -fno-sanitize=integer jvm.o read_class.o -o jvm
jvm is the target file: this rule explains how to build the
read_class.o are the source files: they are needed to build
clang-with-asan ... -o jvm is the command that builds
jvm: it links the
.o files together into a final executable.
When you run
make to build a file, it automatically builds all of the necessary intermediate files.
make jvm will build
jvm, which will recursively build
If you’re interested, you can read more about the theory behind this on Wikipedia.
If you run
make again after editing some files, it will use the rules to rebuild only the files that might change.
For example, if only
jvm.c is edited, then running
make jvm will rebuild
jvm.o (since it is built from
jvm (since it depends on
jvm.o), but not
This can save a lot of time when recompiling a large codebase after making small changes.
When there are many files that can be built the same way, it becomes tedious to write out a build rule for each one.
make allows you to write “pattern rules”, which use
% to match part of the source and target names.
Here’s an example in the JVM
tests/%.class: tests/%.java javac $^
This rule says that any
.class file in the
tests directory can be built from a corresponding
.java file using
Arithmetic.class is built from
Arithmetic.java by running
Jumps.class is built from
% can match any part of a filename, but it must match the same string in the target and the sources.
Variables can be set in
Makefiles and used in recipes, targets, and sources.
They are often used to set configuration options.
For example, the JVM
Makefile defines the standard variables
CC (the C compiler to use) and
CFLAGS (the flags to pass to the C compiler):
CC = clang-with-asan CFLAGS = -Wall -Wextra -Werror -fno-sanitize=integer
Every place where
$(CC) occurs later in the
Makefile, it is substituted with
clang-with-asan, and similarly for
Makefile recipes can also access several “automatic variables”, whose values come from the source and target filenames.
They are used frequently to avoid repeating the sources and targets, or to get the matched filename in a pattern rule.
Unfortunately, the names of these variables are hard to remember.
There is a full list in the
make documentation, but the most common ones are:
$@: the target filename
$^: the space-separated source filenames
$<: the first source filename
Now you should be able to read most of the rules in the JVM
For example, the rule to run the test programs using your JVM:
tests/%-actual.txt: tests/%.class jvm ./jvm $< > $@
This is a pattern rule, where
% represents the name of the test (e.g.
It says that the output depends on both the test’s compiled
.class file and the
That way, if either the test or the
jvm source code changes, the test will be re-run.
Substituting the automatic variables, you can see that the recipe (for the
Primes test, for example) is:
./jvm tests/Primes.class > tests/Primes-actual.txt
(This stores everything printed by
./jvm tests/Primes.class in
make provides many builtin functions for advanced functionality beyond the needs of CS 24.
There is a full list, but the most important one to know is the one to replace filename endings.
This looks like
For example, in the JVM
This says to replace the empty suffix with
-result (i.e. add
-result to the end) for each file in
TESTS_2 variable has the value
OnePlusTwo PrintOnePlusTwo, this rule expands to:
test2: OnePlusTwo-result PrintOnePlusTwo-result
You may have noticed that there are several rules in the JVM
Makefile whose recipes don’t actually create their target file.
These are called “pseudo targets”.
This is allowed by
make, and just means that the recipe will always run if you ask
make to build these targets.
make clean will always run the
rm -f ... command and
make TEST_NAME-result will always print out the result of the test
TEST_NAME, even if the test doesn’t need to be re-run.
One use case for pseudo targets is to represent collections of files to build.
In this case, the rule has a list of sources to build, but no additional commands to build the pseudo target.
For example, the
test8 pseudo target (or just
test) represents running all the tests.
test8 has all of the test results as sources, running
make test8 will report all the test results:
TESTS_8 = $(TESTS_7) Arithmetic CoinSums DigitPermutations FunctionCall \ Goldbach IntegerTypes Jumps PalindromeProduct Primes Recursion test8: $(TESTS_8:=-result)
You can also run
make with no arguments, which will build the first target declared in the
The CS 24
Makefiles intentionally have
all as their first targets, so you can often just run