在依赖项列表更改时重新链接Makefile目标

时间:2011-07-26 19:51:55

标签: dependencies makefile gnu-make

我在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指定的任何其他非源依赖项。

有没有官方的方法来做到这一点,或者至少是一种更好的方式?

(另外,如果可能的话,我还希望在删除源文件时清理关联的对象和依赖文件。这可能通过扩展我的第二个解决方案并使其更加丑陋来实现。)

3 个答案:

答案 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}))