在没有PHONY的情况下制作重建二进制文件的技巧

时间:2016-05-28 06:01:28

标签: makefile gnu-make

我有一组有时需要重新生成的文件。标准制作东西。我有一条知道如何重生的规则。

# RewriteEngine On # # ErrorDocument 404 /err.php?id=404 # ErrorDocument 403 /err.php?id=403 # ErrorDocument 500 /err.php?id=500

这很好用。问题是 $(foreach x, $(BIG_LIST), $(eval $(x): $(x).in)) $(BIG_LIST): opaque_binary -o $@ $^ 有时也需要重建 - 我无法表达它的依赖关系。具体来说,它是一个Go二进制文件(内部处理重建代码),Go可能需要4-5秒才能找到"这里无需做任何事情"。我不能简单地在我的食谱中重建它 - 太慢了。

目前我只是从我的opaque_binary规则单方面运行它,这很烦人,因为大部分时间它是最新的,我不需要重新生成我的BIG_LIST。浪费时间。

更糟糕的是,我有大约六个这样的二进制文件要构建,所以这一步最终需要30秒才能有效地做任何事情。

我希望有某种方式表达"这样做一次,但前提是BIG_LIST中的任何文件需要重新生成"。

2 个答案:

答案 0 :(得分:1)

首先,这个片段对我来说很奇怪。为什么不简单地使用静态模式规则?

$(BIG_LIST): %: %.in
    opaque_binary -o $@ $^

至于手头的问题,当go终于发现它已经没有什么可做的时候,我打赌它不会更新二进制文件的时间戳。然后,它做了所有评估的事实需要以其他方式记录,例如,通过自己更新时间戳。然后我们需要重新评估之后是否有任何.in文件发生了变化。

所以这就是我写它的方式:

$(BIG_LIST): %: %.in | opaque_binary
        opaque_binary -o $@ $^

opaque_binary: $(BIG_LIST:%=%.in)
        have a go at $@
        copy /B $@+,,

您可以在食谱中将opaque_binary缩写为$|

答案 1 :(得分:0)

如果我从问题和评论中直接得到了足够的问题 (也许我还没有),然后:

  • $(BIG_LIST)的成员实际上是make目录
  • 的子目录
  • 您的目标是统一位于这些子目录中的文件,但是 这些目标的先决条件不能从目标中以模式方式构建: 它们只能通过在子目录中运行shell进程来识别。
  • " opaque binary"需要执行每个目标的配方。 但是!它 已过时且必须重建,首先,如果它比任何先决条件旧。

而不是你!无论如何,对于第一次近似,你需要" opaque_binary"是: -

你当然可以断定你不能使用模式规则,如果你不能指明 图案。但是,你必须遵守$(foreach subdir,$(subdirs),$(eval ...))。 最好利用makefile 中的事实,你可以生成一个辅助的makefile 你随后 include。如果您有选择,生成的规则和配方,您保存到生成的makefile 优于仅由$(eval ...)在内存中生成的那些,因为它们是 更多 scrutable

