I am aware that there is a lot of debate around recursive makefiles. That being understood, I'm still trying to get one to work.
Context: I have the following project setup:
quendor
src
zmachine
Makefile
main.c
zmachine.h
Makefile
So I use the root-level Makefile
to call another Makefile
in a directory. (My actual project has other directories that generate other library files but, for now, the above showcases the problem with the minimum amount of context.
The makefile setup I have in place does work in that it generates .o
and .d
files in the zmachine
directory. Then a library (zmachine.a
) is built correctly.
Problem: The problem I have is that when I run the makefile at the root level again, it will not pick up if there have been changes to either the .c
or .h
files in the subdirectory. I just get this message:
make: Nothing to be done for `zmachine_lib'.
Example of My Code:
Here is the root level Makefile
:
CC := gcc
ifeq ($(CC), gcc)
CFLAGS += -std=c11 -Wall -Wextra -Wpedantic -Wconversion -Wmissing-prototypes -Wshadow -MMD -MP
LDFLAGS +=
OPT +=
endif
AR ?= $(shell which ar)
RANLIB ?= $(shell which ranlib)
export CC
export AR
export RANLIB
export CFLAGS
SRC_DIR = src
ZMACHINE_DIR = $(SRC_DIR)/zmachine
ZMACHINE_LIB = $(ZMACHINE_DIR)/zmachine.a
SUB_DIRS = $(ZMACHINE_DIR)
SUB_CLEAN = $(SUB_DIRS:%=%-clean)
# Targets
zmachine_lib : $(ZMACHINE_LIB)
$(ZMACHINE_LIB):
$(MAKE) -C $(ZMACHINE_DIR)
clean : $(SUB_CLEAN)
$(SUB_CLEAN):
-$(MAKE) -C $(@:%-clean=%) clean
Here is the Makefile
from the zmachine
directory:
SOURCES := $(wildcard *.c)
HEADERS = $(wildcard *.h)
OBJECTS := $(SOURCES:%.c=%.o)
DEPS := $(OBJECTS:%.o=%.d)
TARGET = zmachine.a
ARFLAGS = rc
$(TARGET): $(OBJECTS)
$(AR) $(ARFLAGS) $@ $?
$(RANLIB) $@
@echo "** Finished building Z-Machine architecture."
%.o: %.c
$(CC) $(CFLAGS) -fPIC -c $< -o $@
clean:
$(RM) $(TARGET) $(OBJECTS) $(DEPS)
-include $(DEPS)
Again, the logic of the Makefiles works in terms of generating the correct output and library files. So I seem to have got that part right.
What I can't get to work is having the combined Makefiles, working together, recognize that changes have been made and thus recompiling is necessary.
For example, if I change main.c
or zmachine.h
, running make
again won't recognize that the change has happened, thus no recompilation is trigered.
What I Tried:
I did some searching for "recursive makefiles dependencies" and related searches but I couldn't find anything that showed me how to handle dependency changes in this specific context.
I did try running make -d
to look at the files and dates that make
was using. It gave me this output, which wasn't as helpful to me:
No implicit rule found for `zmachine_lib'.
Considering target file `src/zmachine/zmachine.a'.
Finished prerequisites of target file `src/zmachine/zmachine.a'.
No need to remake target `src/zmachine/zmachine.a'.
Finished prerequisites of target file `zmachine_lib'.
Must remake target `zmachine_lib'.
Successfully remade target file `zmachine_lib'.
make: Nothing to be done for `zmachine_lib'.
I realize this must be because I do not have the file set up to recognize what depends on what. But I feel I do with this:
zmachine_lib : $(ZMACHINE_LIB)
$(ZMACHINE_LIB):
$(MAKE) -C $(ZMACHINE_DIR)
Here zmachine_lib is the only target in this example. It depends on $(ZMACHINE_LIB)
which, as you can see, I have set up to call the Makefile in the subdirectory.
So I'm guessing it's this bit of calling $(MAKE)
that is perhaps obfuscating changes made at the subdirectory level since, for the $(ZMACHINE_LIB
) target would appear to be already satisfied (from the perspective of the top-level Makefile).
I did find this makefile with dependency on a shared library sub project, which does seem like it's very close to my issue. But I can't see how to utilize that solution in my case because of the fact that the logic is distributed over the two Makefiles. I did try to change my target in the sub-Makefile to this:
$(TARGET): $(SOURCES)
...
Basically changing $(OBJECTS)
to $(SOURCES)
, which is what it seems like that other solution was suggesting. But that does not work for me; dependency changes are still not recognized.
I am aware that there is a lot of debate around recursive makefiles.
I'm not sure there is so much debate, really. Recursive make
has some pretty well known limitations. Non-recursive make
has different, largely complementary, limitations.
That being understood, I'm still trying to get one to work.
Lots of people do. But although you may understand that recursive make
has known issues, you do not seem to understand their nature, because you are asking about a manifestation of one of main ones.
Problem: The problem I have is that when I run the makefile at the root level again, it will not pick up if there have been changes to either the .c or .h files in the subdirectory.
No, it doesn't. That is to be expected with your makefile.
Recursive make
serves large projects by dividing build information into more manageable pieces and keeping it close to the sources being built. Among the main costs of doing so is that details, especially dependency details, are compartmentalized. In your case, the top-level make
knows that a target src/zmachine/zmachine.a
is to be built, but no dependencies for that target are known to it. As a result, that make
considers the target out of date only if it does not exist. So yes, it will not recognize that target as being out of date relative to its actual sources. That a sub-make
would update it if run is irrelevant, because the sub-make
never runs.
The usual, more practicable approach to recursive make
is to recurse unconditionally. Something like this:
SUBDIRS = src doc
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
Where there are dependencies among the targets managed by different make
runs, those still need to be modeled somehow, else you will sometimes need multiple runs of the overall make
for everything to be updated. Ideally, that's handled on a directory-by-directory basis, not a specific-target basis.
But that's a bit oversimplified, I'm afraid, because one generally wants recursion to work for most or all top-level targets, not just for the default target. You should consider looking at someone else's working recursive make
build system to see how they do it.