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:
Some other common targets that you may see include:
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.