GNU make:使用生成的头文件生成自动依赖项

时间:2011-03-08 07:23:31

标签: makefile auto-generate gnu-make

所以我按照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片段的树

4 个答案:

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

备注:

  1. 我使用order-only dependency

  2. 此解决方案具有相当的可扩展性:每个生成标头规则只需要是generated_headers .PHONY目标的先决条件。假设标题生成规则写得正确,一旦正确生成,满足generated_headers目标应该是无操作。

  3. 无法编译单个对象,即使该对象不需要任何生成的标题,也不能首先生成所有生成的项目标题。虽然这在技术上是合理的,但您的开发人员会抱怨。

    所以你应该考虑使用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.omain.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个问题:

  1. 如果有任何变化,我们会重新执行make
  2. 丑陋但无害的警告:&#34; main.d:没有这样的文件或目录&#34;
  3. 致命错误&#34;没有规则制作目标foo.h&#34;如果删除foo.h文件,甚至 如果提及它从.c文件中删除。
  4. 使用-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

如果生成的标头直接包含在少量文件中,这可能是一种可行的方法。