2

我正在尝试为具有不同优化级别等的不同软件目录进行编译。为此,我创建了以下 makefile:

OWNER           = betsy molly fred
DOG             = poodle mutt doberman
COLOUR          = brown red yellow
ATTR            = big small
LEGS            = 0 3

#we want every possible combination to be excercised
OUTPUT_STUFF = $(foreach own,$(OWNER),$(foreach dog,$(DOG),$(foreach col,$(COLOUR),$(foreach attr,$(ATTR),$(foreach legs,$(LEGS),new/$(own)/$(dog)/$(col)/$(attr)/$(legs)/dogInfo.txt)))))

.PHONY: all

all: $(OUTPUT_STUFF)

define PROGRAM_template

own             = $(1)
dog             = $(2)
col             = $(3)
attr            = $(4)
legs            = $(5)

BUILD_DIR           = new/$(own)/$(dog)/$(col)/$(attr)/$(legs)

#for each build directory, we are going to put a file in it containing the build dir. string
$$(BUILD_DIR)/dogInfo.txt:
    @echo "$$@"
    mkdir $$(BUILD_DIR)
    @echo "$$(BUILD_DIR)" > $$(BUILD_DIR)/dogInfo.txt
endef

#call the function many times
$(foreach own,$(OWNER),$(foreach dog,$(DOG),$(foreach col,$(COLOUR),$(foreach attr,$(ATTR),$(foreach legs,$(LEGS),$(eval $(call PROGRAM_template,$(own),$(dog),$(col),$(attr),$(legs))))))))

如您所见,这个简单的测试程序循环遍历所有者、狗等的不同组合。最终目标是创建一个新目录,其中包含所有所有者作为 dirs,其中包含所有狗等。底部是只是一个包含路径的文件。

当我运行它时,输出是:

new/betsy/poodle/brown/big/0/dogInfo.txt
mkdir new/fred/doberman/yellow/small/3
mkdir: cannot create directory `new/fred/doberman/yellow/small/3': No such file or directory
make: *** [new/betsy/poodle/brown/big/0/dogInfo.txt] Error 1

所以,出于某种原因,目标是好的,但看似完全相同的变量是我循环中的最后一个。从根本上说,我不明白发生了什么。

Makefiles 中奇怪的 foreach + 用户定义函数行为似乎可以回答,但我并不完全明白。在我看来,当调用该函数时,它会用一个 $ 填充所有实例,而转义的则变为 $(BUILD_DIR)。然后它将代码“粘贴”到临时文件,并在完成所有调用后评估文件,并正常替换变量。

我想到的一个(丑陋的)解决方案是让 BUILD_DIR 变量每次都不同,如下所示:

B_D_$(1)_$(2)_$(3)_$(4)_$(5) = ~~~
4

3 回答 3

7

亚历克斯是正确的(尽管我认为他的意思是食谱,而不是收据:-))。调试复杂 eval 问题的最佳方法是将 eval 函数替换为调用info。所以如果你有类似的东西:

$(foreach A,$(STUFF),$(eval $(call func,$A)))

那么您可以将其重写为:

$(foreach A,$(STUFF),$(info $(call func,$A)))

现在 make 将准确地打印出eval 将要解析的内容。通常很清楚,查看 makefile 输出,问题是什么。在您的情况下,您会在输出中看到类似这样的内容(省略所有额外的变量设置):

BUILD_DIR = new/betsy/poodle/brown/big/0
$(BUILD_DIR)/dogInfo.txt:
            @echo "$$@"
            mkdir $(BUILD_DIR)
            @echo "$(BUILD_DIR)" > $(BUILD_DIR)/dogInfo.txt

BUILD_DIR = new/betsy/poodle/brown/big/3
$(BUILD_DIR)/dogInfo.txt:
            @echo "$$@"
            mkdir $(BUILD_DIR)
            @echo "$(BUILD_DIR)" > $(BUILD_DIR)/dogInfo.txt

等等。注意你每次是如何设置全局变量的。BUILD_DIR在 make 中,变量只有一个值(一次)。当 make 正在读取 makefile 时,它​​会立即扩展目标和先决条件列表,因此当时的BUILD_DIR任何值都将用于目标/先决条件,因此这对您有用。

但是当 make 完成对 makefile 的读取时, 的值BUILD_DIR将始终是您设置它的最后一个值;在这种情况下new/fred/doberman/yellow/small/3。现在 make 开始为每个目标调用配方,当它这样做时,它将BUILD_DIR在配方中扩展,因此所有配方都将获得相同的值。

正如 Alex 指出的那样,您应该确保您的配方使用自动变量,例如$@,这些变量已为每个规则正确设置。如果你这样做,你会注意到你根本不需要重新定义规则,因为它实际上是所有目标的相同配方。如果您注意到这一点,您会注意到您首先不需要整个 eval 或调用复杂性。

您所要做的就是计算所有目标的名称,然后编写一条规则:

ALLDOGINFO = $(foreach own,$(OWNER),$(foreach dog,$(DOG),$(foreach col,$(COLOUR),$(foreach attr,$(ATTR),$(foreach legs,$(LEGS),new/$(own)/$(dog)/$(col)/$(attr)/$(legs)/dogInfo.txt)))))

$(ALLDOGINFO):
        @echo "$@"
        mkdir $(dir $@)
        @echo "$(dir $@)" > $@

如果您不想要尾随斜杠,则必须$(patsubst %/,%,$(dir $@))改用。

于 2013-07-25T19:24:01.260 回答
2

问题是当 $$(BUILD_DIR) 在接收中被评估时,循环已经完成。解决方案是重写收据:

$$(BUILD_DIR)/dogInfo.txt:
    @echo "$$@"
    mkdir $$(@D)
    @echo "$$(@D)" > $$@
于 2013-07-25T18:59:05.973 回答
0

我认为您的问题不一定与make. 这个命令:

mkdir new/fred/doberman/yellow/small/3

如果父目录之一(例如yellow)不存在,则会失败。在这种情况下它吐出的错误就是你得到的错误,所以看起来很可能是这种情况。如果您想要一个根据需要创建给定目录的所有父目录的命令,您应该运行mkdir -p,如下所示:

mkdir -p $$(BUILD_DIR)

mkdir有关功能的完整描述,请参见手册页-p

于 2013-07-26T00:28:06.127 回答