17.5. make and Makefiles
In this section we discuss the make
command and Makefiles. We present some
examples starting from simple Makefile and building up to a more complex
and more generic Makefile (Section 17.5.10) that is a good starting point
for most uses. For more information about writing Makefiles see
the GNU Make Manual.
17.5.1. About make
The Unix make
utility simplifies compiling programs.
A user (or a program) writes the Makefile with the rules to execute.
A user just runs make
and the make
program reads in rules from the
Makefile
that tell it how to compile a program.
One benefit of make
is that it can execute complex commands to build
executable files simply by typing make
versus the user having to type in long
gcc
commands to build each time. Larger programs are often split into
separate modules consisting of multiple .c
and .h
files. They may also
link in multiple libraries, or store header and library files in
non-standard locations. All of these make compiling more complicated,
and gcc
command lines longer and more complex. In these cases,
manually typing the full gcc
command line at the shell prompt
is both time consuming and error prone. For example, here is a long
gcc
command line that would be tedious to type by hand:
gcc -Wall -g -I../includes -I. -o mycc emitter.c init.c lexer.c symbol.c parser.c ast.c error.c symbol_table.c codegen.c emitcode.c main.c -L../libs -lmylib -lpthread
With make
the user simply types make
to execute the commands
listed in the Makefile.
Another benefit of make
is that it only rebuilds those things that need to
be rebuilt --- just the object and executable files that depend on files that
have been modified since the last time they were built. For large programs,
compiling everything from scratch can be very time consuming, and thus
make
can save a lot of compilation time by only rebuilding those things
that need to be rebuilt.
A user typically writes the Makefile with the rules for make
to execute.
There are also programs, like CMake and GNU Autotools, that generate
Makefiles. These are
typically used for building large software and for easily supporting
compilation across different systems that includes finding needed compilers
and other software, libraries, and header files on a particular system.
17.5.2. Using make
The steps for using make
are:
-
Create a Makefile listing the rules for building the executable. By default the file should be named 'Makefile'. This only has to be done once, except when new modules (.c or .h files) are added to the program, then the Makefile needs to be updated to add new module dependencies to existing rules or to add new rules to include them in compilation.
-
After editing program file(s), rebuild the executable by typing make:
$ make
A specific rule in the Makefile can be executed by typing:
$ make target_label
For example, to execute the
rm
command to remove all built files in the example Makefile listed below, type:$ make clean
17.5.3. Creating a Makefile
A Makefile typically starts with some variable definitions which are then followed by a set of rules (or target entries) for building specific targets (typically for building .o and executable files). Each rule consists of a target label and a set of associated commands (sometimes called recipes) that are run for the target. The following is the general format of a rule:
# comment # (note: the <tab> in the command line is necessary for make to work) target: dependency1 dependency2 ... <tab> command
Here is an example:
# target entry to build program executable from program.o # and util.o object files # program: program.o util.o gcc -o program program.o util.o
Make and whitespace characters
|
17.5.4. A Simple Makefile
Here is an example simple Makefile that builds an executable
from a single .c file named myprog.c
:
# build an executable named myprog from myprog.c all: myprog.c gcc -g -Wall -o myprog myprog.c clean: rm myprog
Typing make
triggers the command associated with the all
target
label to run gcc -g -Wall -o myprog myprog.c
if the modification date on
the file myprog.c
is newer than the modification date on the file myprog
or if myprog
doesn’t exist. Note that the name all
isn’t special,
by default make
picks the
first target label in the Makefile to execute. To execute
a specific rule other than the first one listed in the makefile, list
the target name on the make
command line. For example,
make clean
runs the rm myprog
command to remove the previously
built executable file myprog
.
In Section 17.5.8 we show how to use Makefile variables to write more generic versions of this Makefile—one that can be copied and minimally edited to create a new Makefile for building a different program.
17.5.5. A Makefile for multiple source files
Below is an example Makefile for building an executable from multiple .c
and
.h
files. It first compiles each .c
into a .o
file, and then
links the .o
and libraries together to build the executable. Because
it lists dependencies for each .o
file, if one .c
file is modified only
one .o
needs to be rebuilt (the one that is dependent on the modified
.c
) and it does not rebuild all .o
files. This is a particularly nice
feature that speeds up compile time for larger programs consisting of many
.c
files.
# Example Makefile for a program with many .c modules # make: build count executable program # make clean: clean-up all built files all: count # To create the executable file count we need the object files # countwords.o, counter.o, and scanner.o: count: countwords.o counter.o scanner.o gcc -g -Wall -o count countwords.o counter.o scanner.o # To create the object file countwords.o, we need the source # files countwords.c, scanner.h, and counter.h: countwords.o: countwords.c scanner.h counter.h gcc -g -Wall -c countwords.c # To create the object file counter.o, we need the source files # counter.c and counter.h: counter.o: counter.c counter.h gcc -g -Wall -c counter.c # To create the object file scanner.o, we need the source files # scanner.c and scanner.h: scanner.o: scanner.c scanner.h gcc -g -Wall -c scanner.c # To start over from scratch, type 'make clean'. This # removes the executable file, as well as old .o object # files and *~ backup files: clean: rm -f count *.o *~
After running make
the first time, if the counter.c
file is modified,
then on the next run of make
, only these two rules are executed
(due to their dependency on counter.c
or counter.o
):
counter.o: counter.c counter.h gcc -g -Wall -c counter.c count: countwords.o counter.o scanner.o gcc -g -Wall -o count countwords.o counter.o scanner.o
In Section 17.5.9 and Section 17.5.10 we show how to use
Makefile variables, implicit Makefile rules, and automatic
dependency generation, to write more generic versions of
a Makefile. The Makefile in Section 17.5.10 is a good generic
Makefile for applications built from many .c
and .h
files, and that may build with header files or libraries
stored in non-standard locations.
17.5.6. make Errors
Errors in make
generally stem from one of two causes. The first
are errors in the syntax of the Makefile itself, and the second are
compilation errors either due to missing rules in the Makefile, or due
to errors in the program itself. When using make
it can sometimes
be difficult to determine if you need to fix the Makefile or your program.
Errors in Makefile syntax will be reported with the line number in the Makefile of the error to help you track down the syntax problem. However, there are some common errors that are difficult to see because they are related to errors with use of whitespace characters. Two example of this type are:
-
Using space characters instead of a
<tab>
at the start of the command part of a target entry. For example, if there are space characters at the beginning of this target entry’s second line instead of a<tab>
character,make
cannot correctly parse this rule:all: myprog.c $(CC) $(CFLAGS) $(INCLUDES) -o myprog myprog.c
-
Having extra spaces at the end of the prefix of a file name that the Makefile expands in other places by adding different suffixes to. For example:
TARGET = myprog SRC = $(TARGET).c
If there is trailing whitespace after
myprog
, then instead ofSRC
beingmyprog.c
it will be something likemyprog .c
, which is not a valid file name.
If the error is with compiling or linking, it could be due to errors in your
Makefile or due to an error in your program. Some examples Makefile errors
that cause compilation or linking errors include: a missing rule to compile a
.o
; missing dependencies not forcing re-compilation; missing explicitly
linking in a needed library; or missing including paths (-I
or -L
)
to header file and library locations needed to compile the program.
Many of these errors will show up as gcc
errors, but require fixes to
the Makefile instead of the program source.
Some tips to help you determine if the Makefile, the source code, or both need to be fixed include:
-
As a first step to see if you are missing a dependency, run
make clean
and thenmake
to rebuild everything from scratch. If it is successful, a dependency is missing in the Makefile. -
If you don’t see a
.o
file that you need to link in, the problem is that you need to add a rule to build it (perhaps updating yourSRCS
definition to include this new file). -
If there is an error with the compiler not being able to find a
.h
file, then this indicates that you need to update yourINCLUDES
variable to add a of the form-I[path to include directory]
to tell make where to find them. -
Similarly, if there are errors with the compiler not being able to find libraries (these show up as undefined errors during the final compilation stage, linking), you need to update your
LIBDIR
orLIBS
definitions to include paths to libraries (-L
) or to add to the command line any missing libraries that need to be linked in (-l
). -
In some cases when the error is in a C source file, such as missing
#include
, both the C source file and the Makefile may need to be updated to fix the error. For example, adding an#include
to a C source file, may also result in updating the include (-I
) path, and in some cases also a library path (-L
) and explicitly linking in a library (-l
) in the Makefile.
17.5.7. Parallel make
For large programs consisting of many .c
and .h
files, compiling can
take a long time. Much of the time can be due to compiling a .o
file from
each .c
file. Compiling each .o
is independent of compiling other .o
files, and thus this is a good target for parallelization where multiple
threads or processes can simultaneously compile a different .c
file in
parallel. Using the -j[num]
command line option to make
tells it to
run up to num
jobs at a time, or to in parallel simultaneously
execute num
different recipes.
For example, to parallelize make
to perform 12 recipes at a time type:
make -j12
On a machine with 12 or more cores, this will run about twelve times faster
than make
.
17.5.8. Advanced: A Generic Simple Makefile
We can rewrite the simple Makefile from Section 17.5.4
to make it more generic. A generic Makefile is one that is easier
to edit for use for another program
(typically users start a new Makefile with a copy of an existing one that
they then modify versus writing one from scratch each time). This more
generic Makefile replaces the specific application file names in rules with
Makefile variables resulting in the rule being generic for any program.
The syntax for defining Makefile variables is
<variable_name> = <value>
. For example:
# define CC to be the name of the compiler CC = gcc
Once defined, the Makefile variable can be used later in the Makefile
using $(variable_name)
syntax. For example:
# use the value of CC in a target entry: all: $(CC) -o myprog myprog.c
Below is an example of a slightly more generic version of the simple Makefile
above to build an executable from a single .c file. This more generic
version of the simple Makefile specifies variables to indicate which
compiler to use (CC
), the compiler flags to pass (CFLAGS
, the name
of the .c
source file (SRCS
), and the name of the program
executable to build (TARGET
). The RM
variable is a builtin Makefile
variable that is defined to be the rm
command. You can change the
value of any builtin variables by redefining them to be something else
in the Makefile.
By using Makefile variables, the result is that everything in the
Makefile starting with all:
is generic and will work for any program.
Only the SRCS
and TARGET
variables need to be changed to build
a different program.
# define the compiler: gcc for C programs CC = gcc # define the compiler flags: # -g adds debugging information to the executable file # -Wall turns on most, but not all, compiler warnings CFLAGS = -g -Wall # source file (could actually list multiple .c files here) SRCS = myprog.c # define the executable file (assume source is same name): TARGET = myprog # this is generic: all: $(TARGET) $(TARGET): $(SRCS) $(CC) $(CFLAGS) -o $(TARGET) $(SRCS) clean: $(RM) $(TARGET)
This Makefile can also be used for building an executable
from more than one .c
source file by just defining SRCS
to list
each c
file. For example, if the executable is built from two
source files myprog.c
and util.c
, define SRCS
to be:
SRCS = myprog.c util.c
One thing to note about this Makefile is that it always recompiles the
executable from all source files listed in SRCS
if any one of them
have been modified (because of this dependency: $(TARGET): $(SRCS)
).
For small programs, that may consist of just one or a few .c
files,
this may be fine. However, we can adjust the Makefile to
have it only rebuild parts that need to be rebuilt when a source file
is modified. We do this by adding rules to compile object files
(.o
) from each .c
file, and then to build the executable file
from linking the .o
files (and any libraries) into the executable.
17.5.9. Advanced: A Generic Makefile for Multiple Files
We can rewrite the Makefile from Section 17.5.5 that compiled an executable from multiple source files as a more generic Makefile by making use of Makefile variables and pattern replacement rules.
Note that the rules for building the .o
files from the .c
in the orginal Makefile (in Section 17.5.5)
are all similar except for the specific file names. It would be
nice if we could simplify this with a more generic rule for building
.o
from .c
files.
One way to do this is to use a pattern-replacement
rule that is triggered for any .o
file built from a .c
file.
Below is an example of such a rule. It uses makefile automatic
variables $<
for the name of the dependency of the rule (a .c
source file)
and $@
for the target file (the .o
object file).
See the gnu make manual for more information about automatic variables.
# This is a pattern replacement rule for building .o files from their # associated .c files. It uses automatic variables: # $< the name of the first dependency of the rule (a .c file) # $@ the name of the target of the rule (a .o file) %.o : %.c $(CC) $(CFLAGS) -c $< -o $@
This is a much shorter rule, and a generic rule, that will work for any C Makefile.
However, we can do even better than this! Because compiling a .o
from a
.c
file is such a common build pattern, make has an implicit rule for
building .o
files from .c
files. If we want to use this
implicit rule, we don’t add any rule for building .o files from .c files
to our makefile. The implicit rule is the following:
# make's implicit rule for building .o files from .c: %.o : %.c $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
The implicit rule uses some makefile variables that have default values, but that can also be redefined in the Makefile (and usually are):
-
CC
: defines the compiler (our Makefile definesCC
to begcc
) -
CFLAGS
: defines compiler flags (our Makefile defines these to be-g -Wall
which includes debugging information and turns on all warnings). -
CPPFLAGS
: defines preprocessor flags (our Makefile doesn’t define any). The preprocessor is the first part of the compiler that runs. It expands anything starting with a#
, like#include
. Here is some more information about the the phases of compilation.
Because we have a rule for building $(TARGET)
that has a dependency
$(OBJS)
, make
will look for a rule for building object files (.o
) from
source files (.c
files), and it if doesn’t find one in the Makefile, it
uses its implicit rule. If the implicit rule suffices, we don’t have
to include any rule for building object files in our Makefile! Instead we
just define the makefile variables it uses (or use its default definitions).
If we don’t like the implicit rule, then we need to override it using our
own rule.
The following is a version of the Makefile that uses the implicit rule
for building .o
files (and note that everything in the Makefile
starting with OBJS =
is generic!):
# Example Makefile using make's implicit .c:.o rule # make: build count executable program # make clean: clean-up all built files # define the compiler CC = gcc # define compiler flags CFLAGS = -g -Wall # define the executable file TARGET = count # define source files SRCS = countwords.c counter.c scanner.c # define the object files # This uses Suffix Replacement within a macro: # $(name:str1=str2): for each word in 'name' replace 'str1' with 'str2' # Here replace the suffix .c of all words in the macro SRCS with .o suffix # OBJS = $(SRCS:.c=.o) # typing 'make' will invoke the first target entry in the file all: $(TARGET) # To create the executable file count we need the object files # countwords.o, counter.o, and scanner.o: # $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) # using implicit rule to build .o files from .c! # clean up built files clean: $(RM) -f $(TARGET) *.o *~
This is a short and generic Makefile. However, there is one subtle
problem with this that we don’t have with the longer less generic version:
we have lost the dependencies of the header files on
the object files, meaning that if the user updates a header file and
types make
the executable and .o files will not be rebuilt, but we
want them to be. To fix this problem, we can add additional dependencies
to the Makefile for TARGET
and OBJS
or for individual object files:
# define a variable to be the set of header files HDRS = scanner.h counter.h # add a dependency to this rule: $(TARGET): $(OBJS) $(HDRS) # add a generic dependency on all $(OBJS) # (note: no command part to this rule, it is just defining a dependency): $(OBJS): $(HDRS) $(SRCS)
The resulting makefile looks like:
# Example Makefile with implicit .c:.o rule and header file dependencies # make: build count executable program # make clean: clean-up all built files # define the compiler CC = gcc # define compiler flags CFLAGS = -g -Wall # define source files SRCS = countwords.c counter.c scanner.c # define the object files # This uses Suffix Replacement within a macro: # $(name:str1=str2): for each word in 'name' replace 'str1' with 'str2' # Here replace the suffix .c of all words in the macro SRCS with .o suffix # OBJS = $(SRCS:.c=.o) # define the header files HDRS = scanner.h counter.h # define the executable file TARGET = count # typing 'make' will invoke the first target entry in the file all: $(TARGET) # To create the executable file count we need the object files # countwords.o, counter.o, and scanner.o: # $(TARGET): $(OBJS) $(HDRS) $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(OBJS): $(HDRS) $(SRCS) # clean up built files clean: $(RM) -f $(TARGET) *.o *~
With added dependencies of .h files added, changes to either a .c or a .h
file will trigger rebuilding of .o and executable files. There is, however,
one subtle inefficiency with this solution. The problem is with the generic
dependency of objects on all sources and headers, which means that
some .o files will be unnecessarily rebuilt when a specific .h or .c
file is updated. For example, scanner.o
will be rebuilt if counter.h
is
updated, but it does not need to be. The solution to this is to more
explicitly enumerate the specific dependencies on each .o file in the
Makefile, making the dependencies less generic and more specific for each
.o
file. For example, replace $(OBJS): $(HDRS) $(SRCS)
in the above
Makefile with:
count.o: count.c count.h scanner.o: scanner.c scanner.h countwords.o: countwords.c scanner.h counter.h
Adding these specific dependencies makes the Makefile less generic, but it
avoids unnecessary compilation with the more generic
$(OBJS): $(HDRS) $(SRCS)
.
For small programs the more generic solution might be fine, but for larger
programs carefully enumerating the dependencies for each .o file is
important to avoid a lot of unnecessary re-compilation, and also to ensure
that a .o file is rebuilt when a file it depends on is modified.
Because of this, there are ways to automatically generate a
full set of dependencies for each .o file (and for each executable).
Using automatic dependency generating tools is almost necessary for
Makefiles for building programs from many source and header files in order
to ensure correct compilation when a file is modified.
One such tool is makedepend
, which examines .c and .h files and adds
dependencies to the end of a Makefile.
Another is to use the gcc -MD
(make dependencies)
precompiler flag to generate dependency files (.d
) and to include these
files in the Makefile.
17.5.10. Advanced: A Generic Featureful Makefile
Our last makefile example is a generic general-purpose makefile that includes a more complete set of commonly used Makefile variables for compiling C programs, and it also demonstrates some more advanced syntax and features, specifically:
-
linking in libraries (
-l
) -
defining link and include paths (
-L
and-I
) for finding library and header files that are not in default locations. -
auto generating file dependency rules (
-MD
) and including them in a makefile (include
).
This is also a good example Makefile to copy and edit for your own use
In general, C programs have many libraries implicitly linked into them, but
sometimes other libraries need to be explicitly linked in using the -l
compiler flag. The gcc
compiler looks in default locations for library
code, but for libraries that are not in those locations, a link path needs to
be explicitly included in the gcc
command line. These are added to the
makefile using -L
. Similarly, header files also may be located in
non-standard locations, and then the Makefile needs to specify an include
file path definition to the location containing them. Include paths are
given using -I
.
Finally, for a program with multiple .c
and .h
files, it is useful
to automatically generate dependencies.
In this example we use the gcc
-MD
(make dependencies)
precompiler flag (added to the CPPFLAGS
definition). It creates dependency
files (.d
) associated with each .o
file during the preprocessing phase of
compilation (the first phase). These dependency files are then included in
the Makefile (include
). There is an implicit dependency of .d
files on
.c
files, so if a .c
file is modified both its .o
and .d
are
regenerated. Also, the -MP
flag is typically used with the -MD
flag,
which suppresses errors if header files are renamed.
Here is the resulting Makefile, which is a good generic makefile to copy and modify for your own use:
# Example Makefile to copy and edit for your own use # make: build .d, .o, and executable target # make clean: remove built files # define compiler CC = gcc # define compiler flags CFLAGS = -Wall -g # define include paths: # define any non-default paths to header files (.h) used by this program # (default location, /usr/include/, is searched by gcc last) INCLUDES = -I../includes -I. # define preprocessor flags # -MD and -MP flags create dependency files (.d) for all SRCS CPPFLAGS = -MD -MP $(INCLUDES) # library paths # define any non-default paths to where libraries used by the program may be # (default location, /usr/lib/, is searched by gcc last) LFLAGS = -L../lib -L/home/newhall/lib # define libraries to link into executable # -lm: std math library in /usr/lib (named libm.so or libm.a) # -lmylib: is a library not in standard location, so -L paths # are used to find it (named libmylib.so or libmylib.a) LIBS = -lm -lmylib # set of source files # (\ is the line continuation character) SRCS = emitter.c init.c lexer.c symbol.c parser.c ast.c \ error.c symbol_table.c codegen.c emitcode.c main.c # set of object files # obtained from SRCS using suffix replacement rule OBJS = $(SRCS:.c=.o) # executable file name TARGET = mycc # .PHONY tells make that these targets are not file names but # the target labels for a set of commands to run when `make [target label]` # is invoked (e.g., `make clean` executes a `rm` command) # .PHONY: clean # the rest of this is generic and should not need to be edited: all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) -o $(TARGET) $(OBJS) $(LFLAGS) $(LIBS) # just use make's built-in rule (.c.o:) to build .o from .c files! clean: $(RM) *.o *~ $(TARGET) *.d # include the dependency files here # this uses a suffix replacement within a macro $(name:string1=string2): # (for each word in 'name' replace 'string1' with 'string2') # here we replace the suffix .c with .d for every name in SRCS # # the '-' before 'include' suppresses error messages when a .d file # doesn't already exist -include $(SRCS:%.c=%.d)
The INCLUDES
variable defines paths to search to find .h
files that
are needed to compile .c
files to .o
for any .h
. The path only needs
to be specified if .h
files are not in the current working directory or
in locations that gcc
checks by default.
During the linking phase of compilation, the compiler links the math library
-lm
and a local library -lmylib
defined by the LIBS
variable
that it
finds given the library path definitions in the LFLAGS
variable
specifying paths to search. The path only needs to be specified in cases
where the
library file is not in locations that gcc
automatically searches by
default.
Running make
using this makefile will first generate .d
files for each
.c
file because we define CPPFLAGS
to have -MD
, which means make
dependency files. If you run ls
you will see these .d
files:
$ make $ ls Makefile ast.c ast.d ast.o codegen.c codegen.d codegen.o ...
The dependency files are included in the Makefile in the last line:
-include $(SRCS:%.c=%d)
This uses a suffix replacement rule (SRC:%.c=%d
) to specify including every
.d
file, and the -
before include
suppresses
error messages if the .d
file doesn’t already exist.
For more details, see automatically generate makefile dependencies
from microHOWTO.
This Makefile also uses make’s built-in rule to build .o
files from
.c
files:
.c.o: $(CC) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) -c $< -o $@
Because we set CPPFLAGS
to include the INCLUDES
as well as the
command options to create the .d
dependency files, we
can use the built-in rule.
If we didn’t add INCLUDES
to CPPFLAGS
, then we could override
the built-in rule in the Makefile with our own (sometimes it is necessary
to override a built-in command like this, but this is not an example
where it is):
... INCLUDES = -I../includes/ -I. CPPFLAGS = -MD -MP ... # override built-in command with our version: .c.o: $(CC) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) -c $< -o $@
17.5.11. Advanced: Makefile Generators
For very large programs, and particularly for software that may be installed on different systems, using a program that generates a Makefile for a particular system makes managing different build targets considerably easier. GNU Automake (part of GNU Autotools library) and CMake are two examples of makefile generating programs. Both take user-specifications in a config file to find libraries and create dependencies on specific systems and auto generate Makefiles for that system. We don’t discuss either here, but our references section has links to more information about both.
17.5.12. References
Here are a few references for using make and writing makefiles:
-
the GNU Make Manual from gnu.org.
-
automatically generate makefile dependencies from microHOWTO
-
Understanding and Using Makefile flags by Aniket Bhattacharyea
-
GNU Automake from gnu.org.
-
CMake from cmake.org.