13

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

K.Mulier
  • 8,069
  • 15
  • 79
  • 141

2 Answers2

27

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.

user657267
  • 20,568
  • 5
  • 58
  • 77
  • Wow, thank you so much for all the explanations. I'm still working through your explanations to fully understand. But you definitely gave me a big boost :-) – K.Mulier Aug 17 '16 at 18:26
  • I'm using the GCC compiler indeed. So there is a more elegant way to generate dependencies? Could you describe it? I'm a bit shy to ask this, because I know this will probably consume a lot of your time. But I would be soooo grateful :-) – K.Mulier Aug 17 '16 at 18:27
  • @K.Mulier Added a short example. – user657267 Aug 17 '16 at 18:33
  • Waw, thank you so much for this example. Unfortunately, I would like to 'build outside the source tree'. In this way, I keep the source folder(s) uncluttered. But to be honest, I have no idea on how to generate the dependencies for that – K.Mulier Aug 17 '16 at 18:35
  • 1
    There are several ways to do "outside the source tree" builds -- either put paths on the .o and .d files to put them elsewhere while you build in the source tree, or use VPATH or paths on the sources and build in an object-only tree. – Chris Dodd Aug 17 '16 at 18:45
  • @K.Mulier I edited the example to support out of tree builds, it's fairly naïve but should work nonetheless. – user657267 Aug 17 '16 at 18:51
  • Hi user657267 and @Chris Dodd , I have started a new Stackoverflow question on the topic of out-of-source builds. Please take a look at it: http://stackoverflow.com/questions/39015453/building-c-program-out-of-source-tree-with-gnu-make Your help is very much appreciated :-) – K.Mulier Aug 18 '16 at 10:44
1

Responding to K Mulier: Try:

gmake -C $workdir ... 
That sets
CURDIR = ${workdir}
but leaves $PWD (from env) alone. All temp/output files are created in ${workdir}. Also, I set the variable $. = ${PWD} and use that to specify anything relative to the dir in which gmake was run (i.e. the source dir). So, instead of
CPPFLAGS += -I../include

there is

CPPFLAGS += -I$.  -I$./../include
Mischa
  • 2,240
  • 20
  • 18