首页 > 解决方案 > 带有变量的 Makefile 目标未按预期工作

问题描述

如果文件包含的文件尚未解压缩,我想解压文件。如果我指定没有变量 % 的目标,那么它可以工作。但是,我不确定如何使用 %. 还依赖于文件及其对应的以 .tgz 结尾的 tar 压缩文件。这是我到目前为止所拥有的。我可以做,make T2但做不到。有人可以帮我理解我在这里做错了什么。谢谢。make T1make T3

TAR_FILE1 := ../a_files.tgz 
EXTRACT_DIR1 := ../ 
EXTRACT_LIST1 :=        \

TAR_FILE2 := ../b_files.tgz 
EXTRACT_DIR2 := ../ 
EXTRACT_LIST2 :=        \

F := ../b_files/b.v
TF := ../b_files.tgz 
DIR := ../ 
ET := 

IP_EXTRACT: $(RTL_FILES%) 

$(RTL_FILES%): $(IP_TAR_FILE%)   
    @echo -e "\nTarget $@ with $*:"
    gtar zxvfmC $(IP_TAR_FILE%) $(IP_EXTRACT_DIR%) $(IP_EXTRACT_LIST%)

$(F): $(TF)   
    @echo -e "\nTarget $@ with $*:"
    gtar zxvfmC $(TF) $(DIR) $(ET)

$(FILES%): $(TAR_FILE%)   
    @echo -e "\nTarget $@ with $*:"
    gtar zxvfmC $(TAR_FILE%) $(EXTRACT_DIR%) $(EXTRACT_LIST%)

FILES1 := \
../a_files/a.v  \
../a_files/a1.v \
../a_files/a2.v

FILES2 := ../b_files/b.v ../b_files/b1.v ../b_files/b2.v

print-%  : ; @echo $* = $($*)


T1: $(FILES%)
T2: $(F)
T3: $(FILES1)

clean:
    rm -rf ../a_files ../b_files

标签: variablesmakefiletarget

解决方案


%make 通配符显然不是你想的那样。在您的 Makefile 中,它几乎无处不在,是一个常规字符,是 make 变量名的一部分。例如,当你写:

$(RTL_FILES%): $(IP_TAR_FILE%)

之间的字符串()是变量名。而且由于您可能没有定义此类变量,因此它们会扩展为空字符串。

唯一的例外是print-%规则,它被正确地用于形成模式规则。出于同样的原因,您对$*自动变量的所有使用都可能是错误的:它总是扩展为空字符串。此外,您的问题不完整,因为您没有解释变量RTL_FILES, IP_TAR_FILE, FILES, TAR_FILE... 是什么。

无论如何,如果您想“如果文件包含的文件尚未解压缩,则解压缩文件”,有几种可能性。以下假设您使用 GNU make 并且您的../a_files.tgztarball 扩展为:

a_files
├── a
└── b
    └── b

它还假设您没有包含空格、制表符、特殊字符的文件或目录名称......

一个简单的解决方案

../a_files.tgz让我们从一个简单的解决方案开始,当且仅当../a_files目录不存在时您才想解压。否则,您认为 tarball 已被提取。

.PHONY: all

all: | ../a_files

../a_files:
    tar -C .. -xvf ../a_files.tgz

请注意目标的仅订单先决条件all|. 它告诉 make 只有先决条件的存在才重要,而不是它的最后修改时间。在大多数情况下,当先决条件是目录时需要它。

示范:

$ make
tar -C .. -xvf ../a_files.tgz
a_files/
a_files/b/
a_files/b/b
a_files/a
$ make
make: Nothing to be done for 'all'.

更复杂(但更强大)的解决方案

但是,如果您不想将存在a_files视为等同于“所有文件已被提取”怎么办?例如,如果您希望 Makefile 能够防止意外删除某些文件怎么办?

只要目录存在,上面的“简单解决方案../a_files”就不会再次解压,即使它的完整内容已被删除:

$ rm -rf ../a_files/*
$ make
make: Nothing to be done for 'all'.

因此,一个不错的解决方案是考虑提取文件的完整列表,而不仅仅是它们的父目录。如果缺少任何一个,您将再次解压缩,否则不会。但事情有点困难,因为我们需要:

  1. 获取文件列表。您可以将列表硬连接到 Makefile 中,但这不是很方便。这个列表也可以通过查看 tarball ( tar -tf <tarball>) 来自动计算,这要归功于 GNU makeshell函数:

    LIST    := $(shell tar -tf ../a_files.tgz)
    
  2. 告诉 make 所有这些文件都是通过一次执行tar配方创建的。这并不容易,因为自然的制作风格是配方的一次执行会创建一个文件。如果我们只是声明 tarball 是所有提取文件的先决条件:

    FILES   := $(addprefix ../,$(LIST))
    
    $(FILES): ../a_files.tgz
        tar -C .. -xvf $<
    

    make 可以决定运行与tar要提取的文件一样多的配方。这不是我们想要的。幸运的是,具有多个目标的模式规则是一个例外,我们可以利用这一点。%棘手的部分是我们必须在所有单词中至少替换一个常见字符$(FILES)。让我们替换sof ../a_files/XXX

    LIST    := $(patsubst a_files/%,%,$(shell tar -tf ../a_files.tgz))
    FILES   := $(addprefix ../a_files/,$(LIST))
    PATTERN := $(addprefix ../a_file%/,$(LIST))
    

    现在,我们的 make 变量将取值:

    LIST    :=  b/ b/b a
    FILES   := ../a_files/b/ ../a_files/b/b ../a_files/a
    PATTERN := ../a_file%/b/ ../a_file%/b/b ../a_file%/a
    

    如果我们将$(PATTERN)其用作规则的目标,make 将考虑所有匹配的目标,对于任何给定的相同%替换,都是由关联配方的一次执行创建的。

  3. 告诉 make 不应使用文件的最后修改时间来决定它们对于 tarball 是否是最新的。我们已经在第一个解决方案中看到的仅订购先决条件是一种方法。

总而言之,以下应该做我们想要的:

.PHONY: all

LIST    := $(patsubst a_files/%,%,$(shell tar -tf ../a_files.tgz))
FILES   := $(addprefix ../a_files/,$(LIST))
PATTERN := $(addprefix ../a_file%/,$(LIST))

all: $(FILES)

$(PATTERN): | ../a_files.tgz
    tar -C .. -xvf ../a_files.tgz

示范:

$ make
tar -C .. -xvf ../a_files.tgz
a_files/
a_files/b/
a_files/b/b
a_files/a
$ make
make: Nothing to be done for 'all'.
$ rm ../a_files/a
$ make
tar -C .. -xvf ../a_files.tgz
a_files/
a_files/b/
a_files/b/b
a_files/a

推荐阅读