Make Tutorial: How-To Write A Makefile

Many C and C++ projects rely on makefiles for their automated builds. Consequently, knowing how to write or -- at least -- read a Makefile is a valuable skill that every C and C++ programmer should have in his/her tool belt. While tutorials on writing makefiles are very plentiful on the web, the number of GOOD tutorials is, IMHO, very low. Most of the tutorials out there on writing makefiles not only fail to warn readers of non-portable constructs, but frequently direct readers to use them. This Makefile tutorial / how-to guide, on the other hand, will ensure that you are aware of the many downfalls and pitfalls of writing makefiles, what elements of makefiles are portable, and what you will frequently see but should ardently avoid.

Before continuing any further, I should point out that if you are starting a project from scratch, you should not use makefiles! As I have already hinted in my opening paragraph, although makefiles can be made portable, there are many ways to shoot yourself in the foot and make them horrifically non-portable. For that reason, you should use Cross-Platform Make (CMake), when creating new projects, instead of Make. Although CMake has Make in its name, CMake is not configured using Makefiles (although it is capable of generating makefiles). If you would like to learn more about CMake and why you should use it, you may be interested in watching the CMake Google Techtalk, reading Why the KDE project switched to CMake, perusing the CMake Wiki, and bookmarking the CMake Manual. If you choose to use CMake, you may be interested in my C++ Application Project Template and my C++ Library Project Template, both of which will allow you to easily get up and running with a CMake-based application or library project with very little effort or knowledge of CMake.

1 Minute Makefile

program_NAME := myprogram
program_C_SRCS := $(wildcard *.c)
program_CXX_SRCS := $(wildcard *.cpp)
program_C_OBJS := ${program_C_SRCS:.c=.o}
program_CXX_OBJS := ${program_CXX_SRCS:.cpp=.o}
program_OBJS := $(program_C_OBJS) $(program_CXX_OBJS)
program_INCLUDE_DIRS :=
program_LIBRARY_DIRS :=
program_LIBRARIES :=

CPPFLAGS += $(foreach includedir,$(program_INCLUDE_DIRS),-I$(includedir))
LDFLAGS += $(foreach librarydir,$(program_LIBRARY_DIRS),-L$(librarydir))
LDFLAGS += $(foreach library,$(program_LIBRARIES),-l$(library))

.PHONY: all clean distclean

all: $(program_NAME)

$(program_NAME): $(program_OBJS)
    $(LINK.cc) $(program_OBJS) -o $(program_NAME)

clean:
    @- $(RM) $(program_NAME)
    @- $(RM) $(program_OBJS)

distclean: clean


The reference Makefile above should prove sufficient for building almost all simple C and C++ projects, and can easily be modified (by defining program_INCLUDE_DIRS, program_LIBRARY_DIRS, and program_LIBRARIES) to support thirdparty library dependencies. Feel free to copy and paste the above for your own use. If you name the file "Makefile" and store it in the same directory as your ".c" and ".cpp" source files, then running "make" will build an executable whose name matches the value of program_NAME. We will eventually explain how everything in the Makefile above works, but first we need to discuss some of the very basics of Makefiles that are essential to know.

