如何解决make对表达式的评估不同于shell
我正在尝试检查地址A上存在的文件副本是否比地址B上存在的原始文件更早。因此,例如,考虑一个名为scratch
的文件与该文件存在于同一文件夹中。包含makefile
的一个。让这些文件的地址为address_a/
。现在,让另一个地址,例如address_b/bin
,我要在其中安装 scratch
。但是,我打算在2种情况下执行安装:
-
address_b/bin
不包含scratch
- 或者只要
scratch
中存在的address_b/bin
副本早于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做什么而引起的。 ifeq
条件句在解析Makefile时由make评估,而稍后扩展目标特定的变量时,则改变条件条件的条件为时已晚...演示:
$ 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为条件条件)
下面的两个其他解决方案根本不是我的最爱,因为使用Shell而不是make来测试文件是否比另一个文件更旧会冒犯我的敏感性(并且我很敏感)。他们在这里仅供参考。
GNU make还有其他条件,例如$(if ...)
函数,您可以使用所需的方式,因为它们仅在make将特定配方变量扩展到make将配方传递给shell之前扩展。
注意:在$(if ...)
测试空度时,我修改了capture_status
的分配方式,使其值为1或空字符串而不是1或0。
注意:由于您的原始版本无效,我在测试中将$(destination)
替换为$(destination)/$(source)
,我认为:目录修改时间与文件修改时间不同。
.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.')
但是shell条件语句的工作原理几乎相同:
.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
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。