11

所以我遵循了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)

主.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*

只有在第二次调用 时makefoo.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

4 回答 4

12

问题是*.dMakefile-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. 我使用仅订单依赖项

  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
    
于 2011-04-20T13:26:12.643 回答
3

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

-MG我猜 gcc在过去 4 年的某个时间点改变了行为。

看来,如果要支持生成的头文件,就没有办法同时生成“.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

这是否受到原始问题试图避免的性能问题的影响?这本质上是高级自动依赖生成论文中描述的“基本自动依赖”解决方案。

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

  1. make如果有任何变化,我们将重新执行。
  2. 丑陋但无害的警告:“main.d:没有这样的文件或目录”
  3. 如果 foo.h 文件被删除,即使从 .c 文件中删除了它的提及,也会出现致命错误“no rule to make targe foo.h”。

问题 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 进行了测试。

于 2015-04-13T15:58:26.070 回答
2

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

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

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

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

于 2011-03-09T03:47:49.673 回答
1

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

main.o: foo.h

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

于 2011-03-08T10:24:14.847 回答