从Makefile内部调用另一个Makefile的规则

时间:2019-01-09 23:12:22

标签: c makefile compilation

我有一个Makefile,如果某些对象文件尚不存在,我将从中运行另一个Makefile的命令。

规则如下:

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

其中的变量在同一Makefile中的定义如下:

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

该规则很好用,但是到最后,该规则唯一需要的实际输入是其他Makefile,所以我尝试了:

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

但这不起作用,为什么呢?

为完整起见,这里是完整的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)

编辑

我得到的错误是这样的:

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.

据我了解,其他Makefile的执行成功。但是之后,它试图执行此命令gcc ../common/obj/error.d.o -o ../common/obj/error.d,这是错误的,但是我不知道哪个规则以及为什么会生成它。

2 个答案:

答案 0 :(得分:2)

为什么您做错了

食谱 A

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

和配方 B

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

实质上具有不同的含义,并且肯定不会产生相同的行为。

食谱 A 说:

  1. 如果目标$(OBJDIRCOMMON)/file.o不存在,则必须将其最新 或早于$(COMMONDIR)/file.c$(COMMONDIR)/Makefile
  2. 如果目标$(OBJDIRCOMMON)/file.o必须是最新的,则$(OBJDIRCOMMON) 必须首先更新。
  3. 要使目标$(OBJDIRCOMMON)/file.o为最新版本,请在shell中运行$(MAKE) -C $(COMMONDIR)的扩展。

食谱 B 说:

  1. 如果目标$(OBJDIRCOMMON)/file.o不存在,则必须将其最新 或早于$(COMMONDIR)/Makefile
  2. 如果目标$(OBJDIRCOMMON)/file.o必须是最新的,则$(OBJDIRCOMMON) 必须首先更新。
  3. 要使目标$(OBJDIRCOMMON)/file.o为最新版本,请在shell中执行$(MAKE) -C $(COMMONDIR)的扩展。

请注意,标准 A.1 与标准 B.1 不同。配方 A 将执行 如果$(OBJDIRCOMMON)/file.o早于$(OBJDIRCOMMON)/file.c。配方 B 不会。 配方 B 放弃了对象文件对相应源文件的依赖关系, 并告诉Make $(OBJDIRCOMMON)/file.o仅在被重造时 早于$(COMMONDIR)/Makefile

  

一天结束时,规则唯一需要的实际输入就是另一个Makefile

您在这里所说的“规则”实际上是$(MAKE) -C $(COMMONDIR)的命令行。 该命令的输入是一回事。执行它的标准是另一个。

您的操作如何导致看到的错误

这更棘手。让我们重现它。

这是一个围栏:

$ ls -R
.:
app  common

./app:
foo.c  main.c  Makefile

./common:
bar.c  Makefile

在这里,./app/Makefile就是您的配方为 A 的Makefile。 ./common/Makefile, 您没有发布的只是:

obj/bar.o: bar.c
    gcc -MMD -MP -c -I. $< -o $@

因为这将起到说明作用。

我们内置./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

很好。

现在,我像您一样更改./app/Makefile,以使用配方 B 并重建。

$ 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

仍然可以...但是请稍等!那个没有调用./common make 根本就是变更可能会影响的那个。更好的clean

$ make clean
rm -f ./obj/foo.o ./obj/main.o ./obj/foo.d ./obj/main.d ../server

然后重试:

$ 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

没有区别吗?嗯,这是因为此Makefile的clean无法删除所有 make生成的文件:省略了../common/obj/bar.o。所以我就这样:

$ rm ../common/obj/*

再去一遍:

$ 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.

这是你的奥秘。

当我压缩../common/obj文件时,不仅删除了其中的所有 object 文件, 而且还有依赖性文件../common/obj/bar.d。现在,Make正在尝试通过运行以下命令来重新制作它:

gcc   ../common/obj/bar.d.o   -o ../common/obj/bar.d

为什么?为了回答这个问题,我们首先将./app/Makefile改回使用配方 A  -考虑完成-然后执行:

$ make --print-data-base > out.txt

将{收集的所有信息转储到out.txt中, 生成文件(Makefile及其所有递归include -s的生成文件, 在这种情况下,只是自动生成的.d文件)。

让我们看看数据库对../common/obj/bar.d的评价。它说:

# 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.

当然我们不想要 ../common/obj/bar.d作为目标,并且不是 定位是因为,在读取了所有makefile并考虑了其所有内置规则之后, 以及它实际上可以找到的所有文件,Make无法看到../common/obj/bar.d 必须对这些文件中的任何文件进行更新。好。

现在让我们再次回到./app/Makefile中的食谱 B -认为已完成- 并再次执行:

$ make --print-data-base > out.txt

,然后再次浏览out.txt中与../common/obj/bar.d有关的内容。这次我们发现:

../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 $@

所以这次../common/obj/bar.d 一个目标!它取决于../common/obj/bar.d.o! 制作方法如下:

    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

它当然会扩展为:

gcc   ../common/obj/bar.d.o   -o ../common/obj/bar.d

由于配方 B ,使Make如何解决该问题?

首先,它考虑了makefile文件中的任何规则还是任何 内置的规则使它可以直接从任何现有文件制作../common/obj/bar.d, 并画了一个空白。

下一步它考虑了其中任何规则是否为 从intermediate file中制作../common/obj/bar.d中间文件是不存在的文件,但其自身可以被 制成 从现有文件中读取任何规则或其内置规则。这个 时间到了。

Make的内置模式规则之一是:

%: %.o
#  recipe to execute (built-in):
    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

您可以在out.txt中找到它。您会看到这是模式规则 它与以下项匹配:

../common/obj/bar.d: ../common/obj/bar.d.o

该配方中有一个配方可以链接程序,名为../common/obj/bar.d 对象文件../common/obj/bar.d.o

没有目标文件../common/obj/bar.d.o,但是它可以是中间文件吗?如果 Make可以找到规则,从存在 do 的文件中制作../common/obj/bar.d.o, 那么它也可以使用此../common/obj/bar.d规则制作%: %.o

可以找到从现有文件中制作../common/obj/bar.d.o的方法 因为我们只给了它一个! -食谱 B

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

这告诉Make是否有任何匹配$(OBJDIRCOMMON)/%.o的目标(例如../common/obj/bar.d.o) 不存在,但是$(COMMONDIR)/Makefile确实存在(确实存在),则该目标 通过运行以下命令来更新:

$(MAKE) -C $(COMMONDIR)

使我们相信。它运行了$(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'

,然后认为../common/obj/bar.d.o是最新的。因此它前进到:

../common/obj/bar.d: ../common/obj/bar.d.o
    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

并运行:

gcc   ../common/obj/bar.d.o   -o ../common/obj/bar.d

由于我们撒谎而失败了:

make -C ../common

根本不做../common/obj/bar.d.o

gcc: error: ../common/obj/bar.d.o: No such file or directory

这对于食谱 A 不会发生,因为

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

不提供不提供提供一种方法,从现有文件中制作$(OBJDIRCOMMON)/bar.d.o, 因为$(OBJDIRCOMMON)/bar.d.c不存在。所以../common/obj/bar.d不是 一个目标。

粘贴食谱 A ,因为它是正确的,食谱 B 是错误的。同时查看 并修复makefile,以使make clean始终删除所有可能已构建的非.PHONY目标,而不会删除其他任何目标。最后,避免在配方未提及目标的情况下使用非.PHONY目标编写配方。

答案 1 :(得分:0)

应该有&&而不是管道

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