Automatically detect changes in header files in a Makefile

Last updated: Feb. 26, 2017, 9:41 p.m.

GNU Make is still a brilliant tool for managing embedded software project build systems. The main strength of the system is the way you can define the dependencies of each stage and then let Make figure out what's changed (based on file dates) and build all the bits required. You may be familiar with rules of the form:

file1.o: file1.c
    gcc -c file1.c -o file1.o

Where file1.o is the object file, file1.c is the source file. This declares that if file1.c is changed, file1.o must be re-made.

Another dependency that is fairly obvious is header files as dependencies of the object file created from a single source file. So for example, if file1.c uses header files header1.h and global_header.h, the rule might look like:

file1.o: file1.c header1.h global_header.h
    gcc -c file1.c -o file1.o

So, now if either file1.c, header1.h or global_header.h is edited the object file file1.o will be re-built. This works, but it's pretty tedious. You would have to update the Makefile every time you change the header files which is easy to forget. The problem becomes even worse if you consider automatic rules which can create any object file from a similarly named c file.

With GNU Make and GCC the solution is surprisingly simple. First you must have a list of all the object files in your project, this is often the easiest way to structure project make files anyway. Here the list is referred to as $(OBJS). Next, you must ask GCC (or more specifically the C pre-processor) to write the rules to a dependency file, simply add -MD to your CFLAGS. At the end of your Makefile, after all the target definitions add one line:

-include $(OBJS:.o=.d)

So a small example might look like:

OBJS = file1.o file2.o file3.o

%.o: %.c
    gcc $(CFLAGS) -o $@ -c $<

%.elf: $(OBJS)
    gcc -o $@ $(OBJS) $(LDFLAGS)

    rm -f *.o
    rm -f *.d

-include $(OBJS:.o=.d)

In this tiny example you'd only have to add a new source file to the objects list to make it compiled into the final elf file and all headers would be automatically made dependencies. This will have the side-effect of creating a .d file for each .o file generated, hence the second line of the clean target.

For further reading, I'd recommend these articles:




Posting comments is not currently possible. If you want to discuss this article you can reach me on twitter or via email.


Email: nathan@nathandumont.com

Mastodon: @hairymnstr@mastodon.social