如何使用GNU make使归档内容成为归档提取规则的真正目标?

时间:2015-05-06 01:41:39

标签: makefile gnu-make

我希望编写几个规则来提取tar档案的内容,以生成许多文件,然后将这些文件用作其他规则的输入依赖项。我希望即使使用并行构建也能工作。我没有使用递归制作。

首先,抱歉马拉松问题,但我不认为我可以用更短的形式解释它。

考虑解开源文件集合,然后使用存储在存档之外的规则对它们进行编译,以生成各种构建工件,然后进一步使用这些工件。我不是在寻求导致遗漏这个问题的其他安排。只是理所当然地认为我有充分的理由这样做。 :)

我将以一个人为的例子展示我的问题。当然,我从一些基本的东西开始:

TAR := test.tar.bz2

CONTENTS := $(addprefix out/,$(filter-out %/,$(shell tar -tf $(TAR))))

out: $(TAR)
        rm -rf out
        mkdir out
        tar -xvf $< -C out --touch || (rm -rf out; exit 1)

$(CONTENTS): out

sums: $(CONTENTS)
        md5sum $^ > $@

.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all clean
all: sums
clean:
        rm -rf out sums

这里的想法是,因为$(CONTENTS)是归档中的所有文件,并且它们都依赖out,然后运行sums目标,我们需要最终提取档案

