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
递增。例如,当 1
和 2
过期时:
$ touch 1.input 2.input
$ make
touch 2.intermediate
touch 1.intermediate
convert 2.input 1.input
touch single_convert
虽然这有点乱七八糟。你在向 make 撒谎,这绝不是一个好主意(比如:你还没有告诉 make 如何构建 file.output
说) .此外,此公式排除了使用 -j
n 标志进行并行操作的可能性,这是 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 功能。
有几个原因:
我认为您使用的 +=
语法是特定于 GNU Make 的。从长期的习惯来看,我尽量保持 makefile 的可移植性,并尽可能避免 make 扩展(不过我对此并不虔诚:%.ext
语法不在 POSIX Make 中,但仍然很常见和有用,以至于不使用它是不正当的)。你已经暗示你很高兴有一个特定于 GNU Make 的解决方案,所以这个理由不会对你有太大的吸引力。
另外,我似乎更清楚以这种方式阐明逻辑:这意味着每个规则都更加独立,而您使用的 +=
设备意味着这两个规则以一种不明显的方式相互作用。
这两点表明我对复杂动作的容忍度较高,对复杂的 Make 语法的容忍度较低——您的口味可能会有所不同。
关键点:您无法摆脱一些这种类型的并发症。 Make 基本上是一次更新一个文件。在(合理地)想要以这种方式折叠更新时,您不可避免地会颠覆 Make 的工作模型:我们不能期望这是自然的。也就是说,我不认为你或我错过了一个技巧。
我有一个由 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
递增。例如,当 1
和 2
过期时:
$ touch 1.input 2.input
$ make
touch 2.intermediate
touch 1.intermediate
convert 2.input 1.input
touch single_convert
虽然这有点乱七八糟。你在向 make 撒谎,这绝不是一个好主意(比如:你还没有告诉 make 如何构建 file.output
说) .此外,此公式排除了使用 -j
n 标志进行并行操作的可能性,这是 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 功能。
有几个原因:
我认为您使用的
+=
语法是特定于 GNU Make 的。从长期的习惯来看,我尽量保持 makefile 的可移植性,并尽可能避免 make 扩展(不过我对此并不虔诚:%.ext
语法不在 POSIX Make 中,但仍然很常见和有用,以至于不使用它是不正当的)。你已经暗示你很高兴有一个特定于 GNU Make 的解决方案,所以这个理由不会对你有太大的吸引力。另外,我似乎更清楚以这种方式阐明逻辑:这意味着每个规则都更加独立,而您使用的
+=
设备意味着这两个规则以一种不明显的方式相互作用。
这两点表明我对复杂动作的容忍度较高,对复杂的 Make 语法的容忍度较低——您的口味可能会有所不同。
关键点:您无法摆脱一些这种类型的并发症。 Make 基本上是一次更新一个文件。在(合理地)想要以这种方式折叠更新时,您不可避免地会颠覆 Make 的工作模型:我们不能期望这是自然的。也就是说,我不认为你或我错过了一个技巧。