英文:
Is it expected behavior for Make to not evaluate a target's timestamp on the first invocation when the rule has no recipe?
问题
关于您提到的Makefile行为,第一个示例中的行为是Make工具的预期行为,而不是错误。Make工具使用时间戳(或者更准确地说是文件的最后修改时间)来确定目标是否需要重新构建。在第一次运行DB_TYPE=tidb make
时,db_type
文件的时间戳与run-only-if-env-var-updated
的时间戳相同,因此Make不会重新构建run-only-if-env-var-updated
,因为它认为它是最新的。
当您添加了额外的echo
命令到db_type
目标后,db_type
目标实际上具有了一个非空的配方,这导致了其最后修改时间被更新,因此在第一次运行DB_TYPE=tidb make
时,db_type
的时间戳更新,从而使run-only-if-env-var-updated
的时间戳旧于db_type
的时间戳,触发了run-only-if-env-var-updated
的重新构建。
这是Make工具的一种工作方式,根据目标和其依赖项的时间戳来判断是否需要重新构建。如果您希望确保run-only-if-env-var-updated
在第一次运行DB_TYPE=tidb make
时也运行,可以像您所示的那样将一个非空的命令添加到db_type
目标中,以确保其时间戳被更新。
至于为什么Make的这种行为是预期行为,Make工具的设计原则和行为在GNU Make的官方文档中有详细描述,您可以在GNU Make手册的相关部分中找到更多信息。 Make的行为是基于时间戳和目标依赖关系的标准构建系统的工作方式。
英文:
The Makefile documentation on force targets states the following:
> If a rule has no prerequisites or recipe, and the target of the rule
> is a nonexistent file, then make imagines this target to have been
> updated whenever its rule is run. This implies that all targets
> depending on this one will always have their recipe run.
My interpretation of the above claim is that the target of the rule must be a non-existent file for a target to be a Force target. Thus, I do not think the force target is what is happening in the example below, but I'm happy to learn of evidence to the contrary.
Consider the following Makefile:
.PHONY: update-db-type
.DEFAULT: run-only-if-env-var-updated
ifeq ("$(shell echo $$DB_TYPE)","mysql")
DB_TYPE := mysql
else
DB_TYPE := tidb
endif
run-only-if-env-var-updated: db_type
echo "Do an expensive operation"
sleep 1
touch $@
# Note that db_type is a real file.
db_type: update-db-type
update-db-type:
echo "DB_TYPE: $(DB_TYPE)"
ifneq ("$(shell cat db_type)","$(DB_TYPE)")
echo "$(DB_TYPE)" > db_type
endif
Now consider the following commands and their output:
echo mysql > db_type
DB_TYPE=mysql make
# Output
# echo "DB_TYPE: mysql"
# DB_TYPE: mysql
# echo "Do an expensive operation"
# Do an expensive operation
# sleep 1
# touch run-only-if-env-var-updated
DB_TYPE=tidb make
# Output
# echo "DB_TYPE: tidb"
# DB_TYPE: tidb
# echo "tidb" > db_type
DB_TYPE=tidb make
# echo "DB_TYPE: tidb"
# DB_TYPE: tidb
# echo "Do an expensive operation"
# Do an expensive operation
# sleep 1
# touch run-only-if-env-var-updated
Observe that the expensive operation run-only-if-env-var-updated
is only run on the second invocation of DB_TYPE=tidb make
.
This implies that, in the first invocation, Make did not observe that the timestamp of the file db_type
was updated, and newer than the file run-only-if-env-var-updated
.
Note that this is NOT the behavior I was expecting; my mental model was that Make would always compare the timestamp of the dependency to timestamp of the target to determine whether the target needed to run.
Now, if we make a small modification to the Makefile, by giving the target db_type
a non-empty recipe, as follows, then we get the expected behavior of running the expensive operation on the first invocation of DB_TYPE=tidb make
.
db_type: update-db-type
echo "Running DB_TYPE"
Is the behavior in the first example (without the extra echo
) expected behavior for Make?
If so, is there documentation that explains why the updated timestamp is being ignored on the first execution?
答案1
得分: 2
GNU make 从不考虑除了它所调用的目标之外的其他目标是否更新了目标文件。因此,如果它发现给定目标没有相关的构建规则,那么它会假定该目标没有被更新。
只有当有一个给定目标的构建规则时,make 才会重新检查目标的修改时间,以查看它是否已更改。构建规则实际上不必执行任何操作,只要它存在即可。例如,这就足够了:
db_type: update-db-type ;
这里的分号添加了一个空的构建规则。
在我看来,你的 makefile 相当令人困惑,我肯定不会这样编写它。首先,为什么要使用 $(shell echo $$DB_TYPE)
?为什么不直接使用 $(DB_TYPE)
?其次,在构建规则中使用 ifeq
等语句几乎总是错误的(至少非常令人困惑)。ifeq
等语句类似于预处理器语句,它们在解析 makefile 时会直接展开,而不是在执行构建规则时展开。
在我看来,下面的 makefile 可以实现你想要的功能,而且更加安全:
.PHONY: update-db-type
.DEFAULT: run-only-if-env-var-updated
ifneq ($(DB_TYPE),mysql)
DB_TYPE := tidb
endif
run-only-if-env-var-updated: db_type
echo "Do an expensive operation"
sleep 1
touch $@
# 请注意,db_type 是一个真正的文件。
db_type: update-db-type ;
update-db-type:
echo "DB_TYPE: $(DB_TYPE)"
[ "$$(cat db_type 2>/dev/null)" = '$(DB_TYPE)' ] \
|| echo "$(DB_TYPE)" > db_type
不过,更加直接和易于理解的方法是将构建规则放在你希望更新的目标中,而不是放在其他目标中,并强制始终调用该构建规则。因此,可以像这样:
db_type: FORCE
echo "DB_TYPE: $(DB_TYPE)"
[ "$$(cat $@ 2>/dev/null)" = '$(DB_TYPE)' ] \
|| echo "$(DB_TYPE)" > $@
FORCE:
这就是更简单、更容易理解的方法。
英文:
GNU make it doesn't ever consider that some other target, other than the one it invoked, updates the target file. So, if it sees that there is no recipe for a given target then it assumes that the target cannot have been updated.
Only if there is a recipe for a given target, will make re-check the modification time on the target to see if it has changed or not. The recipe doesn't have to actually do anything, it just has to exist. For example, this is sufficient:
db_type: update-db-type ;
The semicolon here adds an empty recipe to this rule.
IMO your makefile is pretty confusing and I would definitely not write it like this. First, why do you use $(shell echo $$DB_TYPE)
? Why not just use $(DB_TYPE)
? Second, it's virtually always wrong (and at least very confusing) to use ifeq
etc. inside a recipe. ifeq
etc. are preprocessor-like statements and they are expanded directly when the makefile is parsed, not when the recipe is invoked.
IMO this makefile does what you want and is safer:
.PHONY: update-db-type
.DEFAULT: run-only-if-env-var-updated
ifneq ($(DB_TYPE),mysql)
DB_TYPE := tidb
endif
run-only-if-env-var-updated: db_type
echo "Do an expensive operation"
sleep 1
touch $@
# Note that db_type is a real file.
db_type: update-db-type ;
update-db-type:
echo "DB_TYPE: $(DB_TYPE)"
[ "$$(cat db_type 2>/dev/null)" = '$(DB_TYPE)' ] \
|| echo "$(DB_TYPE)" > db_type
But, a more straightforward method that is IMO more understandable is to put the recipe in the target you want to be updated, rather than some other target, and force that recipe to always be invoked. So it would be this instead:
db_type: FORCE
echo "DB_TYPE: $(DB_TYPE)"
[ "$$(cat $@ 2>/dev/null)" = '$(DB_TYPE)' ] \
|| echo "$(DB_TYPE)" > $@
FORCE:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论