>我正在尝试检查地址 A 处的文件副本是否比地址 B 处的原始文件旧。因此,例如,考虑一个名为scratch
的文件,该文件与保存makefile
的文件位于同一文件夹中。让这些文件的地址address_a/
。现在让我们有另一个地址,比如我想安装scratch
address_b/bin
。但是,我打算在两种情况下执行安装:
address_b/bin
不包含scratch
- 或者每当
address_b/bin
中存在的scratch
的副本比address_a/
中存在的副本旧时。
为了做到这一点,我正在使用以下shell命令:
[ address_b/bin/scratch -ot address_a/scratch ] && echo 1 || echo 0
现在直接在终端上执行此命令将导致 1 作为输出。但是,通过makefile
执行此命令会导致 0 作为输出。我使用此命令的配方如下所示:
.PHONY : install
INSTALL = install
source = scratch
destination = address_b/bin/
install : capture_status != [ $(destination) -ot $(source) ] && echo 1 || echo 0
install :
@echo $(capture_status)
ifeq ($(capture_status), 1)
$(INSTALL) $(source) $(destination)
else
@echo 'Installation has already been done.'
endif
那么,问题究竟在哪里?
注意:我在 Ubuntu 18.04.3 上使用 GNU Make 4.1
您是否知道make的主要作用是比较目标文件和先决条件文件的上次修改时间,以确定是否必须重新制作目标文件?在您的情况下,我认为以下内容以更自然的方式完全符合您的要求:
.PHONY : install
INSTALL = install
source = scratch
destination = address_b/bin/
install: $(destination)/$(source)
$(destination)/$(source): $(source)
$(INSTALL) $< $(dir $@)
总之,它说要做:
install
是$(destination)/$(source)
的别名,而不是真实的文件。$(destination)/$(source)
取决于$(source)
,也就是说,如果$(destination)/$(source)
早于$(source)
则必须重新制作,否则它是最新的。如果必须重新制作
$(destination)/$(source)
(因为它早于$(source)
),要执行的 shell 命令是:$(INSTALL) $< $(dir $@)
$<
是一个 make 自动变量,它作为规则的第一个先决条件进行扩展(在本例中$(source)
),$@
作为目标扩展 ($(destination)/$(source)
),$(dir ...)
是一个返回父目录的 make 函数。因此,该命令等效于:
$(INSTALL) $(source) $(destination)
但更通用。
编辑 1(安装未完成时添加自定义消息)
如果您希望在$(destination)/$(source)
是最新的时发送特定消息或操作,则可以使用空文件将信息从一个规则传递到另一个规则(例如.done
):
.PHONY : install
INSTALL = install
source = scratch
destination = address_b/bin/
DONE := .done
MESSAGE := custom message
install: $(destination)/$(source)
@[ -f $(DONE) ] && rm $(DONE) || echo "$(MESSAGE)"
$(destination)/$(source): $(source)
$(INSTALL) $< $(dir $@)
@touch $(DONE)
编辑 2(添加对观察到的行为的解释)
您观察到的问题是由于何时使做什么。当 make 解析 Makefile 时,由 make 评估ifeq
条件,而稍后扩展特定于目标的变量,当更改条件的条件为时已晚时......示范:
$ cat Makefile
.PHONY: c
c: foo := bar
c:
ifeq ($(foo),bar)
@echo "foo = bar and foo = $(foo)"
else
@echo "foo != bar and foo = $(foo)"
endif
$ make c
foo != bar and foo = bar
编辑 3(制作if
条件和外壳条件)
下面的另外两个解决方案根本不是我最喜欢的,因为使用 shell 而不是 make 来测试一个文件是否比另一个文件旧会冒犯我的敏感性(而且我很敏感)。他们在这里只是为了提供信息。
GNU make 还有其他条件,比如$(if ...)
函数,你可以按照你想要的方式使用它们,因为它们只有在 make 将配方传递给 shell 之前,在特定于目标的变量被扩展之后才会被扩展。
注意:在测试空性$(if ...)
时,我修改了分配capture_status
的方式,使其值为 1 或空字符串,而不是 1 或 0。
注意:我用$(destination)/$(source)
替换了测试中的$(destination)
,因为您的原始版本不起作用,我认为:目录修改时间与文件修改时间不同。
.PHONY : install
INSTALL = install
source = scratch
destination = address_b/bin/
install : capture_status != [ $(destination)/$(source) -ot $(source) ] && echo 1
install :
@echo $(capture_status)
$(if $(capture_status),$(INSTALL) $(source) $(destination),@echo 'Installation has already been done.')
但是外壳条件的工作方式几乎相同:
.PHONY : install
INSTALL = install
source = scratch
destination = address_b/bin/
install : capture_status != [ $(destination)/$(source) -ot $(source) ] && echo 1 || echo 0
install :
@echo $(capture_status)
@if [ $(capture_status) -eq 1 ]; then
echo "$(INSTALL) $(source) $(destination)";
$(INSTALL) $(source) $(destination);
else
echo 'Installation has already been done.';
fi