为了说明,假装不透明的二进制文件是copy,这是一个微不足道的 从copy.c构建的C程序,它只将第一个参数命名的文件复制到标准输出。 你说你不能真正表达makefile中不透明二进制文件的构成, 但是你知道一些制作它的方法,所以我们只是假设它是copy制作的方式(这是 为了更清晰make输出,有点非正统。

这是一个项目的makefile,其中包含现有的子目录$(dirs) 一些*.x文件,我们假装我们只能通过查找来识别。从这个*.x文件 我们希望使用foo.out在同一个子目录中生成copy,但copy必须首先 如果比*.x文件旧,则重建。只是为了我们目前的方便makefile 还提供从头开始制作$(dirs)*.x文件。

Makefile(取1)

dirs := a b c
file := foo
for_convenience := \
    $(foreach dir,$(dirs),\
        $(shell mkdir -p $(dir) && ([ -f $(dir)/file.x ] || \
        echo "$(dir)/file.x content" > $(dir)/file.x)))
outs := $(dirs:%=%/$(file).out)
opaque_binary := copy
void := $(file >gen_rules.mk,)
void := \
    $(foreach dir,$(dirs), \
        $(file >>gen_rules.mk,\
            $(dir:%=%/$(file).out): $(shell ls $(dir)/*.x) | \
            $(opaque_binary); ./$(opaque_binary) $$< > $$@))
void := $(foreach dir,$(dirs),\
            $(file >>gen_rules.mk,\
                $(opaque_binary): $(shell ls $(dir)/*.x)))


.PHONY: all clean test

all: $(outs)

$(opaque_binary): copy.c
    $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LIBS)

clean:
    rm -fr $(dirs) $(opaque_binary)

test:
    cat $(outs)

include gen_rules.mk

make执行任何目标时,它将生成gen_rules.mk,其中 看起来像是:

<强> gen_rules.mk

 a/foo.out: a/file.x | copy; ./copy $< > $@
 b/foo.out: b/file.x | copy; ./copy $< > $@
 c/foo.out: c/file.x | copy; ./copy $< > $@
 copy: a/file.x
 copy: b/file.x
 copy: c/file.x

因为$(opaque_binary)copy$(outs)的仅订单先决条件, 在确定是否需要重新制作$(outs)中的任何一个时,将被考虑, 但它在任何$(outs)之前完成,并且它依赖于所有*.x 如果文件比其中任何一个旧,则会触发其配方。

从头开始,make看起来像是:

$ make
cc  -o copy copy.c  
./copy a/file.x > a/foo.out
./copy b/file.x > b/foo.out
./copy c/file.x > c/foo.out

您可以看到,a/foo.out需要尽快copy,首先制作b/foo.out, 然后不会为c/foo.out$(opaque_binary)重新制作,因为它不需要。

只是为了表明$ make test cat a/foo.out b/foo.out c/foo.out a/file.x content b/file.x content c/file.x content 已经完成了它的工作:

*.x

如果我们更新任何$(opaque_binary)个文件,则$ touch a/file.x b/file.x $ make cc -o copy copy.c ./copy a/file.x > a/foo.out ./copy b/file.x > b/foo.out 会重新制作,只需重复一次:

$(outs)

那会吗?

如果makefile仅用于此目的,那么这对您来说已经足够了

。同时制作所有$(outs)

如果它还用于制作个人$(outs)$(opaque_binary)的子集, 那么$ make make: Nothing to be done for 'all'. 的无意义构建仍有余地 你可能想要消费。 E.g。

a/file.x

一切都好。然后,代理A更新$ touch a/file.x

b/foo.out

代理B制作$ make b/foo.out cc -o copy copy.o 是最新的

$(opaque_binary)

由于*.x依赖于所有 $(opaque_binary)个文件,因此代理B已被收取1 dirs := a b c file := foo for_convenience := \ $(foreach dir,$(dirs),\ $(shell mkdir -p $(dir) && ([ -f $(dir)/file.x ] || \ echo "$(dir)/file.x content" > $(dir)/file.x))) outs := $(dirs:%=%/$(file).out) opaque_binary := copy void := $(file >gen_rules_pass1.mk,) void := $(foreach dir,$(dirs),\ $(file >>gen_rules_pass1.mk,\ $(dir:%=%/$(file).out): $(shell ls $(dir)/*.x) | \ $(opaque_binary); ./$(opaque_binary) $$< > $$@)) void := $(file >gen_rules_pass0.mk,) void := $(foreach dir,$(dirs),\ $(file >>gen_rules_pass0.mk,\ $(dir:%=%/$(file).out): $(shell ls $(dir)/*.x))) void := $(foreach dir,$(dirs),\ $(file >>gen_rules_pass0.mk,\ $(dir:%=%/$(file).out): $(dir:%=%.$(opaque_binary).d); $$(MAKE) $$@)) void := $(foreach dir,$(dirs),\ $(file >>gen_rules_pass0.mk,\ $(dir:%=%.$(opaque_binary).d: $(shell ls $(dir)/*.x)); \ rm -f $(opaque_binary); touch $$@)) deps := $(dirs:%=%.$(opaque_binary).d) .PHONY: all clean test ifeq ($(MAKELEVEL),1) all: $(outs) include gen_rules_pass1.mk else all : $(deps) $(MAKE) include gen_rules_pass0.mk endif $(opaque_binary): copy.c $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS) clean: rm -f $(deps) $(ins) $(outs) $(opaque_binary) gen_rules_pass*.mk test: cat $(outs) 版本的费用 他们没有要求也没有需要。令人激动的是,无偿的指控是他们的整个账单。

Fundamental theorem of software engineering, 你可以通过引入额外的间接水平来弥补这种浪费和不公正, 如:

Makefile(拍2)

gen_rules_pass0.mk

这里我们需要一个两遍制作,所以我们生成$(MAKELEVEL)以包含 在gen_rules_pass1.mk == 1时包含$(MAKELEVEL) == 0和 a/foo.out: a/file.x b/foo.out: b/file.x c/foo.out: c/file.x a/foo.out: a.copy.d; $(MAKE) $@ b/foo.out: b.copy.d; $(MAKE) $@ c/foo.out: c.copy.d; $(MAKE) $@ a.copy.d: a/file.x; rm -f copy; touch $@ b.copy.d: b/file.x; rm -f copy; touch $@ c.copy.d: c/file.x; rm -f copy; touch $@ 时。

<强> gen_rules_pass0.mk

T

在此您可以看到,对于每个目标词干T.$(opaque_binary).d,我们都引入了依赖项文件 T/foo.out: T/file.x 提供 - $(opaque_binary) - 目标,我们可以在其上挂起决定权 是否应重建T/foo.out。如果T/file.x T/foo.out: T.copy.d; $(MAKE) $@ 过时 它会触发配方:

T/foo.out

将在第二轮中重新制作T.copy.d: T/file.x; rm -f copy; touch $@ 。它还需要:

T.copy.d

首先进行评估,T/file.x同样过时$(opaque_binary), 然后会在传递和T.copy.d&#39;中删除T.$(opaque_binary).d 时间戳已更新。对于默认目标,此传递仅重新创建所有 T/foo.out然后调用第二遍。否则 启动指定 a/foo.out: a/file.x | copy; ./copy $< > $@ b/foo.out: b/file.x | copy; ./copy $< > $@ c/foo.out: c/file.x | copy; ./copy $< > $@ 目标的规则。

<强> gen_rules_pass1.mk

gen_rules.mk

与旧的 T/foo.out: T/file.x | copy; ./copy $< > $@ 相比,它的含义相同:

copy: T/file.x

并且没有:

$(opaque_binary)

我们不再需要让*.x依赖于 自第一遍以来make个文件已保留或 完全按照我们的目标要求删除它。但它仍然是一个 这些目标中只有订单的先决条件,现在就是这样 如果需要的话,可以重新制作,因为如果它需要,它就会消失。

使用这个makefile,$ make rm -f copy; touch a.copy.d rm -f copy; touch b.copy.d rm -f copy; touch c.copy.d make make[1]: Entering directory '/home/imk/develop/scrap' cc -o copy copy.c ./copy a/file.x > a/foo.out ./copy b/file.x > b/foo.out ./copy c/file.x > c/foo.out make[1]: Leaving directory '/home/imk/develop/scrap' 从头开始看起来像:

*.x

更新了一些$ touch a/file.x b/file.x $ make rm -f copy; touch a.copy.d rm -f copy; touch b.copy.d make make[1]: Entering directory '/home/imk/develop/scrap' cc -o copy copy.c ./copy a/file.x > a/foo.out ./copy b/file.x > b/foo.out make[1]: Leaving directory '/home/imk/develop/scrap' 个文件后:

$ make
make
make[1]: Entering directory '/home/imk/develop/scrap'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/home/imk/develop/scrap'
$ touch a/file.x    # Agent A
$ make b/foo.out    # Agent B
make: 'b/foo.out' is up to date.    # :)

到目前为止,和以前一样。但是如果我们重播代理A,代理B. 情形:

make a/foo.out
rm -f copy; touch a.copy.d
make a/foo.out
make[1]: Entering directory '/home/imk/develop/scrap'
cc  -o copy copy.c  
./copy a/file.x > a/foo.out
make[1]: Leaving directory '/home/imk/develop/scrap'

make

比以前更有效,更公平,但通过更复杂,更不健全的方式。

您可能不喜欢使用*.d文件混乱的.d目录 而且,更实际的是,担心闲置的手容易 搞砸了。在那个帐户上通常是这样的依赖文件 在隐藏的.deps或{{1}}子目录中分泌。我已经省略了 为简单起见的预防措施。

即使我没有正确理解这个问题,这里的想法可能会帮助你找到解决方案。

(GNU Make 4.1)