Written for CS107E by Anna Zeng, Michelle Park, and Julie Zelenski
What is a Makefile?
Make is a tool that automates building programs; a Makefile describes the commands and options used in the build process. As you will see soon enough, using a Makefile saves you a lot of retyping and makes your life as a developer a whole lot smoother!
This guide introduces Makefiles using examples of cross-development for the ARM architecture. For further information about Makefiles in general, check out the other resources on the bottom of this page.
An example Makefile
The example makefile below builds the
blink.bin program out of the
blink.c source file.
Our labs and assignments will include similar Makefiles,
so you will soon become familiar with the common structure.
NAME = blink CFLAGS = -g -Wall -Og -std=c99 -ffreestanding LDFLAGS = -nostdlib all: $(NAME).bin %.bin: %.elf arm-none-eabi-objcopy $< -O binary $@ %.elf: %.o arm-none-eabi-gcc $(LDFLAGS) $< -o $@ %.o: %.c arm-none-eabi-gcc $(CFLAGS) -c $< -o $@ %.list: %.o arm-none-eabi-objdump -d $< > $@ run: $(NAME).bin rpi-run.py $< clean: rm -f *.o *.elf *.bin *.list
This Makefile may look a bit cryptic at first! Let's try breaking it down step by step.
Rules and recipes
In lecture, Pat whipped up a simple doit script to automate retyping the commands to rebuild a program. The
make tool is just a fancier version of doit. A
Makefile is a text file that describes the steps needed to build a program.
Here is an example of a very simple hard-coded Makefile containing three targets
all: button.bin button.bin: button.c arm-none-eabi-gcc -Og -g -Wall -std=c99 -ffreestanding -c button.c -o button.o arm-none-eabi-gcc -nostdlib button.o -o button.elf arm-none-eabi-objcopy button.elf -O binary button.bin clean: rm -f *.bin *.o
Rules are written in the following way: "the dependencies on the right-hand-side are required
to make the target on the left-hand-side." Thus the first line
button.bin is required to make
all. In other words, to make
all, we must first make
This brings us to the next rule for
button.bin: button.c arm-none-eabi-gcc -Og -g -Wall -std=c99 -ffreestanding -c button.c -o button.o arm-none-eabi-gcc -nostdlib button.o -o button.elf arm-none-eabi-objcopy button.elf -O binary button.bin
The ingredients (dependencies on the right-hand-side) are needed as the starting point to create the desired output (target on the left-hand-side). The indented lines that follow the rule are the commands that turn the ingredients into the final product. These steps are collectively called the recipe. Thus, in order to make
button.bin, we start with our ingredient (
button.c) and then step through the commands in the recipe.
We could add a comment to explain the additional flags included when invoking the compiler. Lines starting with
# are treated as comments.
# Compiler flags used: # -std=c99 use the c99 standard # -Og generate optimized code designed for debugging # -g add debugging information # -Wall give warnings about *all* issues # -ffreestanding generate code assuming no operating system
The final rule indicates what should happen when we
make clean; the recipe for the clean target removes any previous build products so the next compile starts fresh.
clean: rm -f *.bin *.o
One particularly nifty thing
make is that only rebuilds a target when one or more of the components it depends on has changed. If you attempt to re-build a target which is already up-to-date,
make will tell you:
$ make make: Nothing to be done for `all'.
After repeatedly copy-pasting the example Makefile to create a version for a new program, you can see the value in structuring it to be more general-purpose. After all, Makefiles are written for convenience!
NAME = blink CFLAGS = -std=c99 -Og -g -Wall -ffreestanding LDFLAGS = -nostdlib all: $(NAME).bin $(NAME).bin: $(NAME).c arm-none-eabi-gcc $(CFLAGS) -c $(NAME).c -o $(NAME).o arm-none-eabi-gcc $(LDFLAGS) $(NAME).o -o $(NAME).elf arm-none-eabi-objcopy $(NAME).elf -O binary $(NAME).bin clean: rm -f *.bin *.o
We've added three macros up top. They're similar to variables
in that they replace instances of the macro throughout the file with their assigned text.
$(macro_name) syntax is used to access the value of the macro.
This makes it easy to change the name for a new program.
We can further generalize our Makefile by using pattern rules that can be used to operate on any source file, without hard-coding to a particular name.
# This pattern rule compiles a C program into an object file. # filename.o is built from filename.c %.o: %.c arm-none-eabi-gcc $(CFLAGS) -c $< -o $@ # This pattern rule converts assembly instructions into an object file. # filename.o is built from filename.s %.o: %.s arm-none-eabi-as $(CFLAGS) $< -o $@ # This pattern rule links an object file into an executable ELF file. # filename.elf is built from filename.o %.elf: %.o arm-none-eabi-gcc $(LDFLAGS) $< -o $@ # This pattern rule extract binary from an ELF executable # filename.bin is built from filename.elf %.bin: %.elf arm-none-eabi-objcopy $< -O binary $@
The symbols that begin with
% in a pattern rule are handled by
make using the following interpretations:
%is a wildcard symbol when used in a rule;
%.ofor example matches any file that ends with
$@refers to the left part of the rule, before the
$<refers to the first element in the right part of the rule, after the
One more special variable
$^ refers to all elements in the right part of the rule, after the
:, which is to say all of the dependencies.
For further convenience, we can add a rule for the
run target. We use this target to invoke the command
rpi-run.py blink.bin to load our newly-built program on the Pi.
# The run target uploads a freshly made binary image to rpi bootloader run: $(NAME).bin rpi-run.py $<
With that finishing touch, you have a general Makefile that can be easily re-purposed for any Raspberry Pi project. Now that you know that a Makefile is just a cookbook that culminates in the tasty program you wish to create, you're ready to add your favorite recipes and bon appetit!
Some follow up references on Makefiles:
- a Makefile tutorial
- another Makefile tutorial
- CS107 Guide to Makefiles
- An inexhaustible source of make wisdom is the full manual for GNU make which will tell you more that you could ever want to know.
- Reading makefiles from real world projects is a good way to see make in action. A search for makefile on github.com will turn up a treasure trove.
Q. Make is failing with a cryptic error about
Makefile: *** missing separator. What gives?
A. In what is widely considered one of the dumber decisions in the history of computing, a Makefile distinguishes between tabs and spaces. The recipe lines for a target must begin with a tab and an equivalent number of spaces just won't do. Edit your makefile and replace those errant spaces with a tab to restore Makefile joy.