OK, there are still some project-specific things in there. I'll make a cleaner example:
Code: Select all
SRCFILES := $(shell find . -mindepth 1 -name "*.c")
DEPFILES := $(patsubst %.c,%.d,$(SRCFILES))
-include $(DEPFILES)
%.o: %.c Makefile
$(CC) $(CFLAGS) -MMD -MP -MT "$*.d" -c $< -o $@
The Makefile is in the root directory, and the only (!!!) Makefile of your project.
Only a single project-wide Makefile ensures make gets to know the
full ruleset and dependency tree. Recursive make means your "submakes" don't know everything, and might make wrong decisions as a result. If you are lucky, it's just slower than it need to be. If you are unlucky, it will produce wrong results.
Let's go through it line by line.
Code: Select all
SRCFILES := $(shell find . -mindepth 1 -name "*.c")
SRCFILES is a list of all .c files contained in subdirectories of the current (root) directory. (This excludes any "test.c" or something you might write ad-hoc in the rootdir.)
My original example above explicitly states PROJDIRS (to exclude some subdirs containing reference code I do not want to be buildt), and only searches to a maximum depth of 3 dirlevels (to make the search quicker facing deeply nested subdirs I keep for different purposes).
Code: Select all
DEPFILES := $(patsubst %.c,%.d,$(SRCFILES))
DEPFILES is the same as SRCFILES, but with the .c endings replaced with .d (for "dependency"). Those files do not exist in the beginning; see below.
This includes all DEPFILES into the Makefile, insofar as they do exist. The leading "-" suppresses error messages for missing files. This means, in the first (virgin) build, nothing is included here.
Any .o file depends on its .c counterpart and the Makefile. (After all, if you changed e.g. CFLAGS, you'd want to have your sources recompiled, don't you?)
Code: Select all
$(CC) $(CFLAGS) -MMD -MP -MT "$*.d" -c $< -o $@
$(CC) $(CFLAGS) -c $< -o $@ compiles the .c file into a .o file.
-MMD -MP -MT "$*.d" is the real magic here. You can look up the exact meaning of the individual options in the gcc manual, but what it
does is this:
When compiling a .c file, gcc (more precisely, the preprocessor) does create an additional .d file, in which it writes
make dependency rules listing any include files.
This might look like this, for function/example.c:
Code: Select all
functions/example.d functsions/example.o: functions/example.c ./includes/foo.h ./includes/bar.h
./includes/foo.h:
./includes/bar.h:
On any subsequent builds, these dependencies will be included by make, so make "knows" that example.o must be updated whenever the source or any included header files are touched. Moreover, it knows that the dependency must be likewise updated.
The "dummy" dependencies where the header files themselves depend on nothing are a workaround on some strange make behaviour should the headers be removed; it's been some time since, I can't really remember the details anymore.
The original example also included .t files as target, since every one of my object files can, by defining -DTEST and compiling to executable instead of object file, be turned into a test driver for itself - but that's a different story.
Bottom line, gcc gives you the
real,
current dependencies of your sources
automatically, as a
byproduct of your "normal" build routine. You just have to know this is possible.
All in all, it's a good example of one of my personal "golden rules": Before you start looking for a solution (additional tools or libraries), see if you really got a problem (that the tools / libraries you're already using cannot solve).