Why what you did was wrong
Recipe A:
$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/%.c $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
+$(MAKE) -C $(COMMONDIR)
and recipe B:
$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
+$(MAKE) -C $(COMMONDIR)
have essentially different meanings and will certainly not yield the same behaviour.
Recipe A says:
- Any target
$(OBJDIRCOMMON)/file.o
must be made up-to-date if does not exist
or is older than either $(COMMONDIR)/file.c
or $(COMMONDIR)/Makefile
.
- If a target
$(OBJDIRCOMMON)/file.o
must be made up-to-date, then $(OBJDIRCOMMON)
must be made up-to-date first.
- To make a target
$(OBJDIRCOMMON)/file.o
up-to-date, run the expansion of $(MAKE) -C $(COMMONDIR)
in a shell.
Recipe B says:
- Any target
$(OBJDIRCOMMON)/file.o
must be made up-to-date if does not exist
or is older than $(COMMONDIR)/Makefile
.
- If a target
$(OBJDIRCOMMON)/file.o
must be made up-to-date, then $(OBJDIRCOMMON)
must be made up-to-date first.
- To make a target
$(OBJDIRCOMMON)/file.o
up-to-date, execute the expansion of $(MAKE) -C $(COMMONDIR)
in a shell.
Notice that criterion A.1 is different from criterion B.1. Recipe A will execute
if $(OBJDIRCOMMON)/file.o
is older than $(OBJDIRCOMMON)/file.c
. Recipe B will not.
Recipe B discards the dependency of the object files on the corresponding source files,
and tells Make that $(OBJDIRCOMMON)/file.o
is only ever to be remade if it
is older than $(COMMONDIR)/Makefile
.
at the end of the day the only real input required by the rule is the other Makefile
What you mean by "the rule" here is actually the commandline (expanded from) $(MAKE) -C $(COMMONDIR)
.
The inputs of this command are one thing; the criteria for executing it are another.
How what you did caused the error you see.
This is thornier. Let's reproduce it.
Here's a playpen:
$ ls -R
.:
app common
./app:
foo.c main.c Makefile
./common:
bar.c Makefile
Here, ./app/Makefile
is exactly your Makefile with recipe A. ./common/Makefile
,
which you didn't post, is just:
obj/bar.o: bar.c
gcc -MMD -MP -c -I. $< -o $@
because that will do for illustration.
We build in ./app
:
$ cd app
$ make
mkdir -p ./obj
gcc -Wall -Wextra -MMD -MP -c -I../common/ foo.c -o obj/foo.o
gcc -Wall -Wextra -MMD -MP -c -I../common/ main.c -o obj/main.o
mkdir -p ../common/obj
make -C ../common
make[1]: Entering directory '/home/imk/develop/so/make_prob/common'
gcc -MMD -MP -c -I. bar.c -o obj/bar.o
make[1]: Leaving directory '/home/imk/develop/so/make_prob/common'
gcc -Wall -Wextra obj/foo.o obj/main.o ../common/obj/bar.o -o ../server
which is fine.
Now I change ./app/Makefile
as you did, to use recipe B, and rebuild.
$ make
gcc -Wall -Wextra -MMD -MP -c -I../common/ foo.c -o obj/foo.o
gcc -Wall -Wextra -MMD -MP -c -I../common/ main.c -o obj/main.o
gcc -Wall -Wextra obj/foo.o obj/main.o ../common/obj/bar.o -o ../server
Still fine... But wait a minute! That one didn't invoke the ./common
make
at all, which is the one that the change might affect. Better clean
:
$ make clean
rm -f ./obj/foo.o ./obj/main.o ./obj/foo.d ./obj/main.d ../server
and try again:
$ make
gcc -Wall -Wextra -MMD -MP -c -I../common/ foo.c -o obj/foo.o
gcc -Wall -Wextra -MMD -MP -c -I../common/ main.c -o obj/main.o
gcc -Wall -Wextra obj/foo.o obj/main.o ../common/obj/bar.o -o ../server
No difference? Ah, that's because this Makefile's clean
fails to delete all
the files that make
builds: it leaves out ../common/obj/bar.o
. So I'll just:
$ rm ../common/obj/*
And have another go:
$ make
make -C ../common
make[1]: Entering directory '/home/imk/develop/so/make_prob/common'
gcc -MMD -MP -c -I. bar.c -o obj/bar.o
make[1]: Leaving directory '/home/imk/develop/so/make_prob/common'
gcc ../common/obj/bar.d.o -o ../common/obj/bar.d
gcc: error: ../common/obj/bar.d.o: No such file or directory
gcc: fatal error: no input files
compilation terminated.
which is your mystery.
When I zapped the ../common/obj
files, I deleted not only all the object files therein
but also the dependency file ../common/obj/bar.d
. And now Make is trying to remake it by running:
gcc ../common/obj/bar.d.o -o ../common/obj/bar.d
How come? To answer that, we'll first change ./app/Makefile
back to use recipe A
- consider it done - and then do:
$ make --print-data-base > out.txt
which dumps in out.txt
all the information that Make gleans from reading all
the makefiles (Makefile
and all the makefiles that it recursively include
-s,
in this case just the auto-generated .d
files).
Let's see what the database has to say about ../common/obj/bar.d
. It says:
# Not a target:
../common/obj/bar.d:
# Implicit rule search has been done.
# Last modified 2019-01-11 16:01:33.199263608
# File has been updated.
# Successfully updated.
Certainly we don't want ../common/obj/bar.d
to be a target, and it isn't a
target because, having read all the makefiles, and considered all its builtin rules,
and all of the files it can actually find, Make can't see any way in which ../common/obj/bar.d
has to be made up-to-date with respect to any of those files. Good.
Now let's revert to recipe B in ./app/Makefile
again - consider it done -
and again do:
$ make --print-data-base > out.txt
and again look in out.txt
concerning ../common/obj/bar.d
. This time we find:
../common/obj/bar.d: ../common/obj/bar.d.o
# Implicit rule search has been done.
# Implicit/static pattern stem: '../common/obj/bar.d'
# Last modified 2019-01-11 16:01:33.199263608
# File has been updated.
# Successfully updated.
# recipe to execute (built-in):
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
So this time ../common/obj/bar.d
is a target! And it depends on ../common/obj/bar.d.o
!
And the recipe to make it is:
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
which will expand, of course, to:
gcc ../common/obj/bar.d.o -o ../common/obj/bar.d
How was Make able to work that out, thanks to recipe B?
Well first it considered whether any of the rules in the makefiles or any of the
builtin rules gave it direct way to make ../common/obj/bar.d
from any existing files,
and drew a blank.
Next it considered whether any of those rules gave it a way to
make ../common/obj/bar.d
from an intermediate file. An intermediate file being a file that doesn't exist but itself can be made
from existing files, by any of the rules it has read or its builtin-rules. This
time it saw a way.
One of Make's builtin pattern rules is:
%: %.o
# recipe to execute (built-in):
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
You can find it in right there in out.txt
. And you can see this is the pattern rule that
it matches with:
../common/obj/bar.d: ../common/obj/bar.d.o
The recipe there is a recipe to link a program called ../common/obj/bar.d
given
an object file ../common/obj/bar.d.o
.
There is no object file ../common/obj/bar.d.o
, but can it be an intermediate file? If
Make can find a rule for making ../common/obj/bar.d.o
from files that do exist,
then it can also make ../common/obj/bar.d
with this %: %.o
rule.
And it can find a recipe for making ../common/obj/bar.d.o
from existing files
because we just gave it one! - recipe B:
$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
+$(MAKE) -C $(COMMONDIR)
That tells Make that if any target matching $(OBJDIRCOMMON)/%.o
(like ../common/obj/bar.d.o
)
does not exist, but $(COMMONDIR)/Makefile
does exist (which it does), then that target
is made up-to-date by running:
$(MAKE) -C $(COMMONDIR)
Make believed us. It ran $(MAKE) -C $(COMMONDIR)
:
make -C ../common
make[1]: Entering directory '/home/imk/develop/so/make_prob/common'
gcc -MMD -MP -c -I. bar.c -o obj/bar.o
make[1]: Leaving directory '/home/imk/develop/so/make_prob/common'
and then considered ../common/obj/bar.d.o
up-to-date. So it moved on to:
../common/obj/bar.d: ../common/obj/bar.d.o
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
and ran:
gcc ../common/obj/bar.d.o -o ../common/obj/bar.d
which failed because we lied:
make -C ../common
does not make ../common/obj/bar.d.o
at all.
gcc: error: ../common/obj/bar.d.o: No such file or directory
This does not arise with recipe A, because
$(OBJDIRCOMMON)/bar.d.o: $(OBJDIRCOMMON)/bar.d.c $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
+$(MAKE) -C $(COMMONDIR)
does not offer Make a way to make $(OBJDIRCOMMON)/bar.d.o
from existing files,
because $(OBJDIRCOMMON)/bar.d.c
does not exist. So ../common/obj/bar.d
is not
a target.
Stick with recipe A, because it's right, and recipe B is wrong. Also review
and fix the makefiles so that make clean
always deletes all the non-.PHONY
targets that might have been built, and nothing else. Lastly avoid writing recipes with non-.PHONY
targets where the recipe does not mention the target.