Make:如何在一次工具调用中处理多个输入文件?

Make: How to process many input files in one invocation of a tool?

我有一个由 GNU make 驱动的数据转换过程。它采用人工生成的输入文件并使用转换程序创建输出文件。显然,这与生成文件一样简单:

inputs=$(wildcard *.input)
outputs=$(subst .input,.output, $(inputs))

.PHONY: all
all: $(outputs)    

%.output: %.input
    converter $< -o $@ 

它变得更容易; converter 从输入文件知道输出文件的位置,所以我们不需要 $@:

%.output: %.input
    converter $<

到目前为止,还不错。问题是 converter 与实际处理一个文件的时间相比,启动需要很长时间。如果要处理的文件很多,就会浪费很多时间。我希望能够执行 converter 一次,传入 $(inputs) 的所有需要​​转换的成员 .

我目前的技术是使用 eval 填充所有需要转换的输入文件的列表,并在以后的规则中处理该列表:

.PHONY: all
all: single_convert

.PHONY: single_convert
single_convert: $(outputs)
    converter $(newer_inputs)

%.output: %.input
    $(eval newer_inputs+=$<)

尽管如此,这感觉就像我在对抗 make,而 makefile 通常感觉非常自然并且对我很有帮助。

我的问题是;有没有更好的办法?有没有我没有考虑过的边缘情况?我在做什么很危险吗?

您想通过 convert 中的单个 运行 汇集所有 .output 创作。实现此目的的一种方法是让每个 .output 文件依赖于一个简单创建的 .intermediate(比方说)文件。

.DELETE_ON_ERROR: # You always want this

inputs := $(wildcard *.input)
intermediates := ${inputs:.input=.intermediate}
outputs := ${inputs:.input=.output}

${outputs}: single_convert

single_convert: ${intermediates}
    convert ${?:.intermediate=.input}
    touch $@

${intermediates}: %.intermediate: %.input
    touch $@

${outputs}: single_convert

(参见手册中的 Empty Target Files to Record Events。)

这很好用。从零开始:

$ touch 1.input 2.input 3.input
$ make
touch 2.intermediate
touch 1.intermediate
touch 3.intermediate
convert 2.input 1.input 3.input
touch single_convert

递增。例如,当 12 过期时:

$ touch 1.input 2.input
$ make
touch 2.intermediate
touch 1.intermediate
convert 2.input 1.input
touch single_convert

虽然这有点乱七八糟。你在向 make 撒谎,这绝不是一个好主意(比如:你还没有告诉 make 如何构建 file.output 说) .此外,此公式排除了使用 -jn 标志进行并行操作的可能性,这是 make 恕我直言的全部要点。

这个 makefile 更简单:

${outputs}: %.output: %.input
    convert $<

.PHONY: all
all: ${outputs}

如果你有 8 个 CPU,性能会很好说:

$ make -j 9 all

如果我遇到这种情况,我会做如下事情:

all: convert.stamp

convert.stamp: $(inputs)
  rm convert.stamp
  x= ; \
    for f in $(inputs:.input=); do \
      test $$f.output -nt $$f.input || x="$$x $$f.input"; \
      done; \
    converter $$x
  touch convert.stamp

这与您所做的非常相似,只是获取过时文件列表的步骤是在 shell 操作中完成的,而不是使用花哨的 Make 功能。

有几个原因:

  1. 我认为您使用的 += 语法是特定于 GNU Make 的。从长期的习惯来看,我尽量保持 makefile 的可移植性,并尽可能避免 make 扩展(不过我对此并不虔诚:%.ext 语法不在 POSIX Make 中,但仍然很常见和有用,以至于不使用它是不正当的)。你已经暗示你很高兴有一个特定于 GNU Make 的解决方案,所以这个理由不会对你有太大的吸引力。

  2. 另外,我似乎更清楚以这种方式阐明逻辑:这意味着每个规则都更加独立,而您使用的 += 设备意味着这两个规则以一种不明显的方式相互作用。

这两点表明我对复杂动作的容忍度较高,对复杂的 Make 语法的容忍度较低——您的口味可能会有所不同。

关键点:您无法摆脱一些这种类型的并发症。 Make 基本上是一次更新一个文件。在(合理地)想要以这种方式折叠更新时,您不可避免地会颠覆 Make 的工作模型:我们不能期望这是自然的。也就是说,我不认为你或我错过了一个技巧。