Makefile¶
Getting started¶
- Recommended: Makefile Tutorial (by Chase Lambert and contributors): https://makefiletutorial.com/
- A Simple Makefile Tutorial (by Bruce A. Maxwell), https://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/
Books¶
- Robert Mecklenburg, Managing Projects with GNU Make, https://www.oreilly.com/library/view/managing-projects-with/0596006101/
References¶
- GNU Make Manual: http://www.gnu.org/software/make/manual/make.html
Makefiles by example¶
This tutorial was adapted from Bruce A. Maxwell's A Simple Makefile Tutorial.
A Simple Example¶
Let's start off with the following three files:
- hellomake.c represents a typical main program,
- hellofunc.c represents a some functional code, and
- hellomake.h represents the include file corresponding to hellofunc.c.
Here is the content of the files:
-
hellomake.c#include <hellofunc.h> int main() { /* call a function in another file */ apply_pythagorean_theorem(3.0, 4.0); return(0); } -
hellofunc.c#include <hellofunc.h> #include <stdio.h> #include <math.h> float apply_pythagorean_theorem(float a, float b) { printf("Input: a=%g, b=%g\n", a, b); printf("Pythagorean theorem: c=%g\n", sqrt(a*a + b*b)); } -
hellofunc.h/* example include file */ float apply_pythagorean_theorem(float, float);
Normally, you would compile this collection of code by executing the following command:
gcc -I. -o hellomake hellomake.c hellofunc.c -lm
.c files and names the executable hellomake.
The arguments of the command are
- hellomake.c hellofunc.c are the source code files,
- -o hellomake is the output/executable file that will be created,
- -I. tells gcc to look in the current directory (.) for include files,
namely our file hellomake.h,
- -lm tells gcc to link the executable with the math library.
The typical approach to the test/modify/debug (without a Makefile) cycle is to
use the Up arrow in a terminal to go back to your last compile command so you
don't have to type it each time, especially once you've added a few more .c
files to the mix.
Unfortunately, this approach to compilation has two downfalls. Problem #1, if
you lose the compile command or switch computers you have to retype it from
scratch, which is inefficient at best. Problem #2, if you are only making
changes to one .c file, recompiling all of them every time is also
time-consuming and inefficient. So, it's time to see what we can do with a
Makefile.
Defining rules¶
The key functionality of Makefiles are rules. A rule consists of
- a name followed by a : colon,
- dependencies (files to monitor for changes) after the : colon, and
- lines of commands (as we would type in the terminal).
Abstract rule:
[RULE_NAME]: [DEPENDENCY_FILE_1] [DEPENDENCY_FILE_2] [...]
[COMMAND_1, after a Tab]
[COMMAND_2, after a Tab]
[...]
Important: There must be a Tab at the beginning of a command!¶
Note that there is a Tab before every command in the Makefile that is part
of a rule. make will not be happy if is missing or replaced by Space
characters.
The first Makefile¶
The simplest Makefile would look something like this.
Makefile v1:
hellomake: hellomake.c hellofunc.c
gcc -I. -o hellomake hellomake.c hellofunc.c -lm
If you put this rule into a file called Makefile (or makefile is ok, too)
and then type make on the command line it will execute the compile command as
you have written it in the Makefile.
Note that make with no arguments executes the first rule in the file.
Furthermore, by putting the list of files on which the rule depends on after the
: colon, make knows that the rule hellomake needs to be executed if any of
those dependency files change.
Hands-on: Run make again and it will know not to recompile. Change one of the
.c files and make will recognize the need to recompile.
(Unfortunately, if you were to make a change to the header file, hellomake.h,
make would not recompile the .c files, even though they needed to be. It
will be fixed later.)
Note: To force a recompilation, you must delete the executable manually:
rm hellomake
At this point, we solved problem #1 and we can avoid using the Up arrow repeatedly, looking for the last compile command.
Hands-on: Practice inventing some of rules on your own and putting (harmless) terminal commands to be executed, for example
where_am_i:
pwd
make where_am_i
Separate compilation commands for separate source files¶
To make the compilation more efficient, in terms of compiling source files separately and only if they have been changed.
Makefile v2:
# `make` will run the first rule by default
hellomake: hellomake.o hellofunc.o
gcc -o hellomake hellomake.o hellofunc.o -lm
hellofunc.o: hellofunc.c hellofunc.h
gcc -I. -c -o hellofunc.o hellofunc.c
hellomake.o: hellomake.c
gcc -I. -c -o hellomake.o hellomake.c
By putting the object files, hellomake.o and hellofunc.o in the dependency
list and in the rule, make knows it must first compile the .c versions
individually, and then build the executable hellomake.
Hands-on: Change one of the .c files and make will recognize the need to
recompile only that one file.
Note: To force a recompilation, you must delete all compiled files manually (we will address that later):
rm *.o hellomake
Using variables¶
In order to make the implementation of the Makefile more efficient, we define variables.
To define a variable, we write
VARIABLE_NAME=DEFINE_SOMETHING
$(VARIABLE_NAME)
Special variables¶
Additionally, the Makefile syntax provides predefined variables.
These are special variables:
- $@ refers to the target name (left side of the : colon),
- $^ refers to the list of dependencies (right side of the : colon), and
- $< refers to only the first dependency (first item on right side of the : colon).
These variables make the overall compilation rule more general or generic.
Let's adapt the previous Makefile.
Makefile v3:
# set compiler
CC=gcc
# set compiler flags
CFLAGS=-I.
# set library flags
LIBS=-lm
# `make` will run the first rule by default
hellomake: hellomake.o hellofunc.o
$(CC) -o $@ $^ $(LIBS)
hellofunc.o: hellofunc.c hellofunc.h
$(CC) $(CFLAGS) -c -o $@ $<
hellomake.o: hellomake.c
$(CC) $(CFLAGS) -c -o $@ $<
This Makefile works identically to the previous one, but it is much cleaner and simpler to modify and generalize.
Pattern rules¶
Similarly to predefined, or special, variables there are predefined patterns for
rules. A pattern rule looks like an ordinary rule, except that its target
contains exactly one % percent character, for instance,
- %.c matches any file name that ends with .c
- %.o matches any file name that ends with .o
The pattern rules allow to reduce the number of rules dramatically.
Makefile v4:
# set compiler
CC=gcc
# set compiler flags
CFLAGS=-I.
# set library flags
LIBS=-lm
# `make` will run the first rule by default
hellomake: hellomake.o hellofunc.o
$(CC) -o $@ $^ $(LIBS)
%.o: %.c hellofunc.h
$(CC) $(CFLAGS) -c -o $@ $<
Specifying object and header files in variables¶
To improve the structure of the implementation of our Makefile, we create the
variable OBJS that stores all of the object files that we need to generate.
And we create the variable DEPS that stores the set of all .h files on which
the .c files depend.
Finally, let's also store the program in a variable.
Makefile v5:
# set compiler
CC=gcc
# set compiler flags
CFLAGS=-I.
# set library flags
LIBS=-lm
# set the program name
PROGRAM=hellomake
# list object files
OBJS = hellomake.o hellofunc.o
# list header files
DEPS = hellomake.h
# `make` will run the first rule by default
$(PROGRAM): $(OBJS)
$(CC) -o $@ $^ $(LIBS)
%.o: %.c $(DEPS)
$(CC) $(CFLAGS) -c -o $@ $<
Notice that the two rules are entirely generic and can work for most projects.
Additional rules¶
Since a large project will create many .o files and possibly other files, it
is also very valuable to create a rule to clean up (i.e., remove) the build
files. This can be done with additional rules.
Another very common rule that is put as the first rule, is called all. This
specifies what the default behavior of make should be.
The rules that we will add next are not related to any files, so we need to tell
make to not confuse the rules with files named like them. That is done with
.PHONY; see in the example.
Makefile v6:
# set compiler
CC=gcc
# set remove command
RM=rm
# set compiler flags
CFLAGS=-I.
# set library flags
LIBS=-lm
# set the program name
PROGRAM=hellomake
# list object files
OBJS = hellomake.o hellofunc.o
# list header files
DEPS = hellomake.h
.PHONY: all
all: $(PROGRAM)
$(PROGRAM): $(OBJS)
$(CC) -o $@ $^ $(LIBS)
%.o: %.c $(DEPS)
$(CC) $(CFLAGS) -c -o $@ $<
.PHONY: clean
clean:
$(RM) $(OBJS)
$(RM) $(PROGRAM)
Hands-on: Type make clean to execute the clean rule of the Makefile.
References¶
- Bruce A. Maxwell, A Simple Makefile Tutorial, https://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/
- GNU Make Manual, http://www.gnu.org/software/make/manual/make.html
- Robert Mecklenburg, Managing Projects with GNU Make (book), https://www.oreilly.com/library/view/managing-projects-with/0596006101/