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

Makefile GNU make:使用生成的头文件生成自动依赖项,makefile,auto-generate,gnu-make,Makefile,Auto Generate,Gnu Make,所以我跟着报纸走-- 生成文件: ... HDRS := foo.h $(HDRS): mk_header.sh $* clean:: -rm $(HDRS) ... foo.h: --它就像一个符咒 但是当foo.h成为生成的文件时-- 生成文件: ... HDRS := foo.h $(HDRS): mk_header.sh $* clean:: -rm $(HDRS) ... mk_header.sh: #!/bin/bash UP=$(t

所以我跟着报纸走--

生成文件:

...

HDRS := foo.h

$(HDRS):
    mk_header.sh $*

clean::
    -rm $(HDRS)
...
foo.h:

--它就像一个符咒


但是当
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
时,才会生成
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*


因此,我的问题是:有没有一种方法可以扩展上述论文建议的方法,以允许生成头文件,如果不消除在包含
*.d
片段时不必重新评估整个生成树所实现的性能增益,您可以为生成的头创建一个明确的依赖规则:

main.o: foo.h

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

简短回答:不。论文中描述的方法非常聪明,是我最喜欢的方法之一,但它是一种复杂的原始工具的使用。它利用了通常的方案,其中所有需要的头都存在;它试图解决的问题是确定哪些头(如果最近修改过)需要重建给定的对象文件。特别是,如果对象文件不存在,则必须重建它——在这种情况下,没有理由担心头文件,因为编译器肯定会找到它们

现在生成了头文件。因此,
foo.h
可能不存在,因此必须有人运行脚本来生成它,并且只有Make可以这样做。但是,如果不对
main.c
进行一些分析,Make就无法知道
foo.h
是必要的。但是,直到Make开始执行与
main
相关的规则(例如
main.o
main.o.d
),Make才真正能够执行这些规则,直到它决定要构建哪些目标

所以我们必须使用。。。递归生成![Dun-dunnn!]


我们无法实现论文中避免重新使用Make的目标,但我们至少可以避免(一些)不必要的重建。你可以做一些像论文中描述的“基本的自相关性”;本文描述了这种方法的问题。或者您可以使用类似“高级”配方中的命令来生成标题列表,然后将其传递给
$(MAKE)
;这种方法是整洁的,但可能会在同一个头上多次调用Make,这取决于代码树的外观。

问题是,
*.d
生成文件片段必须在所有头生成完成后执行。通过这种方式,可以使用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
因此,开发商可以发布:

make FAST_AND_LOOSE=1 main.o

原始问题中的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
的行为

似乎如果您想支持生成的头文件,就不再需要 任何同时生成“.d”文件和“.o”文件的方法,无需 调用C预处理器两次

因此,我已将配方更新为:

%.o: %.c
    $(CC) -MM -MG -MP -MT $*.o -MF $*.d $<
    $(CC) -c $< -o $@
再次运行它可以工作:

$ 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 $@
这是否会受到原始问题的性能问题的影响 试图避免?这本质上就是“基本的自相关性”解决方案 本文对此进行了描述

该文件声称该解决方案存在3个问题:

  • 如果有任何变化,我们将重新执行
    make
  • 丑陋但无害的警告:“main.d:没有这样的文件或目录”
  • 如果删除了foo.h文件,则出现致命错误“无规则生成target foo.h”,甚至 如果从.c文件中删除了对它的提及
  • 问题2通过使用
    -include
    而不是
    include
    来解决。尽我所能 这与论文避免重复执行的技巧是正交的
    make
    。至少我使用
    -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进行了测试。

    我已经意识到,这将强制实现正确的依赖关系,但是这不会扩展,并且与整个努力试图解决的问题背道而驰。
    $ 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
    
    %.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
    
    %.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