不幸的是,如果您在上一次构建之后使用并行调用时仅更新test.tar.bz2,则不会(始终)工作,因为make可能决定检查$的时间戳( 运行out规则之前的内容,这意味着它认为每个来源都早于sums,因此无需执行任何操作:

$ make clean
rm -rf out sums

$ make -j6
rm -rf out
mkdir out
tar -xvf test.tar.bz2 -C out --touch || (rm -rf out; exit 1)
data.txt
file
weird.file.name
dir/
dir/another.c
dir/more
md5sum out/data.txt out/file out/weird.file.name out/dir/another.c out/dir/more > sums

$ touch test.tar.bz2 
$ make -j6
rm -rf out
mkdir out
tar -xvf test.tar.bz2 -C out --touch || (rm -rf out; exit 1)
data.txt
file
weird.file.name
dir/
dir/another.c
dir/more

糟糕! sums规则没有运行!

所以,接下来的尝试是告诉make一个解压规则实际上确实直接生成所有$(CONTENTS)。这似乎更好,因为我们正在告诉make什么真正发生,所以它知道什么时候忘记任何缓存的目标,当它们通过他们的规则重新制作时。

首先,让我们看看看起来有用的东西,然后我就会遇到问题:

TAR := test.tar.bz2

CONTENTS := $(addprefix out/,$(filter-out %/,$(shell tar -tf $(TAR))))

# Here's the change.
$(addprefix %/,$(patsubst out/%,%,$(CONTENTS))): $(TAR)
        rm -rf out
        mkdir out
        tar -xvf $< -C out --touch || (rm -rf out; exit 1)

sums: $(CONTENTS)
        md5sum $^ > $@

.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all clean
all: sums
clean:
        rm -rf out sums

在这种情况下,我们有效地制定了一条规则:

%/data.txt %/file %/weird.file.name %/dir/another.c %/dir/more: test.tar.bz2
        rm -rf out
        mkdir out
        tar -xvf $< -C out --touch || (rm -rf out; exit 1)

现在您可以看到我将输出强制转换为out目录的原因之一:为我提供一个使用%的位置,以便我可以使用模式规则。我被迫使用模式规则,即使这里没有强大的模式,因为它是唯一可以告诉make一条规则从单个调用创建多个输出文件的方法。 (不是吗?)

如果触摸了任何文件(对我的用例来说不重要)或触摸了test.tar.bz2文件,即使在并行构建中也是如此,因为make具有所需的信息:运行此配方会使所有这些文件将改变他们所有的时间戳。

例如,在上一次成功构建之后:

$ touch test.tar.bz2 
$ make -j6
rm -rf out
mkdir out
tar -xvf test.tar.bz2 -C out --touch || (rm -rf out; exit 1)
data.txt
file
weird.file.name
dir/
dir/another.c
dir/more
md5sum out/data.txt out/file out/weird.file.name out/dir/another.c out/dir/more > sums

所以,如果我有一个有效的解决方案,那我的问题是什么?

好吧,我提取了很多这些档案,每个档案都有自己的$(CONTENTS)。我可以管理它,但问题在于编写一个很好的模式规则。由于每个归档都需要定义自己的规则,因此即使归档具有相似(或相同)的内容,每个规则的模式也不能重叠。这意味着所提取文件的输出路径必须为每个存档唯一,如:

TAR := test.tar.bz2

CONTENTS := $(addprefix out.$(TAR)/,$(filter-out %/,$(shell tar -tf $(TAR))))

$(patsubst out.$(TAR)/%,out.\%/%,$(CONTENTS)): $(TAR)
        rm -rf out.$(TAR)
        mkdir out.$(TAR)
        tar -xvf $< -C out.$(TAR) --touch || (rm -rf out.$(TAR); exit 1)

sums: $(CONTENTS)
        md5sum $^ > $@

.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all clean
all: sums
clean:
        rm -rf out.$(TAR) sums

因此,这可以使用正确的目标特定变量,但现在意味着提取点全部都是丑陋的&#34;以与构造makefile的方式非常相关的方式:

$ make -j6
rm -rf out.test.tar.bz2
mkdir out.test.tar.bz2
tar -xvf test.tar.bz2 -C out.test.tar.bz2 --touch || (rm -rf out.test.tar.bz2; exit 1)
data.txt
file
weird.file.name
dir/
dir/another.c
dir/more
md5sum out.test.tar.bz2/data.txt out.test.tar.bz2/file out.test.tar.bz2/weird.file.name out.test.tar.bz2/dir/another.c out.test.tar.bz2/dir/more > sums

我采取的下一个自然步骤是尝试将静态模式规则与多目标 - 通过模式规则方法相结合。这样我就可以保持模式的一般性,但是将它们的应用限制在一组特定的目标中:

TAR := test.tar.bz2

CONTENTS := $(addprefix out/,$(filter-out %/,$(shell tar -tf $(TAR))))

# Same as second attempt, except "$(CONTENTS):" static pattern prefix
$(CONTENTS): $(addprefix %/,$(patsubst out/%,%,$(CONTENTS))): $(TAR)
        rm -rf out
        mkdir out
        tar -xvf $< -C out --touch || (rm -rf out; exit 1)

sums: $(CONTENTS)
        md5sum $^ > $@

.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all clean
all: sums
clean:
        rm -rf out sums

大!除了它没有工作:

$ make
Makefile:5: *** multiple target patterns.  Stop.
$ make --version
GNU Make 4.0

那么,有没有办法将多个目标模式与静态模式规则一起使用?如果没有,是否有另一种方法可以实现上一个上一个工作示例中的内容,但是没有输出路径上的约束来制作独特的模式?我基本上需要告诉make&#34;当您解压缩此存档时,此目录中的所有文件(我愿意在必要时枚举)都有新的时间戳&#34;。一个解决方案,我可以强制make重新启动,当且仅当它解压缩存档时也是可以接受的,但不太理想。

1 个答案:

答案 0 :(得分:1)

原始makefile的问题在于名称中存在冲突。您有一个名为out的目标(非虚假)和一个名为out的目录。 make认为那些是同一件事而且非常困惑。

(注意:我在你的第一个makefile中加了.SUFFIXES:来减少一些噪音,但它没有改变任何东西。-r-R标志禁用make内置规则和变量也用于降噪。)

$ make clean
....
$ make -j6
....
$ touch test.tar.bz2
$ make -rRd -j6
....
Considering target file 'all'.
 File 'all' does not exist.
  Considering target file 'sums'.
    Considering target file 'out/data.txt'.
     Looking for an implicit rule for 'out/data.txt'.
     No implicit rule found for 'out/data.txt'.
      Considering target file 'out'.
        Considering target file 'test.tar.bz2'.
         Looking for an implicit rule for 'test.tar.bz2'.
         No implicit rule found for 'test.tar.bz2'.
         Finished prerequisites of target file 'test.tar.bz2'.
        No need to remake target 'test.tar.bz2'.
       Finished prerequisites of target file 'out'.
       Prerequisite 'test.tar.bz2' is older than target 'out'.
      No need to remake target 'out'.
     Finished prerequisites of target file 'out/data.txt'.
     Prerequisite 'out' is older than target 'out/data.txt'.
    No recipe for 'out/data.txt' and no prerequisites actually changed.
    No need to remake target 'out/data.txt'.
.... # This following set of lines repeats for all the other files in the tarball.
    Considering target file 'out/file'.
     Looking for an implicit rule for 'out/file'.
     No implicit rule found for 'out/file'.
      Pruning file 'out'.
     Finished prerequisites of target file 'out/file'.
     Prerequisite 'out' is older than target 'out/file'.
    No recipe for 'out/file' and no prerequisites actually changed.
    No need to remake target 'out/file'.
....
   Finished prerequisites of target file 'sums'.
   Prerequisite 'out/data.txt' is older than target 'sums'.
   Prerequisite 'out/file' is older than target 'sums'.
   Prerequisite 'out/weird.file.name' is older than target 'sums'.
   Prerequisite 'out/dir/more' is older than target 'sums'.
   Prerequisite 'out/dir/another.c' is older than target 'sums'.
  No need to remake target 'sums'.
 Finished prerequisites of target file 'all'.
Must remake target 'all'.
Successfully remade target file 'all'.
make: Nothing to be done for 'all'.

这里的主要细节是这两行:

  1. Considering target file 'out'.
  2. Prerequisite 'out' is older than target 'out/data.txt'
  3. 此处out目录无关紧要。我们并不关心它(并且make无论如何都不能很好地处理目录先决条件,因为修改目录上的时间戳并不意味着它们与文件相同)。更不用说你不希望out/data.txt没有被创建,因为构建工件目录目标已经存在(并且看起来更旧)。

    您可以通过将out目标标记为.PHONY来“修复”此问题,但是每次运行make时都会获取make以提取tarball(您已经运行tar -tf每次你运行make所以如果你要这样做的话,最好只结合这两个步骤。)

    那说我不会这样做。我认为这个问题最简单的解决方案是John Graham-Cunning构建的“原子规则”思想并解释here

    sp :=
    sp +=
    
    sentinel = .sentinel.$(subst $(sp),_,$(subst /,_,$1))
    
    atomic = $(eval $1: $(call sentinel,$1) ; @:)$(call sentinel,$1): $2 ; touch $$@ $(foreach t,$1,$(if $(wildcard $t),,$(shell rm -f $(call sentinel,$1))))
    
    .PHONY: all
    
    all: a b
    
    $(call atomic,a b,c d)
    
       touch a b
    

    您也可以使用提取戳记文件(tarball上的prereq),将tarball解压缩到“shadow”目录并复制/链接到“final”位置(build/$file: shadow/$file target)我想,但是我觉得这会有点复杂。