While a Makefile can be used to do many things, the focus here will be on the construction of a single executable from a set of C source files. The discussion will not delve into the many of the options, control structures, and special commands that are available - only a little more than the very basics will be covered.
It is assumed that the reader has some familiarity with compiling C code.
gcc -o myprogram myprogram.cis sufficient to compile a simple program into an executable. It could be argued that such a simple program really doesn't need a Makefile at all - and perhaps that is true in most cases. However, in many cases more than one source file is needed to create an executable:
gcc -c part1.c gcc -c part2.c gcc -c main.c gcc -o myprogram part1.o part2.o main.oSuddenly, keeping track of what portions need to be re-compiled before linking ("Did I change something in part2.c, or was that just in part1.c?") becomes non-trivial, and gets much worse as the number of source files increase.
The idea behind a Makefile is simple: with the single command:
makea sequence of commands can be carried out. As an added bonus, Makefiles can be taught about the dependencies between the files: e.g. that if part2.c gets changed, then part2.o needs to be re-built. The best part is that the Makefile is very good at keeping track of these sorts of dependencies - in fact, that is what it was designed to do!
Makefiles consist of a series of rules in a simple flat file format (i.e. your average ASCII text file). Each rule is of the form:
target: requirements target build instructionsNote that the target build instructions have a TAB in front of them - not spaces (Don't ask!). If you use spaces instead of TABs, you will get an error message something like:
Makefile:2: *** missing separator. Stop.or
"Makefile", line 2: Need an operator Fatal errors encountered -- cannot continueWhile there are a few "magical" characters, the only one to be concerned with at this point is the ":", which separates the target from the requirements.
Before target can be built, all the requirements must be in place. In other words, the target depends on the requirements. Each of those requirements, however, can be the target of another rule. If all the requirements are in place, then the target build instructions are performed to construct target. In most cases, the primary target of a Makefile is defined by the first rule in the Makefile. Consider the following makefile:
# A simple Makefile myprogram: main.o part1.o part2.o gcc -o myprogram main.o part1.o part2.o part1.o: part1.c part1.h header.h gcc -O -c part1.c part2.o: part2.c header.h gcc -O -c part2.c main.o: main.c header.h gcc -O -c main.c clean: rm -f myprogram main.o part1.o part2.oSince this Makefile describes how to construct a program called 'myprogram', it is not surprising that the rule for 'myprogram' is also the main target of this Makefile. For this Makefile, the result of doing a 'make' will be the construction of 'myprogram'. The lines:
myprogram: main.o part1.o part2.o gcc -o myprogram main.o part1.o part2.oindicate that three pieces: main.o, part1.o, and part2.o are required to construct 'myprogram'. If main.o does not exist, then 'make' searches for an appropriate rule to build main.o. As is seen in the Makefile, there are additional rules which specify how each of these three pieces are to be constructed. Let us consider a small portion of the above in more depth:
part1.o: part1.c part1.h header.h gcc -O -c part1.cThis says that in order to construct part1.o, the files part1.c, part1.h, and header.h must be present. These are the requirements to build the target part1.o. If those files are present, then the command to construct part1.o is:
gcc -O -c part1.cAs a bonus, part1.o will only be re-built if part1.c, part1.h, or header.h have changed since the last time part1.o was changed! 'make' looks at the timestamps on the files to determine what has been changed, and thus what needs to be re-built according to your makefile rules. A change to header.h, however, will rebuild everything, as in this case header.h is used in each of the C files for this program.
Since each of the rules in a makefile stand on their own, we could just do a:
make main.oto only perform those rules necessary to build the target main.o. In a similar way, a:
make cleanwill invoke the rule:
clean: rm -f myprogram main.o part1.o part2.oNotice that this rule does not have any requirements, and thus will simply just remove the 'myprogram' executables, and all of the .o files associated with it.
Rules like these are very handy for performing functions that are slightly more tedious to do by hand. Even a rule like:
backup: cp -Rp * /some/safe/locationcould be added to a Makefile so that a simple 'make backup' would copy all the files from the current directory to the directory '/some/safe/location'.
Traditionally, Makefile variables are all upper-case, and are referenced using ${VAR_NAME}, just like sh or csh variables. For example, a common set of compiler flags could be specified as:
CFLAGS = -O2 -DDEBUG=1 -I/usr/X11R6/includeand then used as ordinary text substitution (just like #ifdef in C) in:
gcc ${CFLAGS} -c main.cNote that variable names are case-sensitive.
The following Makefile uses a few different varaibles:
# A more complex Makefile CC = gcc CFLAGS = -O OBJS = part1.o part2.o main.o myprogram: ${OBJS} ${CC} -o myprogram ${CFLAGS} ${OBJS} part1.o: part1.c part1.h header.h ${CC} ${CFLAGS} -c part1.c part2.o: part2.c header.h ${CC} ${CFLAGS} -c part2.c main.o: main.c header.h ${CC} ${CFLAGS} -c main.c clean: rm -f myprogram ${OBJS} @echo "all cleaned up!"Here we find three different variables in use: CC, CFLAGS, and OBJS. CC is used to specify the compiler - in this case, 'gcc'. Each place where ${CC} is found, the contents of the variable CC will be substituted in. Thus after the substitutions are done, the line:
${CC} ${CFLAGS} -c main.cwould be:
gcc -O -c main.cShould we wish to use the default compiler 'cc', the change is as simple as:
CC = ccin the Makefile. The compiler flags are specified CFLAGS. In this case, only '-O' is specified. But should we wish to do some execution profiling, we may wish to change those options to:
CFLAGS = -pg -gAgain, by simply making the change in one place, we can change how all of the source files are compiled.
The third variable used in this file is OBJS. In this makefile, this variable specifies all the object files required to construct the main program. While each object file could be specified each place ${OBJ} is used, it is simpler and cleaner to just specify each object once in:
OBJS = part1.o part2.o main.oand then use OBJS afterwards. While:
OBJS = part1.o part2.o main.o myprogram: ${OBJS} ${CC} -o myprogram ${CFLAGS} ${OBJS}is only slightly easier to maintain than:
myprogram: part1.o part2.o main.o ${CC} -o myprogram ${CFLAGS} part1.o part2.o main.othe use of variables for these sorts of things becomes much more important as the number of object files increases.
Of note in this Makefile is the rule:
clean: rm -f myprogram ${OBJS} @echo "all cleaned up!"This rule has no requirements, and has two target building instructions. Each time a
make cleanis done, the executable file and all the object files will be removed, and the text "all cleaned up" will be displayed. In the general case, 'make' displays the line to be executed before it performs the line. The "@" at the beginning of the line (but after the TAB!) suppresses the displaying of the the line about to be executed.
mypaper: paper.tex latex paper.tex latex paper.tex dvips paper.dvicould be used to simplify running LaTeX and dvips when writing a paper using LaTeX.
A set of simulation runs, and the production of corresponding graphs, could also be orchestrated with a simple makefile:
everything: run1 run2 run3 plots run1: mysim -o result1.out input_file -parameter1 run2: mysim -o result2.out input_file -parameter2 run3: mysim -o result3.out input_file -parameter3 plots: gnuplot plot1.plt gnuplot plot2.plt gnuplot plot3.pltIn short, anything that has a rule-based dependency ordering can be handled with a Makefile.
man makealthough this typically has way more detail than is required to construct simple makefiles.