12
votes

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 :-)

2

2 Answers

20
votes

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.

0
votes

Responding to K Mulier: Try:

gmake -C $workdir ... 
CURDIR = ${workdir}
CPPFLAGS += -I../include

there is

CPPFLAGS += -I$.  -I$./../include