所以我按照Advanced Auto-Dependency Generation论文 -
生成文件:
SRCS := main.c foo.c
main: main.o foo.o
%.o: %.c
$(CC) -MMD -MG -MT '$@ $*.d' -c $< -o $@
cp $*.d $*.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$$;;' \
-e '/^$$/d' -e 's;$$; :;' < $*.tmp >> $*.d
rm $*.tmp
clean::
-rm *.o *.d main
-include $(SRCS:.c=.d)
main.c中:
#include "foo.h"
int main(int argc, char** argv) {
foo() ;
return 0 ;
}
foo.h中:
#ifndef __FOO_H__
#define __FOO_H__
void foo() ;
#endif
- 它就像一个魅力。
但是当foo.h
成为生成的文件时 -
生成文件:
...
HDRS := foo.h
$(HDRS):
mk_header.sh $*
clean::
-rm $(HDRS)
...
mk_header.sh:
#!/bin/bash
UP=$(tr "[:lower:]" "[:upper:]" <<< $1)
cat <<EOF > $1.h
#ifndef __${UP}_H__
#define __${UP}_H__
void $1() ;
#endif
EOF
我第一次运行make
时,尚未生成main.d
,因此foo.h
不被视为先决条件,因此未生成:
$ ls
foo.c main.c Makefile mk_header.sh*
$ make
cc -MMD -MG -MT 'main.o main.d' -c main.c -o main.o
cp main.d main.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$;;' \
-e '/^$/d' -e 's;$; :;' < main.tmp >> main.d
rm main.tmp
cc -MMD -MG -MT 'foo.o foo.d' -c foo.c -o foo.o
cp foo.d foo.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$;;' \
-e '/^$/d' -e 's;$; :;' < foo.tmp >> foo.d
rm foo.tmp
cc main.o foo.o -o main
$ ls
foo.c foo.d foo.o
main* main.c main.d main.o
Makefile mk_header.sh*
仅在make
的第二次调用中,生成foo.h
,结果是另一个构建级联。
$ make
./mk_header.sh foo
cc -MMD -MG -MT 'main.o main.d' -c main.c -o main.o
cp main.d main.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$;;' \
-e '/^$/d' -e 's;$; :;' < main.tmp >> main.d
rm main.tmp
cc main.o foo.o -o main
$ ls
foo.c foo.d foo.h foo.o
main* main.c main.d main.o
Makefile mk_header.sh*
只有在那之后make
才意识到:
$ make
make: `main' is up to date.
所以我的问题是:是否有办法扩展上述文件中建议的配方,以允许生成的头文件,而无需通过不必重新评估整个make来实现性能增益的消除包含*.d
片段的树
答案 0 :(得分:12)
问题是在所有标头生成完成后,必须执行*.d
Makefile-fragments生成。用这种方式,可以使用make依赖项来强制正确的顺序:
SRCS := main.c foo.c
HDRS := foo.h
main: main.o foo.o
%.o: %.c | generated_headers
$(CC) -MMD -MG -MT '$@ $*.d' -c $< -o $@
cp $*.d $*.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$$;;' \
-e '/^$$/d' -e 's;$$; :;' < $*.tmp >> $*.d
rm $*.tmp
-include $(SRCS:.c=.d)
$(HDRS):
mk_header.sh $*
generated_headers: $(HDRS)
clean:
-rm $(HDRS) *.o *.d main
.PHONY: clean generated_headers
备注:强>
此解决方案具有相当的可扩展性:每个生成标头规则只需要是generated_headers
.PHONY
目标的先决条件。假设标题生成规则写得正确,一旦正确生成,满足generated_headers
目标应该是无操作。
无法编译单个对象,即使该对象不需要任何生成的标题,也不能首先生成所有生成的项目标题。虽然这在技术上是合理的,但您的开发人员会抱怨。
所以你应该考虑使用FAST_AND_LOOSE
标志,这将关闭此功能:
%.o: %.c | $(if $(FAST_AND_LOOSE),,generated_headers)
...
因此,开发人员可以发出:
make FAST_AND_LOOSE=1 main.o
答案 1 :(得分:2)
简答:不。本文中描述的配方非常聪明,是我最喜欢的配方之一,但它是一种复杂的粗糙工具。它利用了通常的方案,其中存在所有需要的头;它试图解决的问题是确定哪些标题(如果最近修改过)需要重建给定的目标文件。特别是,如果目标文件不存在则必须重建 - 在这种情况下,没有理由担心头文件,因为编译器肯定会找到它们。
现在生成头文件。所以foo.h
可能不存在,所以有人必须运行脚本来生成它,只有Make可以做到这一点。但是,如果不对foo.h
进行一些分析,Make就无法知道main.c
是必要的。但是,在Make开始执行main
相关规则(例如main.o
或main.o.d
)之前,这种情况真的不可能发生,直到之后它才能执行决定了它将要建立的目标。
所以我们将不得不使用...递归制作! [敦敦dunnnn!
我们无法实现本文避免重新定位Make的目标,但我们至少可以避免(某些)不必要的重建。你可以做一些像论文中描述的“基本自动依赖”;该论文描述了该方法的问题。或者您可以使用类似“高级”配方中的命令生成标题列表,然后将其传递给$(MAKE)
;这种方法很整洁,但可能会在同一个标题上调用Make多次,具体取决于代码树的外观。
答案 2 :(得分:2)
原始问题中的makefile对我来说并不适用于gcc 4.8.2:
cc -MMD -MG -MT main.d -c main.c -o main.o
cc1: error: -MG may only be used with -M or -MM
我猜gcc在过去的4年中某些时候改变了-MG
的行为。
似乎如果您想支持生成的头文件,则不再存在 以任何方式生成&#34; .d&#34;文件和&#34; .o&#34;文件同时,没有 两次调用C预处理器。
所以我已将配方更新为:
%.o: %.c
$(CC) -MM -MG -MP -MT $*.o -MF $*.d $<
$(CC) -c $< -o $@
(另请注意,gcc现在有-MP
为每个标头生成虚假目标,
所以你不再需要在gcc的输出上运行sed。)
我们仍然遇到与原始问题相同的问题 - 运行make
第一次无法生成foo.h
:
$ make
cc -MM -MG -MP -MT main.o -MF main.d main.c
cc -c main.c -o main.o
main.c:1:17: fatal error: foo.h: No such file or directory
#include "foo.h"
^
compilation terminated.
Makefile:7: recipe for target 'main.o' failed
make: *** [main.o] Error 1
再次运行它:
$ make
./mk_header.sh foo
cc -MM -MG -MP -MT main.o -MF main.d main.c
cc -c main.c -o main.o
cc main.o -o main
因为我们必须两次运行C预处理器,所以让我们生成.d
文件在单独的规则中:
%.d: %.c
$(CC) -MM -MG -MP -MT $*.o -MF $@ $<
%.o: %.c
$(CC) -c $< -o $@
现在它正确生成头文件:
$ make clean
rm -f *.o *.d main foo.h
$ make
cc -MM -MG -MP -MT main.o -MF main.d main.c
./mk_header.sh foo
cc -c main.c -o main.o
cc main.o -o main
这是否会受到原始问题的性能问题的影响 试图避免?这基本上是&#34;基本自动依赖&#34;解 在Advanced Auto-Dependency Generation论文中描述。
该论文声称该解决方案有3个问题:
make
。使用-include
代替include
解决问题2。尽我所能
告诉我,这与纸张避免重新执行的技术是正交的
make
。至少我使用-include
无法解决任何问题
而不是include
。
问题3由GCC的-MP
(或等效的sed脚本)解决 - 这是
也与避免重新执行make
的技术正交。
问题1或许可以通过以下方式得到改善:
%.d: %.c
$(CC) -MM -MG -MP -MT $*.o -MF $@.new $<
cmp $@.new $@ 2>/dev/null || mv $@.new $@; rm -f $@.new
在改变之前:
$ make clean
rm -f *.o *.d main foo.h
$ make -d 2>&1 | grep Re-executing
Re-executing[1]: make -d
$ make -d 2>&1 | grep Re-executing
$ touch main.c; make -d 2>&1 | grep Re-executing
Re-executing[1]: make -d
改变之后:
$ make clean
rm -f *.o *.d main foo.h
$ make -d 2>&1 | grep Re-executing
Re-executing[1]: make -d
$ make -d 2>&1 | grep Re-executing
$ touch main.c; make -d 2>&1 | grep Re-executing
略好一点。当然,如果引入了新的依赖关系,make
仍将存在
需要重新执行。也许没有什么可以改善这一点;它似乎是正确性和速度之间的权衡。
所有上述内容均以make 3.81进行测试。
答案 3 :(得分:1)
您可以为生成的标头创建显式依赖关系规则:
main.o: foo.h
如果生成的标头直接包含在少量文件中,这可能是一种可行的方法。