【问题标题】:GNU make: Generating automatic dependencies with generated header filesGNU make:使用生成的头文件生成自动依赖项
【发布时间】:2011-07-10 21:59:29
【问题描述】:

所以我关注了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

我第一次运行makemain.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 片段时的树?

【问题讨论】:

    标签: makefile auto-generate gnu-make


    【解决方案1】:

    问题是*.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. 此解决方案具有相当的可扩展性:每个 generate-header 规则只需成为 generated_headers .PHONY 目标的先决条件。假设头部生成规则写得正确,一旦生成正确,满足generated_headers目标应该是空操作。

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

      所以你应该考虑使用FAST_AND_LOOSE 标志,这将关闭此功能:

      %.o: %.c | $(if $(FAST_AND_LOOSE),,generated_headers)
          ...
      

      因此开发者可能会发布:

      make FAST_AND_LOOSE=1 main.o
      

    【讨论】:

      【解决方案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 的行为。

      好像要支持生成的头文件,就没有了 同时生成“.d”文件和“.o”文件的任何方式,无需 调用 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
      

      这是否受到原始问题的性能问题的影响 试图避免?这本质上是“基本自动依赖”解决方案 在Advanced Auto-Dependency Generation 论文中描述。

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

      1. 如果有任何变化,我们将重新执行 make
      2. 丑陋但无害的警告:“main.d:没有这样的文件或目录”
      3. 如果 foo.h 文件被删除,甚至 如果提及它已从 .c 文件中删除。

      问题 2 通过使用 -include 而不是 include 来解决。尽我所能 告诉,这与论文避免重新执行的技术正交 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】:

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

        现在生成头文件。所以foo.h 可能不存在,所以必须有人运行脚本来生成它,而只有 Make 可以做到这一点。但是如果不对main.c 进行一些分析,Make 就无法知道foo.h 是必要的。但这在 Make 开始执行 main 相关规则(例如 main.omain.o.d)之前确实不会发生,直到 它决定要执行哪些目标之后才能执行构建。

        所以我们将不得不使用...递归make! [顿顿顿顿!]

        我们无法实现论文中避免重新调用 Make 的目标,但我们至少可以避免(一些)不必要的重建。您可以执行本文中描述的“基本自动依赖”之类的操作;该文件描述了这种方法的问题。或者您可以使用类似于“高级”配方中的命令来生成标题列表,然后将其传递给$(MAKE);这种方法很整洁,但可能会在同一个标​​头上多次调用 Make,具体取决于您的代码树的样子。

        【讨论】:

          【解决方案4】:

          您可以为生成的标头创建显式依赖规则:

          main.o: foo.h
          

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

          【讨论】:

          • 我已经意识到这将强制正确的依赖,但是这不会扩展,并且面对整个努力试图解决的问题。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-05-03
          • 1970-01-01
          • 1970-01-01
          • 2012-06-25
          • 1970-01-01
          • 1970-01-01
          • 2021-01-12
          相关资源
          最近更新 更多