【问题标题】:When does `make` apply the implicit rule `Makefile: Makefile.o`?`make` 何时应用隐式规则 `Makefile: Makefile.o`?
【发布时间】:2020-06-23 10:19:51
【问题描述】:

在我的工作目录中,有一个makefile、一个空的obj 文件夹和一个包含foo.csrc 文件夹。我有一个简单的规则,它接受一个目标文件,用 src 替换第一个斜杠之前的所有内容,并用 .c 替换结尾的 .o

示例: obj/foo.o --> src/foo.c

MadScientist 的帮助下(感谢他)我得到了下面的模式规则(请不要提出更简单的规则 --> 我知道它们的存在,但只有这条规则说明了我的问题):

OBJFILES=obj/foo.o
all: $(OBJFILES)
    @echo "all executed"

Y = $(shell echo $1 | sed -e 's,^[^\/]*,src,' -e 's,\.o$$,.c,')
.SECONDEXPANSION:
%.o: $$(call Y,$$@)
    @echo "OBJECTS executed for $@ - [$^]"

src/foo.c:
    @echo "Congrats"

运行上面的 makefile 后,我得到一个循环依赖:

xxxx@null:~/Desktop/experiment$ make
make: Circular src.o <- src dependency dropped.
OBJECTS executed for obj/foo.o - [src/foo.c]
all executed

原因很快就找到了:Make 创建了隐式规则Makefile: Makefile.o,它触发了我的模式规则。由于Makefile.o 不包含斜线,因此sed 的计算结果为srcMake 再次应用隐式规则 src:src.o 最终导致循环依赖。这可以在 make 输出中看到:

make --print-data-base | grep src
make: Circular src.o <- src dependency dropped.
OBJECTS executed for obj/foo.o - [src/foo.c]
Y = $(shell echo $1 | sed -e 's,^[^\/]*,src,' -e 's,\.o$$,.c,')
# src (device 64769, inode 14031369): No files, no impossibilities so far.
Makefile.o: src
src: src.o
#  Implicit/static pattern stem: 'src'
src.o:
#  Implicit/static pattern stem: 'src'
# @ := src.o
# * := src
# < := src.o
obj/foo.o: src/foo.c
# + := src/foo.c
# < := src/foo.c
# ^ := src/foo.c
# ? := src/foo.c
src/foo.c:

如果我们现在以这种方式重新定义YY = $(patsubst obj/%.o,src/%.c,$1),则不会发生循环依赖,因为make 甚至不会尝试应用隐式规则Makefile:Makefile.o

make --print-data-base | grep src
OBJECTS executed for obj/foo.o - [src/foo.c]
Y = $(patsubst obj/%.o,src/%.c,$1)
# src (device 64769, inode 14031369): No files, no impossibilities so far.
obj/foo.o: src/foo.c
# + := src/foo.c
# < := src/foo.c
# ^ := src/foo.c
# ? := src/foo.c
src/foo.c:

make 何时创建隐式规则Makefile: Makefile.oY的两种不同定义有什么区别?

【问题讨论】:

    标签: makefile gnu-make


    【解决方案1】:

    首先,如果您要使用二次扩展,您有责任确保结果正确。在模式规则中,这意味着您的扩展必须处理 % 的所有不同值。在这里,您的问题基本上是您没有正确处理不包含斜杠的 % 值,但您的目标 %.o 将适用于所有目标,而不仅仅是子目录中的目标。您需要修复您的 sed 调用。一种选择是:

    Y = $(shell echo $1 | sed -e 's,^[^\/]*/,src/,' -e 's,\.o$$,.c,')
    

    通过添加斜线,替换将不匹配简单的文件名。但是,.o 替换为 .c 仍然会发生。

    您看到此行为的原因是因为 make tries to remake makefiles。所以它想建立一个目标Makefile。有一个内置的模式规则知道如何从具有相同名称的目标文件构建可执行文件:% : %.o Makefile 匹配 %,因此 make 尝试构建 Makefile.o。它会看到您的模式规则 %.o 并尝试应用它,并在第二次扩展中将 Makefile.o 转换为 src,如上所述。

    所以,除了上述之外,您还可以考虑其他事项:

    如果您有足够新的 make 版本(并且您定义了所有自己的规则),您可以通过添加以下内容来禁用所有内置规则:

    MAKEFLAGS += -r
    

    这是一个好主意,因为它可以通过删除所有内置规则使 make 运行得更快。

    其次,您可以将模式更改为更具体,使其仅匹配目标文件,如下所示:

    obj/%.o: $$(call Y,$$@)
            ...
    

    现在,此模式规则将匹配该子目录中的 .o 文件。

    【讨论】:

    • 谢谢。我知道make 试图重新制作makefile,而后者与% : %.o 规则相匹配。但是如果我使用Y = $(patsubst obj/%.o,src/%.c,$1) 作为先决条件,为什么make 不尝试重新制作makefile?目标不会改变,因此应该触发相同的内置模式规则。
    • 它确实尝试了该模式规则。但是,patsubst 在这种情况下将不匹配(模式 obj/%.o 与文本 Makefile.o 不匹配)因此替换的结果与输入文件相同。也就是说,你得到Makefile.o : Makefile.o。模式替换中有一条规则,我们总是忽略我们已经在这个链中考虑过的任何构造目标,所以它会被忽略并继续进行。
    • 使用-p 的输出并不是查看正在发生什么的好方法。这只告诉你所有检查的最终结果。它不会告诉您任何有关 make 在得出该结果时采取了哪些步骤以及忽略了哪些步骤。如果你想知道,你必须使用-d 选项。请注意,有很多输出。但是您会看到 make 确实考虑了它;它会说寻找带有中间文件'Makefile.o'的规则。然后是尝试带有词干'Makefile'的模式规则。尝试隐式先决条件'Makefile .o'.,然后将其忽略。
    • 在您的 sed 示例中,sed 操作的结果不是Makefile.o,而是src。这就是我在上面要说的,关于您的 sed 操作是错误的事实:它错误地处理了没有目录的文件名。所以结果是Makefile.o : src,并且src 确实 存在(它显然是你环境中的一个目录)所以这个确实 匹配并尝试使用这个规则.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多