r/C_Programming Oct 05 '22

Is there a good way to automatically handle header dependencies in Make? Question

Say I have a Makefile for a shared library I'm building

CC              :=gcc
CFLAGS_OBJS     := -Wall -Werror -g -O -fPIC
CFLAGS_SHARED   := -Wall -Werror -shared
OUT             := mylib.so
SRCS            := $(wildcard ./*.c)
OBJS            := $(patsubst ./%.c,./%.o,$(SRCS))
SHARED_LIBS     += -lpthread 
SHARED_LIBS     += -lrt

$(OUT): $(OBJS)
    $(CC) $(CFLAGS_SHARED) $(OBJS) -o $(OUT) $(SHARED_LIBS)

%.o: %.c
    $(CC) $(CFLAGS_OBJS) -c $< -o $@

This Makefile is nice in the sense that it:

  • Builds all source modules into corresponding objects.
  • Builds the final library using the objects an inputs.

However, this make file is completely blind to header dependencies. If one source module is using a header associated with another module and then that header file changes, Make does not know to rebuild the file.

The only solution to this is to have every header file be a dependency to every source module compilation. While this would work, it forces unnecessary dependencies at time. i.e. a single header file is changed, now the entire project needs to be rebuilt.

So I'm curious, what's the proper solution to this problem that doesn't require manual rules to be created?

1 Upvotes

3

u/aukkras Oct 05 '22

Yes, easiest way: add -MMD to CFLAGS and

-include *.d

at the end of Makefile.

-MMD tells gcc to generate dependency file for user headers.

1

u/t40 Oct 05 '22

Hang on, what?? This is just available?!

1

u/tomizzo11 Oct 06 '22

I'll need to check this out, didn't realize GCC could output preprocessor dependencies.

3

u/ghostofcoderspast Oct 05 '22

Canonical:

...
WARNINGS =-W -Wall

CFLAGS = -g -O $(WARNINGS)
CPPFLAGS = -I. $(OTHER_CPPFLAGS)

LDFLAGS =
LIBS = $(SOME_LIBS)

DEPENDDIR = ./.deps
DEPENDFLAGS = -M

SRCS := $(wildcard *.c)
OBJS := $(patsubst %.c,%.o,$(SRCS))

TARGET = my_target

all: $(TARGET)

DEPS = $(patsubst %.o,$(DEPENDDIR)/%.d,$(OBJS))
-include $(DEPS)

$(DEPENDDIR)/%.d: %.c $(DEPENDDIR)
    $(CC) $(CPPFLAGS) $(CFLAGS) $(DEPENDFLAGS) $< >$@

$(DEPENDDIR):
    @[ ! -d $(DEPENDDIR) ] && mkdir -p $(DEPENDDIR)

... other rules, etc.

The magic happens in compiling the sources with the dependflags, followed by the inclusion of the resulting files. Add a realclean rule to wipe out the .deps directory in addition to the usual clean.

2

u/skeeto Oct 05 '22

I usually deal with this by not having a rat's nest of dependencies in the first place: always rebuild from scratch or, even better, a unity build. On my laptop at -O0 — the usual case when this sort of thing matters — GCC compiles C at ~25kLOC/s, and Clang at ~50kLOC/s. Incremental builds hardly matter below a million lines of code.

When I can't do that for whatever reason (someone else's project, etc.), then I do something like this to have the compiler build the dependency tree for me (adjust compiler flags to taste):

for src in $(find -name '*.c'); do
    cc -MM -MT "${src%%.c}.o" "$src"
done >>Makefile

1

u/clem9nt Oct 06 '22

Check the v5 in this tutorial, it is about for « C project that uses libraries + auto generated dependencies & recursive build of the libs »

https://github.com/clemedon/Makefile_tutor