rule to call another Makefile from inside a Makefi

2019-08-12 08:30发布

I have a Makefile from which I run the command of another Makefile if some objects file do not exist already.

The rule is the following:

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/%.c $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

where the variables are defined as follows in the same Makefile:

COMMONDIR     := ../common
SOURCESCOMMON := $(wildcard $(COMMONDIR)/*.c)
OBJDIRCOMMON  := $(COMMONDIR)/obj
OBJECTSCOMMON := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.o, $(SOURCESCOMMON))
DEPENDSCOMMON := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.d, $(SOURCESCOMMON))

This rule works fine but at the end of the day the only real input required by the rule is the other Makefile so I tried:

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

but this does not work, why is that?

For completeness here is the complete Makefile

CC = gcc

INC_PATH = -I../common/

SOURCEDIR := ./
SOURCES := $(wildcard $(SOURCEDIR)/*.c)
OBJDIR  :=./obj
OBJECTS := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.o, $(SOURCES))
DEPENDS := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.d, $(SOURCES))

COMMONDIR     := ../common
SOURCESCOMMON := $(wildcard $(COMMONDIR)/*.c)
OBJDIRCOMMON  := $(COMMONDIR)/obj
OBJECTSCOMMON := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.o, $(SOURCESCOMMON))
DEPENDSCOMMON := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.d, $(SOURCESCOMMON))

# ADD MORE WARNINGS!
WARNING := -Wall -Wextra

# OBJS_LOC is in current working directory,
EXECUTABLE := ../server
# .PHONY means these rules get executed even if
# files of those names exist.
.PHONY: all clean

# The first rule is the default, ie. "make",
# "make all" and "make parking" mean the same
all: $(EXECUTABLE)

clean:
    $(RM) $(OBJECTS) $(DEPENDS) $(EXECUTABLE)

# Linking the executable from the object files
# $^   # "src.c src.h" (all prerequisites)
$(EXECUTABLE): $(OBJECTS) $(OBJECTSCOMMON)
    $(CC) $(WARNING) $^ -o $@

-include $(DEPENDS) $(DEPENDSCOMMON)

$(OBJDIR):
    mkdir -p $(OBJDIR)

$(OBJDIR)/%.o: $(SOURCEDIR)/%.c Makefile | $(OBJDIR)
    $(CC) $(WARNING) -MMD -MP -c $(INC_PATH) $< -o $@

$(OBJDIRCOMMON):
    mkdir -p $(OBJDIRCOMMON)

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/%.c $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

EDIT

The error I get is like this:

Entering directory '/home/user/Documents/UnixSystem/network/common'
gcc -Wall -Wextra -MMD -MP -c utilities.c -o obj/utilities.o
gcc -Wall -Wextra -MMD -MP -c error.c -o obj/error.o
make[2]: Leaving directory '/home/user/Documents/UnixSystem/network/common'
gcc   ../common/obj/error.d.o   -o ../common/obj/error.d
gcc: error: ../common/obj/error.d.o: No such file or directory
gcc: fatal error: no input files
compilation terminated.

From which I understand the execution of the other Makefile was successful. However after that it is trying to execute this command gcc ../common/obj/error.d.o -o ../common/obj/error.d which is wrong but I do not know which rule and why it's generating it.

2条回答
在下西门庆
2楼-- · 2019-08-12 08:40

Should there be && instead of pipe

      $(OBJDIRCOMMON)/%.o: $(COMMONDIR)/Makefile | (OBJDIRCOMMON)
+$(MAKE) -C $(COMMONDIR
查看更多
Anthone
3楼-- · 2019-08-12 08:52

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:

  1. 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.
  2. If a target $(OBJDIRCOMMON)/file.o must be made up-to-date, then $(OBJDIRCOMMON) must be made up-to-date first.
  3. To make a target $(OBJDIRCOMMON)/file.o up-to-date, run the expansion of $(MAKE) -C $(COMMONDIR) in a shell.

Recipe B says:

  1. Any target $(OBJDIRCOMMON)/file.o must be made up-to-date if does not exist or is older than $(COMMONDIR)/Makefile.
  2. If a target $(OBJDIRCOMMON)/file.o must be made up-to-date, then $(OBJDIRCOMMON) must be made up-to-date first.
  3. 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.

查看更多
登录 后发表回答