从URL列表中下载文件列表作为makefile中的先决条件

jos*_*444 6 makefile downloadfile

我想有一个用于编译项目的makefile,如果它们丢失,我需要下载一些文件(二进制文件),因为用git跟踪它们不是一个好主意.

我有一个URL列表和一个给定名称的列表(我希望能够选择名称来保存它们,因为我无法控制URL名称,而且大多数名称非常糟糕且不具描述性).假设文件将始终相同或者我们不关心它们是否在服务器中更改(我们不需要再次下载它们).

所以我的想法是在我的makefile中写这样的东西:

files = one two three four

main : $(files)
  command to do when files are present
Run Code Online (Sandbox Code Playgroud)

但我希望每个人都可以下载,如果它们不存在,并且每个都来自它自己的URL,所以我需要像$(文件)元素的迭代.

我会在脑海里写下我想在混合python代码和makefile中知道的想法.我知道这是垃圾,但我不知道其他任何方式(问题是如何写好,基本上),我认为这很容易理解.

urls = url1 url2 url3 url4

for i in len(files):
    $(files)[i] :
        curl -Lo $(files)[i] $(urls)[i]
Run Code Online (Sandbox Code Playgroud)

因此问题是:我该怎么做?

我一直在查看make文档一段时间,如果没有多次写入文件名,我找不到正确的方法(我认为应该避免使用变量,而应该使用变量).也许罐头食谱是方式,但我看不出如何.

Ren*_*let 4

有几种方法可以做你想做的事。警告:它们有点棘手,因为它们使用了 GNU make 的高级功能:

解决方案#1:

files := one two three four
urls := url1 url2 url3 url4

main: $(files)
    @echo 'command to do when files are present'

# $(1): file name
# $(2): url
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)))\
)
Run Code Online (Sandbox Code Playgroud)

为了更容易测试,我用 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
Run Code Online (Sandbox Code Playgroud)

说明:

DOWNLOAD_rule多行变量是下载操作的模板规则,其中代表$(1)文件名,$(2)代表对应的URL。

$(1)您可以执行和$(2)in的替换DOWNLOAD_rule,并将结果实例化为 make 语法:

$(eval $(call DOWNLOAD_rule,one,url1))
Run Code Online (Sandbox Code Playgroud)

这和你写的一样:

one:
    @echo 'curl -Lo one url1'
Run Code Online (Sandbox Code Playgroud)

foreach函数允许循环单词列表。所以:

$(foreach f,$(files),$(eval $(call DOWNLOAD_rule,$(f),url1)))
Run Code Online (Sandbox Code Playgroud)

将为所有文件实例化规则...除了url1所有文件的 URL 都是相同的 ( ) 之外。不是你想要的。

为了获取每个文件对应的 URL,我们可以eval在循环中对该函数进行两次不同的调用foreach

  • 第一个使用当前文件名和第一个 URL 实例化规则$(urls)
  • 第二个从 中删除第一个单词$(urls)并将结果重新分配给urls

注意:=作业,它们是必不可少的。默认的=(递归扩展)在这里不起作用。

$(wordlist 2,$(words $(urls)),$(urls))可能看起来很复杂,但事实并非如此:

  • $(wordlist s,e,l)扩展se列表中的单词数l(单词编号从 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:

也可以将第二个打包到变量evalDOWNLOAD_rule,但还有另一个方面需要考虑:配方在传递到 shell 之前由 make 扩展。url在第一次分析过程中扩展的特定于目标的变量 ( ) 解决了这个问题:

files := one two three four
urls := url1 url2 url3 url4

main: $(files)
    @echo 'command to do when files are present'

# $(1): file name
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))))
Run Code Online (Sandbox Code Playgroud)

请注意 的$$定义中的DOWNLOAD_rule,它们是必需的,因为eval扩展了它的参数,并且 make 在按照常规 make 语法分析结果时会第二次扩展它。这$$是一种保护我们的变量引用免受第一次扩展的方法,这样eval在 的四次迭代期间实例化的foreach是:

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))
Run Code Online (Sandbox Code Playgroud)

这将完全符合我们的要求。如果没有它,$$它将是:

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
Run Code Online (Sandbox Code Playgroud)

请记住这一点:使用时eval有两个扩展,并且$$经常需要转义第一个扩展。

解决方案#3:

我们还可以为每个文件声明一个变量 ( <file>-url) 来存储 URL,并声明一个宏来设置这些变量和文件列表 ( files):

# Set file's URL and update files' list
# Syntax: $(call set_url,<file>,<url>)
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'

# $(1): file name
define DOWNLOAD_rule
$(1):
    @echo 'curl -Lo $(1) $$($(1)-url)'
endef
$(foreach f,$(files),$(eval $(call DOWNLOAD_rule,$(f))))
Run Code Online (Sandbox Code Playgroud)

解决方案#4:

最后,我们可以使用John Graham-Cumming编写的出色的GNU Make 标准库( gmsl)及其关联数组来完成与解决方案 #3 中相同的操作。例如,我们可以定义一个关联数组,以文件名作为键,以 URL 作为值:urls

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'

# $(1): file name
define DOWNLOAD_rule
$(1):
    @echo 'curl -Lo $(1) $$(call get,urls,$(1))'
endef
$(foreach f,$(files),$(eval $(call DOWNLOAD_rule,$(f))))
Run Code Online (Sandbox Code Playgroud)