In the makefile of a program, one has to write rules that define the dependencies of each object file. Consider the object file fileA.o
. It is obvious that this object file depends on the source file fileA.c
. But it will also depend on all the header files that this source file includes. So the following rule should be added to the makefile:
# This rule states that fileA.o depends on fileA.c (obviously), but also
# on the header files fileA.h, fileB.h and fileC.h
fileA.o: fileA.c fileA.h fileB.h fileC.h
Note that the rule has no recipe. One could add a recipe to it, but it is strictly speaking not necessary, because GNU make can rely on an implicit rule (with recipe) to compile a *.c
file into a *.o
file.
Anyway, writing such rules manually is a hellish task. Just imagine the work to keep the makefile rules in sync with the #include statements from the source code.
The GNU make manual describes in chapter 4.14 "Generating Prerequisites Automatically" a methodology to automate this procedure. The procedure starts with the generation of a *.d
file for each source file. I quote:
For each source file name.c there is a makefile name.d which lists what files the object file name.o depends on.
The manual proceeds:
Here is the pattern rule to generate a file of prerequisites (i.e, a makefile) called name.d from a C source file called name.c :
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
Sadly, the manual does not explain in detail how this rule actually works. Yes, it gives the desired name.d file, but why? The rule is very obfuscated..
When looking at this rule, I get the feeling that its recipe will only run smoothly on Linux. Am I right? Is there a way to make this recipe run correctly on Windows as well?
Any help is greatly appreciated :-)
GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files. Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files.
A dependency is a file that is used as input to create the target. A target often depends on several files. A command is an action that make carries out. A rule may have more than one command, each on its own line.
A D file is a source dependency file generated by GCC, a GNU C compiler. It contains dependencies in plain text that describe the files that were used to create compiled objects (. O files) by a C compiler. D files are generated automatically when the -MMD flag is activated when compiling with GCC.
GNU Make is a program that automates the running of shell commands and helps with repetitive tasks. It is typically used to transform files into some other form, e.g. compiling source code files into programs or libraries. It does this by tracking prerequisites and executing a hierarchy of commands to produce targets.
Exit on all errors
@set -e;
Delete existing dep file ($@
= target = %.d
)
rm -f $@;
Have the compiler generate the dep file and output to a temporary file postfixed with the shell pid ($<
= first prerequisite = %.c
, $$$$
-> $$
-> pid)
$(CC) -M $(CPPFLAGS) $< > $@.$$$$;
Capture the target matching $*.o
($*
= match stem = %
), replace it with the target followed by the dependency file itself, output to dep file
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
Delete temp dep
rm -f $@.$$$$
Let's plug in foo
, CC = gcc
, and CPPFLAGS = ''
to see what happens after make has finished expanding stuff:
foo.d: foo.c
@set -e; rm -f foo.d; \
gcc -M foo.c > foo.d.$$; \
sed 's,\(foo\)\.o[ :]*,\1.o foo.d : ,g' < foo.d.$$ > foo.d; \
rm -f foo.d.$$
The shell itself will expand $$
to the pid, and the final rule in the dep file will look something like
foo.o foo.d : foo.c foo.h someheader.h
Note that this is a pretty outdated way of generating dependencies, if you're using GCC or clang you can generate them as part of compilation itself with CPPFLAGS += -MMD -MP
.
Say you have a program called foo
:
objs := foo.o bar.o
deps := $(objs:.o=.d)
vpath %.c $(dir $(MAKEFILE_LIST))
CPPFLAGS += -MMD -MP
foo: $(objs)
.PHONY: clean
clean: ; $(RM) foo $(objs) $(deps)
-include $(deps)
That's all you need, the built-in rules will do the rest. Obviously things will be a little more complicated if you want the object files in a different folder or if you want to build outside the source tree.
The vpath
directive allows you to run make in a different directory and have the files created there, e.g. make -f path/to/source/Makefile
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With