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

发布于 2024-10-20 16:40:55 字数 2410 浏览 5 评论 0原文

因此,我遵循了高级自动依赖生成论文——

Makefile

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 成为生成文件时 --

Makefile:

...

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 才被生成。 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.

所以我的问题是:是否有一种方法可以扩展上面论文建议的方法,以允许生成头文件,而不消除性能在包含 *.d 片段时不必重新评估整个 make 树,从而获得收益?

So I followed the Advanced Auto-Dependency Generation paper --

Makefile:

SRCS := main.c foo.c

main: main.o foo.o

%.o: %.c
    $(CC) -MMD -MG -MT '$@ $*.d' -c 
lt; -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

-- and it works like a charm.


But when foo.h becomes a generated file --

Makefile:

...

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

The 1st time I run make, main.d is not yet generated, and thus foo.h is not considered a prerequisite, and thus isn't been generated:

$ 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*

Only in the 2nd invocation of make, the foo.h is generated, and as a result another build cascades.

$ 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*

And only after that make realizes that:

$ make
make: `main' is up to date.

So my question is: Is there a way to extend the recipe suggested by the paper above, to allow for generated header files, without the elimination of the performance gain realized by not having to re-evaluate the entire make tree when including the *.d fragments?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

梦晓ヶ微光ヅ倾城 2024-10-27 16:40:55

问题是,*.d Makefile 片段生成必须在所有标头生成完成之后执行。这样说,可以使用 make 依赖项来强制正确的顺序:

SRCS := main.c foo.c
HDRS := foo.h

main: main.o foo.o

%.o: %.c | generated_headers
    $(CC) -MMD -MG -MT '$@ $*.d' -c 
lt; -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. 此解决方案具有相当高的可扩展性:每个生成标头规则只需成为 generated_headers .PHONY 目标的先决条件。假设标头生成规则编写正确,一旦正确生成,满足 generate_headers 目标应该是无操作。

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

    因此,您应该考虑使用 FAST_AND_LOOSE 标志,这将关闭此功能:

    <前><代码>%.o: %.c | $(如果$(FAST_AND_LOOSE),,生成的_headers)
    ...

    因此开发人员可能会发出:

    make FAST_AND_LOOSE=1 main.o
    

The problem is that the *.d Makefile-fragments generation must be performed after all the header generation is complete. Putting it this way, one can use the make dependencies to force the right order:

SRCS := main.c foo.c
HDRS := foo.h

main: main.o foo.o

%.o: %.c | generated_headers
    $(CC) -MMD -MG -MT '$@ $*.d' -c 
lt; -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

Notes:

  1. I use an order-only dependency.

  2. This solution is fairly scalable: Each generate-header rule, needs only to be a prerequisite of the generated_headers .PHONY target. Assuming that the header generation rule is written properly, once it has been generated correctly, satisfying the generated_headers target should be a no-op.

  3. One can't compile a single object, even if that object does not require any generated headers, without generating all the generated headers of the project first. While this is technically sound, your developers will complain.

    So you should think about having a FAST_AND_LOOSE flag, that will turn this feature off:

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

    Thus a developer may issue:

    make FAST_AND_LOOSE=1 main.o
    
月牙弯弯 2024-10-27 16:40:55

原始问题中的 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 
lt;
    $(CC) -c 
lt; -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 $@ 
lt;

%.o: %.c
    $(CC) -c 
lt; -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”如果 foo.h 文件被删除,甚至
    如果从 .c 文件中删除了它的提及。

问题2通过使用-include而不是include解决。尽我所能
告诉我,这与本文避免重新执行的技术正交
制作。至少我还没有能够通过使用 -include 造成任何问题
而不是 include

问题 3 通过 GCC 的 -MP (或等效的 sed 脚本)解决——这是
也与避免重新执行 make 的技术正交。

问题 1 或许可以通过如下方式得到一定程度的改善:

%.d: %.c
    $(CC) -MM -MG -MP -MT $*.o -MF [email protected] 
lt;
    cmp [email protected] $@ 2>/dev/null || mv [email protected] $@; rm -f [email protected]

更改之前:

$ 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 进行测试。

The makefile in the original question doesn't work for me with 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

I guess gcc changed the behaviour of -MG at some point in the last 4 years.

It seems that if you want to support generated header files, there is no longer
any way to generate the ".d" file and the ".o" file at the same time, without
invoking the C preprocessor twice.

So I've updated the recipe to:

%.o: %.c
    $(CC) -MM -MG -MP -MT $*.o -MF $*.d 
lt;
    $(CC) -c 
lt; -o $@

(Note also that gcc now has -MP to generate phony targets for each header,
so you no longer need to run sed on gcc's output.)

We still have the same problem as the original question -- running make the
first time fails to generate 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

Running it again works:

$ 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

Since we have to run the C preprocessor twice anyway, let's generate the .d
file in a separate rule:

%.d: %.c
    $(CC) -MM -MG -MP -MT $*.o -MF $@ 
lt;

%.o: %.c
    $(CC) -c 
lt; -o $@

Now it generates the header file correctly:

$ 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

Does this suffer from the performance issue that the original question was
trying to avoid? This is essentially the "Basic Auto-Dependencies" solution
described in the Advanced Auto-Dependency
Generation
paper.

That paper claims 3 problems with this solution:

  1. We re-exec make if anything changes.
  2. Ugly but harmless warning: "main.d: No such file or directory"
  3. Fatal error "no rule to make targe foo.h" if foo.h file is removed, even
    if mention of it is removed from the .c file.

Problem 2 is solved by using -include instead of include. As far as I can
tell, this is orthogonal to the paper's technique for avoiding re-exec of
make. At least I haven't been able to cause any problems by using -include
instead of include.

Problem 3 is solved by GCC's -MP (or the equivalent sed script) -- this is
also orthogonal to the technique for avoiding re-exec of make.

Problem 1 can be perhaps ameliorated somewhat by something like this:

%.d: %.c
    $(CC) -MM -MG -MP -MT $*.o -MF [email protected] 
lt;
    cmp [email protected] $@ 2>/dev/null || mv [email protected] $@; rm -f [email protected]

Before that change:

$ 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

After that change:

$ 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

Slightly better. Of course if a new dependency is introduced, make will still
need to re-execute. Maybe there's nothing that can be done to improve this; it seems to be a tradeoff between correctness and speed.

All of the above was tested with make 3.81.

梦归所梦 2024-10-27 16:40:55

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

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

所以我们必须使用...递归make! [Dun-dun-dunnnn!]

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

Short answer: no. The recipe described in the paper is very clever, one of my favorites, but it's a sophisticated use of a crude tool. It takes advantage of the usual scheme in which all needed headers exist; what it tries to solve is the problem of determining which headers, if recently modified, require the given object file to be rebuilt. In particular, if the object file doesn't exist then it must be rebuilt-- and in that case there's no reason to worry about the header files because the compiler will surely find them.

Now header files are generated. So foo.h may not exist, so somebody will have to run the script to generate it, and only Make can do that. But Make can't know that foo.h is necessary without performing some analysis of main.c. But that really can't happen until Make starts to execute main-related rules (e.g main.o or main.o.d), which it cannot execute until after it has decided which targets it is going to build.

So we will have to use... recursive make! [Dun-dun-dunnnn!]

We can't achieve the paper's goal of avoiding reinvocation of Make, but we can at least avoid (some) unnecessary rebuilding. You could do something like the "Basic Auto-Dependencies" described in the paper; the paper describes the problems of that approach. Or you could use a command like the one in the "Advanced" recipe to generate a list of headers, then pass that to $(MAKE); this approach is tidy, but might call Make many times on the same header, depending on what your code tree looks like.

绿光 2024-10-27 16:40:55

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

main.o: foo.h

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

You could create an explicit dependency rule for your generated header:

main.o: foo.h

If the generated header is directly included in a small number of files, this may be a workable approach.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文