有几种方法可以实现您想要的功能。警告:它们有点棘手,因为它们使用 GNU make 的高级功能:
解决方案#1:
files := one two three four
urls := url1 url2 url3 url4
main: $(files)
@echo 'command to do when files are present'
define DOWNLOAD_rule
$(1):
@echo 'curl -Lo $(1) $(2)'
endef
$(foreach f,$(files),\
$(eval $(call DOWNLOAD_rule,$(f),$(firstword $(urls))))\
$(eval urls := $(wordlist 2,$(words $(urls)),$(urls)))\
)
为了更方便测试,我用echo替换了配方:
$ make -j
curl -Lo one url1
curl -Lo four url4
curl -Lo three url3
curl -Lo two url2
command to do when files are present
解释:
DOWNLOAD_rule
是一个多行变量,用于下载操作的模板规则,其中 $(1)
表示文件名,$(2)
表示相应的 URL。
您可以在 DOWNLOAD_rule
中进行 $(1)
和 $(2)
的替换,并将结果实例化为 make 语法:
$(eval $(call DOWNLOAD_rule,one,url1))
这与您编写以下内容相同:
one:
@echo 'curl -Lo one url1'
foreach
函数允许循环遍历单词列表。因此:
$(foreach f,$(files),$(eval $(call DOWNLOAD_rule,$(f),url1)))
将规则实例化到所有文件上...但是URL对于所有文件来说是相同的(
url1
),这不是你想要的。
为了获取与每个文件对应的URL,我们可以在
foreach
循环中放置两个不同的
eval
函数调用:
- 第一个调用使用当前文件名和
$(urls)
中的第一个URL来实例化规则,
- 第二个调用从
$(urls)
中删除第一个单词,并将结果重新分配给urls
。
请注意
:=
赋值,它们是必不可少的。默认的
=
(递归展开) 在这里不起作用。
$(wordlist 2,$(words $(urls)),$(urls))
可能看起来很复杂,但实际上并不是:
$(wordlist s,e,l)
会将列表l
中第s
到第e
个单词展开(单词从1到l
的长度进行编号)
$(words l)
会将列表l
中的单词数展开
因此,如果$(urls)
是url2 url3 url4
:
$(words $(urls))
会展开为3
$(wordlist 2,$(words $(urls)),$(urls))
会展开为url3 url4
,因为它等同于$(wordlist 2,3,url2 url3 url4)
解决方案#2:
也可以将第二个eval
打包在DOWNLOAD_rule
变量中,但还有另一个要考虑的方面:配方在传递给shell之前由make扩展。一个目标特定的变量(url
),在分析的第一次通过时扩展,解决了这个问题:
files := one two three four
urls := url1 url2 url3 url4
main: $(files)
@echo 'command to do when files are present'
define DOWNLOAD_rule
$(1): url := $$(firstword $$(urls))
$(1):
@echo 'curl -Lo $(1) $$(url)'
urls := $$(wordlist 2,$$(words $$(urls)),$$(urls))
endef
$(foreach f,$(files),$(eval $(call DOWNLOAD_rule,$(f))))
请注意
DOWNLOAD_rule
定义中的
$$
,它们是必需的,因为
eval
会扩展其参数,而make在将结果解析为常规make语法时会再次扩展。
$$
是一种保护变量引用免受第一次扩展影响的方式,这样在
foreach
的四次迭代期间由
eval
实例化的内容是:
one: url := $(firstword $(urls))
one:
@echo 'curl -Lo one $(url)'
urls := $(wordlist 2,$(words $(urls)),$(urls))
two: url := $(firstword $(urls))
two:
@echo 'curl -Lo two $(url)'
urls := $(wordlist 2,$(words $(urls)),$(urls))
three: url := $(firstword $(urls))
three:
@echo 'curl -Lo three $(url)'
urls := $(wordlist 2,$(words $(urls)),$(urls))
four: url := $(firstword $(urls))
four:
@echo 'curl -Lo four $(url)'
urls := $(wordlist 2,$(words $(urls)),$(urls))
这将完全符合我们的要求。如果没有$$
,它将是:
one: url := url1
one:
@echo 'curl -Lo one '
urls := url2 url3 url4
two: url := url1
two:
@echo 'curl -Lo two '
urls := url2 url3 url4
three: url := url1
three:
@echo 'curl -Lo three '
urls := url2 url3 url4
four: url := url1
four:
@echo 'curl -Lo four '
urls := url2 url3 url4
记住这一点:在使用
eval
时,有两个扩展符,
$$
经常需要用来转义第一个。
解决方案#3:
我们也可以声明每个文件(
<file>-url
)一个变量来存储URL,并设置这些变量和文件列表(
files
)的宏:
set_url = $(eval $(1)-url := $(2))$(eval files := $$(files) $(1))
$(call set_url,one,url1)
$(call set_url,two,url2)
$(call set_url,three,url3)
$(call set_url,four,url4)
main: $(files)
@echo 'command to do when files are present'
define DOWNLOAD_rule
$(1):
@echo 'curl -Lo $(1) $$($(1)-url)'
endef
$(foreach f,$(files),$(eval $(call DOWNLOAD_rule,$(f))))
解决方案 #4:
最后,我们可以使用出色的GNU Make Standard Library(gmsl
),由John Graham-Cumming和其关联数组提供的功能,执行与解决方案#3几乎相同的操作。例如,我们可以定义一个名为urls
的关联数组,以文件名作为键,URL作为值:
include gmsl
$(call set,urls,one,url1)
$(call set,urls,two,url2)
$(call set,urls,three,url3)
$(call set,urls,four,url4)
files := $(call keys,urls)
main: $(files)
@echo 'command to do when files are present'
define DOWNLOAD_rule
$(1):
@echo 'curl -Lo $(1) $$(call get,urls,$(1))'
endef
$(foreach f,$(files),$(eval $(call DOWNLOAD_rule,$(f))))