Makefile Basics
This document is meant to serve as a very brief reference on how to read the Makefiles provided in this class. This tutorial is meant to be just enough to help you read the Makefiles you provide, and is not meant to be a complete overview of Makefiles or enough to help you make your own. If you are interested in learning more, there are some good tutorials online, such as this walkthrough.
A Makefile is often used with C to help with automating the (repetitive) task of compiling multiple files. This is especially helpful in cases where there are multiple pieces of your codebase you want to compile separately, such as choosing to test a program or run that program.
Variables
To illustrate how this works, let us examine a few lines in the Makefile that will be used for the minifloat
assignment. Our first line of code is to define a variable CFLAGS
:
CFLAGS=-Wall -Wpedantic -Werror -Wshadow -Wformat=2 -Wconversion -std=c99
As in other settings, defining this variable CFLAGS
allows us to use the contents (a string in this case) later in our Makefile. Our specific choice of CFLAGS
here is to indicate that we are defining the flags (for C) that we will be using in this Makefile. Later, when we use this variable in-line, the Makefile will simply replace the variable with whatever we defined it as, thus allowing us to use the same flags consistently for every command we run.
Commands
The rest of our Makefile for this assignment will consist of commands. A command has the following structure:
name: dependent_files
operation_to_run
The name of a command is what you run in your terminal after make
, such as make part1
or make all
(this gets a bit more complicated in some cases). The dependent_files
indicate which files this command depends on – the Makefile will only run this command if one of these files changed since the last time we ran it. Finally, the operation is what actually gets run in our console, such as when we run gcc main.c -o main.o
.
Example Command
To make this more concrete, let us examine our first command for part1
:
part1: minifloat.c minifloat_test_part1.c minifloat_test_part1.expected
$(CC) $(CFLAGS) minifloat.c minifloat_test_part1.c -o minifloat_test_part1.out
This command will execute when we run make part1
, but only if one of minifloat.c
, minifloat_test_part1.c
or minifloat_test_part1.expected
have been modified since we last ran this command. What actually runs is the next line, with the $(CC)
, $(CFLAGS)
, and a bunch of filenames. $(CC)
is a standard Makefile variable that is replaced by our C compiler – in our case, this is gcc
. The $(CFLAGS)
variable here is what we defined earlier, so we include all of the flags we desired. Finally, the list of files is exactly the same as we might normally run with gcc
. In total, then, this entire operation will be translated to:
$(CC) $(CFLAGS) minifloat.c minifloat_test_part1.c -o minifloat_test_part1.out
-->
gcc $(CFLAGS) minifloat.c minifloat_test_part1.c -o minifloat_test_part1.out
-->
gcc -Wall -Wpedantic -Werror -Wshadow -Wformat=2 -Wconversion -std=c99 minifloat.c minifloat_test_part1.c -o minifloat_test_part1.out
This compilation would be a huge pain to type out everytime, especially with all of those flags (and easy to mess up), but with the Makefile, we can run all this with just make part1
. We can do the same with make part2
to run the next set of commands instead.
Clean
One final node is that it is conventional (though not required) to include a make clean
that removes any generated files, often for being able to clean up our folder or push our work to a Git repository. In our particular file, we have defined clean
to remove the generated .out
files and any .txt
files that were used for testing:
clean:
rm -f *.out.stackdump
rm -f *.out
rm -f *.txt
Complete Makefile
For reference, the entirity of our Makefile is included here:
CFLAGS=-Wall -Wpedantic -Werror -Wshadow -Wformat=2 -Wconversion -std=c99
CC = gcc
all: part1 part2 part3
part1: minifloat.c minifloat_test_part1.c minifloat_test_part1.expected
$(CC) $(CFLAGS) minifloat.c minifloat_test_part1.c -o minifloat_test_part1.out
part2: minifloat.c minifloat_test_part2.c
$(CC) $(CFLAGS) minifloat.c minifloat_test_part2.c -o minifloat_test_part2.out
part3: minifloat.c minifloat_test_part3.c
$(CC) $(CFLAGS) minifloat.c minifloat_test_part3.c -o minifloat_test_part3.out
clean:
rm -f *.out.stackdump
rm -f *.out
rm -f *.txt
.PHONY: all clean