我在Makefiles中看到的一个常见问题是,当依赖项列表发生更改时,可执行文件(以及库)不一定会重新链接。例如:
SRCS=$(wildcard *.c)
CPPFLAGS=-MMD -MP
target: $(SRCS:.c=.o)
$(CC) $(LDFLAGS) $^ -o $@
-include $(SRCS:.c=.d)
到目前为止,非常好:它会自动处理源文件及其包含的依赖项的任何更改。如果添加了一个文件(因为通配符,这意味着只是将它添加到目录中,但显式列表会出现同样的问题),make会看到它并重新链接目标。但是,当删除源文件时,make不知道需要重建目标。
如何让make(gmake,特别是)来处理这种情况?
不可否认,这并不是一个常见的情况,因为删除文件很可能意味着其他文件也必须更改,然后无论如何都会强制重新链接,但我之前已经看过它。
我提出了两种解决方案,两者都不理想。首先,如果我们使源列表显式而不是通配符,我们可以使目标依赖于Makefile。更改Makefile以删除源文件然后导致目标重新链接。这有两个问题。首先,你必须在链接之前从$ ^中删除Makefile,这有点难看(但可以使用filter-out)。其次,这个Makefile是一个模板,包含在其他Makefile中(指定源和目标) - 我们必须使目标依赖于那个 Makefile。啊。
第二种解决方案是包括如下逻辑:
SRCS=$(wildcard *.c)
CPPFLAGS=-MMD -MP
target: $(SRCS:.c=.o)
$(CC) $(LDFLAGS) $^ -o $@
@echo '$$(if $$(filter-out $$(SRCS:.c=.o), $(SRCS:.c=.o)), .PHONY:target)' > target.d
-include $(SRCS:.c=.d)
-include target.d
这使得target.d文件跟踪依赖项列表并强制重建目标(如果删除了任何内容)。它有效,但它很难看。它也无法解释包含Makefile指定的任何其他非源依赖项。
有没有官方的方法来做到这一点,或者至少是一种更好的方式?
(另外,如果可能的话,我还希望在删除源文件时清理关联的对象和依赖文件。这可能通过扩展我的第二个解决方案并使其更加丑陋来实现。)
答案 0 :(得分:3)
要做得好,你可能不得不诉诸Paul Smith's Advanced Auto-Dependency Generation。 Paul是GNU make的当前维护者,并描述了有关依赖关系生成的所有常见问题(及其解决方案!)。
他的网站上也推荐了其他白皮书。
我现在使用他的提示和技巧已经好几年了 - 即使偶尔变得有点复杂 - 研究它们也值得每一秒。
答案 1 :(得分:0)
您需要更智能的gmake版本。 ElectricMake是一个兼容gmake的替代产品,它包含一个分类帐功能,它基本上使得最新的检查比检查输出比输入更新更复杂。特别是,分类帐将对目标的输入列表进行哈希处理,如果该列表发生更改,则会导致重建输出:
# cat Makefile
out: a b c
@echo out from $^
@touch out
# emake --emake-ledger=timestamp
out from a b c
# emake --emake-ledger=timestamp
make: `out' is up to date.
# vi Makefile ;# Remove 'c' from the prereq list.
# cat Makefile
out: a b
@echo out from $^
@touch out
# emake --emake-ledger=timestamp
out from a b
如果您想尝试一下,可以get an eval copy。
答案 2 :(得分:0)
理想情况下,应该只有一个make模式规则来执行所有链接(好吧,一个用于可执行文件,一个用于共享库,一个用于存档)。类似的东西:
# rules.mk
$(BUILD_DIR}/exe/% : rules.mk
g++ -o $@ $(filter-out %.mk,$^)
接下来,当你定义构建目标时,让它们依赖于它自己的makefile:
# project_a.mk
$(BUILD_DIR}/exe/a : project_a.mk ${a_obj}
通常,此依赖项将包含在宏中:
define EXE_TARGET
$(BUILD_DIR}/exe/${1} : ${2}
$(BUILD_DIR}/exe/${1} : $(lastword $(MAKEFILE_LIST))
endef
所以在project_a.mk中它会这样做:
# project_a.mk
$(eval $(call EXE_TARGET,a,${a_obj}))