Basic Syntax

  • The pound sign / hash symbol (#) begins a single-line comment.
  • Make is very sensitive about tabs; a line begins with a tab if and only if it is a step in a build rule.
  • Variables are expanded by surrounding a variable's name with parentheses and prefixing the it with a dollar sign (as in $(VAR) ).
  • Variables are declared using := or =, but the former is statically bound while the latter is dynamically bound, and so the former is more efficient.
  • Variables may be augmented using +=, which is equivalent to reassigning with a space followed by the content to the right of the += sign.
  • A name followed by a colon denotes a target. Anything to the right of the colon is interpreted as a dependency of that target.
  • If one or more statements beginning with a tab follows after the declaration of a target, those statements are used to build the target (they are executed by $(SHELL)).

Makefile Name

The make utility looks for a file named "Makefile" in the current directory, and attempts to build the target whose name is given on the commandline. If no target is given on the commandline, then the first target in the file named "Makefile" is built. One can use a name other than "Makefile"; however, doing so necessitates the use of the "-f" option with make to specify that a different file should be used and, therefore, using any name other than "Makefile" for a makefile is very strongly discouraged.

Targets

Make creates a dependency graph out of all targets, which it topologically sorts to build the dependencies in a valid and efficient order. Each target is presumed to correspond to a file in the filesystem of the same name, and the target is built only if at least one dependency is newer than the target. It is sometimes the case that a target does not correspond to an actual file. Such targets are called "phony targets", and one tells make that a target is a "phony target" by writing ".PHONY:" followed by a list of all such phony targets. Specifying a target as a phony target saves Make from having to look for a file, and ensures that the target will always be built even if, for example, an empty file of the same name were created.

Perhaps one of the most useful and confusing features of Make is its ability to deduce intermediate targets, automatically. Make is smart enough to figure out a file ending in ".o" depends on a file with the same basename but which ends in ".c", ".cpp", or some other source file suffix. Consequently, depending on the ".o" file is sufficient for Make to infer the ".cpp" or ".c" file as a dependency. Furthermore, not only does Make know that the ".cpp" or ".c" file is used to create the ".o" file, but it also knows how to create the ".o" file from the ".cpp" or ".c" file, and so simply listing the ".o" file as a dependency is sufficient for Make to build the ".o" file using the system's default C compiler or C++ compiler.

Predefined Stuff

I have already stated that Make already knows about the system's compilers and how to build an object file from a C or C++ source file. This is not the only stuff that Make is aware of. There is actually a huge database of predefined variables and rules that Make knows about. You can get make to print out that database using the invocation make -p

Variables

Variables that are used in Makefiles may come from a large number of places; variables may be defined in the makefile itself, or in Make's large database of predefined variables (more on this a paragraph or so), or in the commandline invocation of Make, or within the environment. If a variable does not exist, expanding it is permitted, but simply yields an empty result.

Both by standard convention and due to the way that Make implements its automatic build rules, the following variables have special signficance:
  • CFLAGS
  • CPPFLAGS
  • CXXFLAGS
  • LDFLAGS
The CFLAGS variable gives a list of flags that should be passed to the C compiler (e.g. you can use this for specifying the version the C language, for specifying warning settings to be used with the C compiler, or for other options specific to the C compiler). The CPPFLAGS variable gives a list of flags that should be passed to the C/C++ preprocessor (e.g. you can use this to add paths to the header file search mechanism). The CXXFLAGS variables gives a list of flags that should be passed to the C++ compiler (use this, for example, to set the version of the C++ language, to specify the warning settings, or for other options specific to the C++ compiler). The LDFLAGS variable gives a list of flags that should be passed to the linker (use this, for example, for specifying libraries against which to link and for augmenting the list of search paths to be used for locating and resolving library dependencies).

It is very important that you allow users of your Makefile to add elements to these special variables on the commandline. For example, someone building your project who wants to debug the project may want to invoke Make using "make CFLAGS=-g CXXFLAGS=-g", which will add the "-g" (debugging enabled) flag to the C and C++ compilers. If you assign values to any of these variables using := or =, then you will have clobbered the user's settings. Consequently, use += to augment such variables, but do not assign to them with := or =.

** PITFALL: It is bad manners to overwrite CFLAGS, CPPFLAGS, CXXFLAGS, or LDFLAGS using := or =, but it is ok to augment them with +=. **

In addition to the various "FLAGS" variables, make defines variables for a variety of tools and utilities. The following variables are automatically defined by make:
  • CC
  • CPP
  • CXX
  • LD
The CC variable specifies the system's default C compiler, the CPP variable specifies the system's default C preprocesor, the CXX variable specifies the system's default C++ compiler, and the LD variable specifies the system's default linker. Note that you should use $(CXX) in lieu of $(LD) when linking a C++ executable (in order to link to the C++ standard library).

It is not uncommon to see programmers who have absolutely no clue what they are doing give explicit definitions for these variables. Don't do it! Make automatically defines these, and overriding any of them (e.g. hardcoding "CXX = g++") makes your Makefile non-portable, as it forces your system to build using a very specific compiler. Another temptation, of the opposite sort, is to simply specify "g++" or "cc" wherever a C++ compiler or C compiler is needed. Don't do that, either. Using $(CXX) and $(CC) makes your Makefile flexible, and allows the compiler to be easily swapped out with another (e.g. by specifying a value for CXX or CC on the commandline), which is invaluable when cross-compiling.

** PITFALL: Use $(CXX) and $(CC) instead of a specific compiler. And never hard-code a value for $(CXX) or $(CC). Let make define them for you! **

At some point in your computer programming career, you are likely to come across an abomination like the following:

main.o : main.c
    gcc -c main.c -o main.o

Not only is the above rule redundant (Make can figure out that "main.o" depends on "main.c" automatically, and can figure out how to build "main.o" from "main.c" without being told how to do so explicitly), but the above is also incredibly detrimental to your build. By ignoring $(CFLAGS) and $(CPPFLAGS), for example (which would not be ignored by the builtin rule for generating "main.o" from "main.c"), every such rule would have to be updated in order to support extra warnings or to add an additional header file search path (whereas, relying on the builtin rules, all object files would be built properly only be changing the CFLAGS and CPPFLAGS variables). In addition, by hard-coding gcc, it is no longer possible to use a different compiler. Now you might say that no one should ever use a compiler other than GCC, and I can certainly sympathize with that (yeah, I agree, Visual C/C++/Studio/.Net is awful), but consider, for example, the case where it is necessary to cross-compile. When cross-compiling you might want to, for example, use arm-elf-gcc instead of the default installation of gcc. Ordinarily, one could cross-compile using arm-elf-gcc simply by setting CC=arm-elf-gcc on the commandline; however, this unnecessary, explicit rule has broken cross-compilation!

Conventional Targets

By convention, every Makefile is expected to support the following targets:
  • all
  • clean
  • distclean
Some other common targets that you may see include:
  • install
  • help
When you invoke "make", it automatically builds the first target listed in the makefile. By convention, this target should be named "all" and should depend on all other targets and/or build products that your makefile is capable of producing. The "clean" target removes the result and any intermediate build products of invoking "make all". The "make distclean" target does everything that "make clean" does, except that it may also remove additional configuration information if applicable.

The install target is responsible for installing the build results onto the local computer. By convention, the install target should be sensitive to a variable named DESTDIR, which allows the destination into which to install the program to be overridden. On UNIX systems, a default of "/usr/local/" should be used if DESTDIR is not provided.

If a "help" target exists, it usually prints a list of all the available build targets with a description of what the target does.

Explaining the 1 Minute Makefile

Now that we have gone over the basics, we can now break up the 1 Minute Makefile line by line, and explain how it works...

#
# The following defines a variable named "program_NAME" with a value of "myprogram". By convention,
# a lowercase prefix (in this case "program") and an uppercased suffix (in this case "NAME"), separated
# by an underscore is used to name attributes for a common element. Think of this like
# using program.NAME, program.C_SRCS, etc. There are no structs in Make, so we use this convention
# to keep track of attributes that all belong to the same target or program. 
#
program_NAME := myprogram



# This is a list of all files in the current directory ending in ".c". The $(wildcard) is a globbing expression. This similar to how the shell expands *.c
program_C_SRCS := $(wildcard *.c)

# This is a list of all files in the current directory ending in ".cpp". The $(wildcard) is used to expand *.cpp to match all files ending in *.cpp in the current directory.
program_CXX_SRCS := $(wildcard *.cpp)

# This names all C object files that we are going to build. It uses a substitution expression, which simply replaces ".c" with ".o"
program_C_OBJS := ${program_C_SRCS:.c=.o}

# This names all C++ object files that we are going to build. It simply uses text substitution to replace ".cpp" with ".o" for all the ".cpp" source files.
program_CXX_OBJS := ${program_CXX_SRCS:.cpp=.o}

# This is simply a list of all the ".o" files, both from C and C++ source files.
program_OBJS := $(program_C_OBJS) $(program_CXX_OBJS)

# This is a place holder. If you used program_INCLUDE_DIRS := ./include, then headers in "./include" would be found with #include <>
program_INCLUDE_DIRS :=

# This is a place holder. If you used program_LIBRARY_DIRS := ./lib, then libraries in "./lib" would be found by the linker.
program_LIBRARY_DIRS :=


# This is a place holder. If you used program_LIBRARIES := boost_signals, then libboost_signals would be linked in.
program_LIBRARIES :=


# This adds -I$(includedir) for every include directory given in $(program_INCLUDE_DIRS)... so if you used ./include, it would expand to -I./include
# Remember that CPPFLAGS is the C preprocessor flags, so anything that compiles a C or C++ source file into an object file will use this flag.
CPPFLAGS += $(foreach includedir,$(program_INCLUDE_DIRS),-I$(includedir))

# This adds -L$(librarydir) for every library directory given in $(program_LIBRARY_DIRS)... so if you used ./lib, it would expand to -L./lib
# Since the LDFLAGS are used when linking, this will cause the appropriate flags to be passed to the linker.
LDFLAGS += $(foreach librarydir,$(program_LIBRARY_DIRS),-L$(librarydir))

# This adds -l$(library) for every library given in $(program_LIBRARIES), so if you used boost_signals, it would expand to -lboost_signals
LDFLAGS += $(foreach library,$(program_LIBRARIES),-l$(library))

# This indicates that "all", "clean", and "distclean" are "phony targets". Therefore, "make all", "make clean", and "make distclean"
# should execute the content of their build rules, even if a newer file named "all", "clean", or "distclean" exists.
.PHONY: all clean distclean

# This is first build rule in the makefile, and so executing "make" and executing "make all" are the same.
# The target simply depends on $(program_NAME), which expands to "myprogram", and that target is given below:
all: $(program_NAME)

#
# The program depends on the object files (which are automatically built using the predefined build rules... nothing needs to be given explicitly for them).
# The build rule $(LINK.cc) is used to link the object files and output a file with the same name as the program. Note that LINK.cc makes use of CXX,
# CXXFLAGS, LDFLAGS, etc. On my own system LINK.cc is defined as: $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH),
# so if CXXFLAGS, CPPFLAGS, LDFLAGS, and TARGET_ARCH are undefined, but CXX is g++, then it will expand to g++ $(program_OBJS) -o $(program_NAME).
#
$(program_NAME): $(program_OBJS)
    $(LINK.cc) $(program_OBJS) -o $(program_NAME)

# Note that the line that starts with $(LINK.cc) is indented with a single tab. This is very important! Otherwise, it will not work.



#
# This target removes the built program and the generated object files. The @ symbol indicates that the line should be run silently, and the -
# symbol indicates that errors should be ignored (i.e., if the file already doesn't exist, we don't really care, and we should continue executing subsequent commands)
#
clean:
    @- $(RM) $(program_NAME)
    @- $(RM) $(program_OBJS)


#
# The distclean target depends on the clean target (so executing distclean will cause clean to be executed), but we don't add anything else.
#
distclean: clean

Recursive Make

The 1 minute makefile assumes that all the source files exist in a single directory. Most likely, there is some hierarchy of folders that you will use. It is very common, in those situations, for build systems to invoke Make recursively. However, using recursive make is a very bad idea!!! Invoking make recursively is incredibly slow! There is even a famous paper entitled Recursive Make Considered Harmful (modeled after Go To Statement Considered Harmful), which discusses how invoking make recursively leads to incredibly slow builds.

That said, it is important to know how to portably invoke make recursively (even though you shouldn't invoke make recursively) in addition to alternatives to recursive make. A common pitfall of invoking make recursively (aside from the fact that it is slow and stupid), is that developers will sometimes hard-code "make" in the recursive invocation. However, not all implementations of "make" are named "make", and so invoking make recursively with "make" is just as bad as hard-coding "g++" or some other specific compiler. Instead, you should use the predefined variable $(MAKE) when invoking make recursively. However, not all versions of make define $(MAKE), and so it is necessary to add the following boilerplate:

ifeq ($(MAKE),)
    MAKE := make
endif

The code above defines the variable MAKE to have a value of "make" if the variable $(MAKE) is equal to the empty string (which would be the case if it were not defined). With this little bit of boilerplate, it is then safe to use the variable $(MAKE) in recursive invocations of make. Such recursive invocations are typically of the form $(MAKE) -C subdirectory . The -C option tells the make program that it should look in the given directory (otherwise, "subdirectory" would be interpreted as the name of a target rather than a directory).

Now that we have discussed how to portably use recursive make, we can talk about how to avoid it... make supports an "include" statement for including other makefiles. Using the "include" syntax, it is possible to add more targets to your makefile without ever having to invoke make recursively. That is essentially the solution to recursive make.

Header File Dependencies

The makefile that I have provided does not specify header file dependencies. This is usually not a problem, but it can be in situations where you have lots of inline functions or template classes where the definitions are updated in the header files. This can be a problem, because if the source file hasn't changed since the last time you built the program, executing "make" will not rebuild the affected object files, which means that the changes to the headers (which are actually meaningful, since you have inline definitions) would not be reflected in the resulting executable. To get around this problem, you can invoke make clean and then rebuild the program; however, that defeats one of the purposes of Make, which is to streamline the build by requiring you to build only the files that actually need to be built (if you invoke "make clean", you would be forced to rebuild everything, even the object files whose source files do not depend on the updated header files and which, therefore, did not need to be recompiled).

The simple solution to this problem is to let Make know about this dependency. While Make is smart enough to figure out that object files depend on a ".c" or ".cpp" source file, it is not smart enough to figure out the dependency on the equivalent header file. In this case, it is necessary to specify such a dependency explicitly. One of the really cool things about makefiles is that we can give this dependency explicitly without overwriting or giving up any of the predefined build rules. In fact, with make, you can add any number of dependencies to a target simply by specifying the target with the dependency or dependencies and with no build rule, multiple times. So, suppose "main.cpp" depends on "main.h" and that "main.h" has some sort of inline function, let's say "inline void say_hello()", and that "main.cpp" just prints out the result of this. We want changing "main.h" to cause "main.o" to be rebuilt. To achieve this, we simply add the following statement in our Makefile, which says that "main.o" depends on "main.h" (in addition to depending on "main.cpp", which it knows automatically):

main.o : main.h

Now, you can write all of these rules out explicitly yourself, but why do all that work when you can do it automatically? Moreover, if you did that by hand, it is very likely that you would add dependencies that at a later date become unnecessary or you might forget to add new dependencies to your Makefile as source files include additional header files. Therefore, manually stating each and every such dependency is really a bad idea. In a pure C project, there is a tool called makedepend, which is capable of generating this list simply by analyzing the include directives in your source files. To use makedepend with your Makefile, you should add the following lines to the end of the Makefile:

depend:
    
makedepend -- $(CPPFLAGS) $(CFLAGS) -- $(program_C_SRCS)

# Don't place anything below this line, since
# the make depend program will overwrite it
# DO NOT DELETE THIS LINE -- make depend depends on it.

If you invoke "make depend", it will cause "makedepend" to be invoked with a list of your C source files, which it will scan for header file inclusions, and it will use $(CPPFLAGS) and $(CFLAGS) for finding any additions to the header search path. The "makedepend" program will overwrite everything below the line "#DO NOT DELETE THIS LINE..." with an automatically generated list of dependencies. Unfortunately, this will not work for a project with C++ source files, since "makedepend" doesn't know how to resolve inclusions from the C++ standard library (e.g. "makedepend" will choke if it sees "#include <vector>", since it doesn't know where to find it), and in order to inform "makedepend" about the location of these headers one would need to make use of a compiler-specific set of paths or a compiler-specific command for accessing the list of header search paths for C++ headers.

As an alternative, if you follow the strict convention that each source file ending in ".c" or ".cpp" has a corresponding header file ending in ".h" (or, equivalently, each object file ending in ".o" depends on a corresponding header file ending in ".h"), then we can do the pattern substitution trick we did for deducing the name of all our object files, and apply it to automatically generate these dependency rules/statements, without writing all of them out explicitly. Now, doing this involves from pretty crafty Makefile tricks, but at the end of the day it looks like:

define OBJECT_DEPENDS_ON_CORRESPONDING_HEADER
    $(1) : ${1:.o=.h}
endef

$(foreach object_file,$(program_OBJS),$(eval $(call OBJECT_DEPENDS_ON_CORRESPONDING_HEADER,$(object_file))))

What the above does is creates a template (a block of text that can be reused with parts of the text replaced) that has been aptly named "OBJECT_DEPENDS_ON_CORRESPONDING_HEADER". The template takes a single argument which is bound to the variable named "1" (and dereferenced/expanded using $(1) or dereferenced/expanded with text substitution using ${1:.o=.h}). The template, as you can probably figure out, makes its parameter (which is going to be the name of an object file ending in ".o") depend on the header file with the same basename but ending in ".h" (which is expressed with the expansion with textual substitution). Then, at the very end, we iterate over each object file (which we store in a variable named object_file) from the list of all object files (given in $(program_OBJS)), and we evaluate the template with the first argument (that is, the variable $(1)) bound to the value of the variable object_file (which stores the name of the current object file over which we are iterating).

So, putting all of that together and adding it to our 1-minute makefile (now it's more like a 2-minute makefile), gives us the following complete Makefile:

program_NAME := myprogram
program_C_SRCS := $(wildcard *.c)
program_CXX_SRCS := $(wildcard *.cpp)
program_C_OBJS := ${program_C_SRCS:.c=.o}
program_CXX_OBJS := ${program_CXX_SRCS:.cpp=.o}
program_OBJS := $(program_C_OBJS) $(program_CXX_OBJS)
program_INCLUDE_DIRS :=
program_LIBRARY_DIRS :=
program_LIBRARIES :=

CPPFLAGS += $(foreach includedir,$(program_INCLUDE_DIRS),-I$(includedir))
LDFLAGS += $(foreach librarydir,$(program_LIBRARY_DIRS),-L$(librarydir))
LDFLAGS += $(foreach library,$(program_LIBRARIES),-l$(library))

.PHONY: all clean distclean

all: $(program_NAME)

$(program_NAME): $(program_OBJS)
    $(LINK.cc) $(program_OBJS) -o $(program_NAME)

clean:
    @- $(RM) $(program_NAME)
    @- $(RM) $(program_OBJS)

distclean: clean

define OBJECT_DEPENDS_ON_CORRESPONDING_HEADER
    $(1) : ${1:.o=.h}
endef

$(foreach object_file,$(program_OBJS),$(eval $(call OBJECT_DEPENDS_ON_CORRESPONDING_HEADER,$(object_file))))

Note, though, that the solution above is imperfect.... if you have an indirect dependency on a header file, it still won't be updated correctly. So, you will still need to invoke "make clean" before invoking "make" if you have a header file that affects source files other than the source file with the corresponding name, which -- for me -- is about 99% of the time.

Because both the textual substitution and the makedepend route are imperfect and will still require the use of "make clean" (or "make depend"), my own preference (assuming I am forced to use Make instead of CMake) is to simply use the 1-Minute Makefile presented at the start of this tutorial, and to simply invoke "make clean" when header files are updated.

More About Make

I've already highlighted several of the common problems that you should avoid when writing makefiles, these are:
  • Not taking advantage of the automatic build rules.
  • Overwriting the value of the various "FLAGS" variables, instead of simply augmenting them.
  • Overwriting the value of the various variables representing system programs and utilities.
  • Hard-coding a compiler, linker, preprocessor, or any other system utility.
  • Hard-coding an implementation of make when invoking make recursively.
  • Invoking make recursively, at all, instead of using "include" with makefiles.
I have also given you a very quick and dirty look at make. If you want to learn more, then I strongly suggest you read the GNU Make Manual.

Download the Makefile

I figure that some of you will want to download and try-out the Makefile that I have described in this tutorial. The problem with copy-and-paste, though, is that it doesn't always treat tabs the way that it ought to. Therefore, I have created a hyperlink where you can download this Makefile, with the tabs properly formatted. You can download the Makefile at the link.
Subpages (1): Files
Comments