Dependencies 当依赖项列表更改时,重新链接Makefile目标

Dependencies 当依赖项列表更改时,重新链接Makefile目标,dependencies,makefile,gnu-make,Dependencies,Makefile,Gnu Make,我在Makefiles中看到的一个常见问题是,当依赖项列表更改时,不一定会重新链接可执行文件(以及库)。例如: SRCS=$(wildcard *.c) CPPFLAGS=-MMD -MP target: $(SRCS:.c=.o) $(CC) $(LDFLAGS) $^ -o $@ -include $(SRCS:.c=.d) 到目前为止,还不错:它自动处理对源文件及其包含的依赖项的任何更改。如果添加了一个文件(由于通配符,这意味着只需将其添加到目录中,但显式列表也会有同样的

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

有没有一个官方的方法来做到这一点,或者至少有更好的方法


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

你需要一个更智能的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

如果您想尝试一下,您可以。

理想情况下,应该只有一个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}))

为了做好这件事,你可能不得不求助于。Paul是GNU make的当前维护者,他描述了所有关于依赖项生成的常见问题(及其解决方案!)

他的网站上的其他白皮书也被推荐


我使用他的技巧和技巧已经有好几年了,而且——即使有时会变得有点复杂——研究它们每一秒都是值得的。

我想,明确列出依赖项并依赖于Makefile或
$(Makefile_LIST)
(也列出包括Makefile在内的任何依赖项)是最常见的解决方案。作为第三种选择,您还可以将此技巧应用于SRCS宏:您能详细说明“我以前见过这种情况”吗?如果删除了源文件,重建有什么意义?重建将失败,或者源代码已失效。我能想到的唯一目的是:1)通过删除死二进制代码来精简可执行文件;2)检测丢失的文件;3)通过破坏性测试来检测无用的源文件。似乎没有一个值得如此努力来实现自动化。主要动机是:源文件(实际上是C++)中只有静态初始值设定项,它们将自己添加到全局实例化列表中(不是最好的设计,但这正是我所使用的)。删除文件不会导致重新链接(因为其他文件都没有更改),并且会导致一些真正令人困惑的调试。这是一种可能发生的情况,但非常罕见。我将不得不研究emake——它有一些听起来很有希望的特性。但目前我对在我们的开发环境中允许使用的工具非常有限。