A makefile is a collection of instructions that should be used to compile
your program. Once you modify some source files, and type the command "make"
(or "gmake" if using GNU's make), your program will be recompiled using
as few compilation commands as possible. Only the files you modified and
those dependent upon them will be recompiled. Of-course, this is not done
via usage of magic. You need to supply the rules for compiling various
files and file types, and the list of dependencies between files (if file
"A" was changed, then files "B", "C" and "D" also need to be re-compiled),
but that only has to be done once.
CFLAGS = -g -Wall SRCS = main.c file1.c file2.c CC = gcc
main.o: main.c gcc -g -Wall -c main.cThis rule means that "main.o" has to be recompiled whenever "main.c" is modified. The rule continues to tell us that in order to compile "main.o", the command "gcc -g -Wall -c main.c" needs to be executed.
When make Is invoked, it first evaluates all variable assignments, from top to bottom, and when it encounters a rule "A" whose target matches the given target (or the first rule, if no target was supplied), it tries to evaluate this rule. First, it tries to recursively handle the dependencies that appear in the rule. If a given dependency has no matching rule, but there is a file in the disk with this name, the dependency is assumed to be up-to-date. After all the dependencies were checked, and any of them required handling, or refers to a file newer then the target, the command list for rule "A" is executed, one command at a time.
This might appear a little complex, but the example in the next section
will make things clear.
# top-level rule to create the program. all: main # compiling the source file. main.o: main.c gcc -g -Wall -c main.c # linking the program. main: main.o gcc -g main.o -o main # cleaning everything that can be automatically recreated with "make". clean: /bin/rm -f main main.oFew notes about this makefile:
# top-level rule to compile the whole program. all: prog # program is made of several source files. prog: main.o file1.o file2.o gcc main.o file1.o file2.o -o prog # rule for file "main.o". main.o: main.c file1.h file2.h gcc -g -Wall -c main.c # rule for file "file1.o". file1.o: file1.c file1.h gcc -g -Wall -c file1.c # rule for file "file2.o". file2.o: file2.c file2.h gcc -g -Wall -c file2.c # rule for cleaning files generated during compilations. clean: /bin/rm -f prog main.o file1.o file2.oFew notes:
The solution to this problem is using variables to store various flags, and even command names. This is especially useful when trying to compile the same source code with different compilers, or on different platforms, where even a basic command such as "rm" might reside in a different directory on each platform.
Lets see the same makefile, but this time with the introduction of variables:
# use "gcc" to compile source files. CC = gcc # the linker is also "gcc". It might be something else with other compilers. LD = gcc # Compiler flags go here. CFLAGS = -g -Wall # Linker flags go here. Currently there aren't any, but if we'll switch to # code optimization, we might add "-s" here to strip debug info and symbols. LDFLAGS = # use this command to erase files. RM = /bin/rm -f # list of generated object files. OBJS = main.o file1.o file2.o # program executable file name. PROG = prog # top-level rule, to compile everything. all: $(PROG) # rule to link the program $(PROG): $(OBJS) $(LD) $(LDFLAGS) $(OBJS) -o $(PROG) # rule for file "main.o". main.o: main.c file1.h file2.h $(CC) $(CFLAGS) -c main.c # rule for file "file1.o". file1.o: file1.c file1.h $(CC) $(CFLAGS) -c file1.c # rule for file "file2.o". file2.o: file2.c file2.h $(CC) $(CFLAGS) -c file2.c # rule for cleaning re-compilable files. clean: $(RM) $(PROG) $(OBJS)Few notes:
# we'll skip all the variable definitions, just take them from the previous # makefile . . # linking rule remains the same as before. $(PROG): $(OBJS) $(LD) $(LDFLAGS) $(OBJS) -o $(PROG) # now comes a meta-rule for compiling any "C" source file. %.o: %.c $(CC) $(CFLAGS) -c $<We should explain two things about our meta-rule:
# define the list of source files. SRCS = main.c file1.c file2.c . . # most of the makefile remains as it was before. # at the bottom, we add these lines: # rule for building dependency lists, and writing them to a file # named ".depend". depend: $(RM) .depend makedepend -f- -- $(CFLAGS) -- $(SRCS) > .depend # now add a line to include the dependency list. include .dependNow, if we run "make depend", the "makedepend" program will scan the given source files, create a dependency list for each of them, and write appropriate rules to the file ".depend". Since this file is then included by the makefile, when we'll compile the program itself, these dependencies will be checked during program compilation.
There are many other ways to generate dependency lists. It is advised
that programmers interested in this issue read about the compiler's "-M"
flag, and read the manual page of "makedepend" carefully. Also note that
gnu make's info pages suggest a different way of making dependency lists
created automatically when compiling a program. The advantage is that the
dependency list never gets out of date. The disadvantage is that many times
it is being run for no reason, thus slowing down the compilation phase.
Only experimenting will show you exactly when to use each approach.