如何在生产配方中处理极长的文件列表?

时间:2011-08-12 12:04:09

标签: makefile

因为GNU make允许变量与内存允许的一样大,所以构建大量依赖列表没有问题。但是,如果要在配方中实际使用这些文件列表(用于构建目标的shell命令序列),则会遇到问题:命令可能超出 shell的命令行长度限制,产生错误,例如“参数列表太长”。

例如,假设我想连接列表$(INPUTS)中包含的几个文件以生成文件combined.txt。通常,我可以使用:

combined.txt: $(INPUTS)
        cat $^ > $@

但如果$(INPUTS)包含数千个文件,就像我的情况一样,对cat的调用太长而且失败。有没有办法解决这个问题?可以安全地假设存在一些与一个庞大命令具有相同行为的命令序列 - 在这种情况下,一系列cat命令,每个输入文件一个,使用>>追加到combined.txt会有效。但如何说服make生成这些命令?

1 个答案:

答案 0 :(得分:10)

在寻找答案时,我能找到的最佳建议是to break up the list into a series of smaller lists and process them using shell for loops。但是你不能总是那样做,即使你可以这样做也是一个混乱的黑客:例如,一旦命令失败,如何获得通常的make停止行为并不明显。幸运的是,经过大量的搜索和实验,结果证明存在一般解决方案。

子shell和换行符

make食谱为食谱中的每一行调用一个单独的子shell。此行为可能令人烦恼且违反直觉:例如,一行上的cd命令不会影响后续命令,因为它们在单独的子shell中运行。然而,实际上我们需要让make对很长的文件列表执行操作。

通常,如果使用常规变量赋值构建“多行”文件列表,使用反斜杠将语句分成多行,make将删除所有换行符:

# The following two statements are equivalent
FILES := a b c

FILES := \
a \
b \
c

但是,使用define指令,可以构建包含换行符的变量值。更重要的是,如果将这样的变量替换为配方,每一行确实将使用单独的子shell运行,以便例如从make test使用下面的makefile运行/home/jbloggs(并假设没有调用文件) test存在)将产生输出/home/jbloggs,因为cd ..命令的效果在其子shell结束时会丢失:

define CMDS
cd ..
pwd
endef

test:
        $(CMDS)

如果我们使用define创建一个包含换行符的变量,它可以像往常一样与其他文本连接,并使用所有常用的make函数进行处理。这与$(foreach)函数相结合,可以让我们得到我们想要的东西:

# Just a single newline!  Note 2 blank lines are needed.
define NL


endef

combined.txt: $(INPUTS)
        rm $@
        $(foreach f,$(INPUTS),cat $(f) >> $@$(NL))

我们要求$(foreach)将每个文件名转换为换行符终止命令,该命令将在其自己的子shell中执行。对于更复杂的需求,您可以使用一系列echo命令将文件名列表写入文件,然后使用xargs

备注

  • define指令被描述为可选择在第一行的末尾加上=:=+=令牌,以确定variable flavour是哪个要创建 - 但请注意,这仅适用于GNU make 3.82及更高版本!您可能正在运行流行的版本3.81,就像我一样,如果您添加其中一个令牌,它会默默地为变量赋值,从而导致很多挫败感。有关详情,请参阅here
  • 所有食谱行必须以文字制表符开头,而不是我在此处使用的8个空格。