如何编写保持两个文件同步的“双向”makefile?

问题描述 投票:0回答:2

我正在使用一组二进制文件,这些文件可以“反编译”为一组 INI 文件或从一组 INI 文件“编译”。由于二进制文件和 INI 文件都已签入我的存储库,因此我使用一个小脚本来(反)编译所有这些文件。

我们的工作流程通常涉及直接编辑二进制文件,并将修改后的二进制文件反编译为INI格式。但是,有时我们需要编辑 INI 文件并将更改编译为二进制文件。

问题:我可以制作一个单独的 makefile 来检测最近修改了哪一组文件,并自动向任一方向发出(反)编译命令以使两组文件保持同步吗?我更喜欢使用常见的(GNU?)make 功能,但如果有更专业的工具可以使用,我会洗耳恭听。

(我可以制作两个单独的指令,“全部反编译”和“全部编译”。我想知道是否有单命令选项。)

makefile synchronization
2个回答
1
投票

我不明白这是如何运作的。假设可以在 make 中完成;现在你有两个文件

foo.exe
foo.ini
(你没有说出你的实际文件名模式是什么)。您运行 make 并发现
foo.exe
foo.ini
新,因此它会反编译二进制文件以构建新的
foo.ini
。现在,您再次运行 make,这次它会看到
foo.ini
foo.exe
新,因为您刚刚构建了前者,所以它将
foo.ini
编译为
foo.exe

等等。每次运行 make 时,它都会对所有文件执行一项操作,因为其中一个文件总是会过期。

唯一可行的方法是,如果您(a)测试文件的上次修改时间是否不完全相同,并且(b)有办法重置已编译/反编译文件的时间,以便它是与构建它的文件相同,而不是“现在”,这当然是默认的。

答案是make不能用于这种情况。当然,您可以自己编写一个小 shell 脚本,遍历每个文件并测试最后修改时间是否相同,如果未编译它们,则使用

touch -m -r origin
,其中 origin 是具有较新修改时间的文件,以便两者具有相同的修改时间。


0
投票

我在 XML 上下文中遇到过这个用例。我希望在 xml 和紧凑语法中都有一个宽松的 ng 模式,并进行安排,以便编辑其中一个可以更新另一个。它在 make 中基本上是可行的,尽管可能不是一个明智的工具选择。

Make 允许您将多个规则与单个目标关联,并通过“双冒号”语法独立运行它们。 Make不太愿意接受循环依赖。它愿意从配方中写入全局变量,并且有点理解数组是什么。

因此,对于 foo.rng 和 foo.rnc (xml 大小写的后缀),创建一个目标 foo.whatever ,其目的是表示这两个输入文件的最新状态。我任意选择使用与 .rng 相同的格式,它可能是一些不相关的格式。理想情况下,将该文件放在隐藏目录中,然后将其删除干净等 - 它实际上只是为了在多个调用之间持久保存状态并从依赖关系图中隐藏循环而存在。

此外,我还选择了一种我喜欢的格式,从某种意义上说,当它们都过时时,我不会尝试选择较新的格式,尽管这可以更麻烦地实现。

大致结构就是这样。我稍后可能会重构它,如果我这样做的话,我会记得更新这篇文章。

# Only got one of the files, can build the other from it                                                                                                                                                           
$(DERIVED_SECONDARY):   %.$(SECONDARY_SUFFIX):  %.$(PRIMARY_SUFFIX)
        $(call secondary_from_primary,$@,$^)

$(DERIVED_PRIMARY):     %.$(PRIMARY_SUFFIX):    %.$(SECONDARY_SUFFIX)
        $(call primary_from_secondary,$@,$^)

# These only exist for source files that existed before the makefile invocation                                                                                                                                    
PRIMARY_NEWER := $(DERIVED_SOURCE)
$(DERIVED_SOURCE_GIVEN_PRIMARY):: %.$(DERIVED_SUFFIX):  %.$(PRIMARY_SUFFIX)
        $(eval index=$(call pos,$@,$(DERIVED_SOURCE)))
        $(eval PRIMARY_NEWER=$(call insert,$(index),$^,$(PRIMARY_NEWER)))

SECONDARY_NEWER := $(DERIVED_SOURCE)
$(DERIVED_SOURCE_GIVEN_SECONDARY):: %.$(DERIVED_SUFFIX):        %.$(SECONDARY_SUFFIX)
        $(eval index=$(call pos,$@,$(DERIVED_SOURCE)))
        $(eval SECONDARY_NEWER=$(call insert,$(index),$^,$(SECONDARY_NEWER)))

# See whether either dependency warrants rebuilding this target                                                                                                                                                                                                                                                                                                           
$(DERIVED_SOURCE):: %.$(DERIVED_SUFFIX) :
        $(eval index=$(call pos,$@,$(DERIVED_SOURCE)))

        $(eval primary=$*.$(PRIMARY_SUFFIX))
        $(eval secondary=$*.$(SECONDARY_SUFFIX))

#       Test whether either candidate dependency is newer than this target                                                                                                                                         
        $(eval from_primary=$(findstring $(primary), $(word $(index),$(PRIMARY_NEWER))))
        $(eval from_secondary=$(findstring $(secondary), $(word $(index),$(SECONDARY_NEWER))))

#       Not totally sure this is worth the bother relative to writing to $@ directly
        @$(eval tmp=$(shell mktemp -t [email protected]))

        $(if $(from_primary), \
                cp $(primary) $(tmp), \
                $(if $(from_secondary), $(call primary_from_secondary,$(tmp),$(secondary))))

#       If this was updated by either file, update the other from it                                                                                                                                               
        $(if $(from_primary), \
                $(call secondary_from_primary,$(secondary),$(tmp)), \
                $(if $(from_secondary), cp $(tmp) $(primary)))


#       Fix up the timestamp so current target still looks newer than those it just rebuilt                                                                                                                        
        $(if $(or $(from_primary), $(from_secondary)), touch $(tmp) && mv $(tmp) $@, @rm $(tmp))

前提是:

  • 使用双冒号规则来处理是否存在任何可能的源文件
  • 根据每个源文件记录目标是否需要重建
  • 在一个包罗万象的目标中分支,从其中构建派生文件
  • 更新未使用的源文件来更新派生文件
  • 当两者都发生变化时,始终丢弃名为 secondary 的那个

TODO 给自己的笔记:

  • 记住 makefile 是如何工作的,这对 eval 的依赖程度似乎值得怀疑
  • 仔细考虑丢失文件的依赖关系图,第一次构建可能总是达到修复点
  • 从时间戳更改为哈希值

这解决了我希望两种表现同时出现的直接问题,对于OP来说仅仅晚了五年。

© www.soinside.com 2019 - 2024. All rights reserved.