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.
The reference Makefile above should prove sufficient for building almost all simple C and C++ projects, and can easily be modified (by defining
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.
Both by standard convention and due to the way that Make implements its automatic build rules, the following variables have special signficance:
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
** PITFALL: It is bad manners to overwrite CFLAGS, CPPFLAGS, CXXFLAGS, or LDFLAGS using
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:
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:
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!
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.
Recursive Make Considered Harmful (modeled after Go To Statement Considered Harmful), which discusses how invoking make recursively leads to incredibly slow builds.
# 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
# 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.
# This names all C object files that we are going to build. It uses a substitution expression, which simply replaces ".c" with ".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.
# This is simply a list of all the ".o" files, both from C and C++ source files.
# This is a place holder. If you used program_INCLUDE_DIRS := ./include, then headers in "./include" would be found with #include <>
# This is a place holder. If you used program_LIBRARIES := boost_signals, then libboost_signals would be linked in.
# 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.
# 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.
# This adds -l$(library) for every library given in $(program_LIBRARIES), so if you used boost_signals, it would expand to -lboost_signals
# 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.
# 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:
# 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).
# 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)
# The distclean target depends on the clean target (so executing distclean will cause clean to be executed), but we don't add anything else.
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:
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
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.
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):
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:
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:
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
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:
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.
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.
Software Engineering >