我正在尝试编译具有不同优化级别的不同软件目录等.我创建了以下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))))))))
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,这个简单的测试程序循环通过所有者,狗等的不同组合.最终目标是有一个目录,new,所有所有者都是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
Run Code Online (Sandbox Code Playgroud)
因此,由于某种原因,目标是可以的,但看似完全相同的变量是我的循环中的最后一个.从根本上说,我不明白发生了什么.
Makefile中奇怪的foreach +用户定义的函数行为似乎回答了,但我并没有完全理解它.在我看来,当调用该函数时,它用一个$填充所有实例,并且转义的函数变为$(BUILD_DIR).然后它将代码"粘贴"到临时文件中,在完成所有调用后,它会评估文件,将变量替换为正常.
我想到的一个(丑陋)解决方案是每次都使BUILD_DIR变量不同:
B_D_$(1)_$(2)_$(3)_$(4)_$(5) = ~~~
Run Code Online (Sandbox Code Playgroud)
亚历克斯是正确的(虽然我认为他的意思是食谱,而不是收据 :-)).调试复杂eval问题的最佳方法是使用调用替换eval函数info.所以如果你有类似的东西:
$(foreach A,$(STUFF),$(eval $(call func,$A)))
Run Code Online (Sandbox Code Playgroud)
然后你可以重写为:
$(foreach A,$(STUFF),$(info $(call func,$A)))
Run Code Online (Sandbox Code Playgroud)
现在,让打印出你究竟是什么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
Run Code Online (Sandbox Code Playgroud)
请注意每次如何设置全局变量BUILD_DIR.在make中,变量只有一个值(一次).当make正在读取makefile时,它会立即扩展目标和先决条件列表,因此当时的任何值BUILD_DIR都将用于目标/先决条件,因此这适用于您.
但是当make完成读取makefile时,值BUILD_DIR将始终是你设置它的最后一件事; 在这种情况下new/fred/doberman/yellow/small/3.现在make开始调用每个目标的配方,当它执行时,它将BUILD_DIR在配方中扩展,因此所有配方将获得相同的值.
正如Alex指出的那样,您应该确保您的配方仅使用自动变量,例如$@为每个规则正确设置的变量.如果你这样做,你会注意到你根本不需要重新定义规则,因为它实际上是所有目标的相同配方.如果你注意到这一点,你会注意到你不需要整个评估或调用复杂性.
您所要做的就是计算所有目标的名称,然后编写一个规则:
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 $@)" > $@
Run Code Online (Sandbox Code Playgroud)
如果您不想使用尾部斜杠,则必须使用$(patsubst %/,%,$(dir $@)).