如何在 GNU Make 中以编程方式定义目标?

Posted

技术标签:

【中文标题】如何在 GNU Make 中以编程方式定义目标?【英文标题】:How to programmatically define targets in GNU Make? 【发布时间】:2015-03-02 16:44:52 【问题描述】:

我不知道在 GNU Make 中以编程方式定义目标的任何方法。这怎么可能?

有时可以离开with alternate methods。然而,在 Makefile 中以编程方式定义目标的能力对于使用 make 编写和组织复杂的生产规则非常重要。在 FreeBSD 的构建系统或 Makefile 库如BSD Owl中可以找到复杂生产规则的示例

shell 脚本和 Makefile 之间的main differences 是:

在 Makefile 中,程序的状态由命令行和文件系统给出,因此可以在作业中断后恢复作业。当然,这需要正确编写 Makefile,但即使这相当困难,也比使用 shell 脚本实现类似的效果要容易得多。

在 Makefile 中,用建议或钩子装饰过程非常容易,而这在 shell 脚本中基本上是不可能的。

例如,一个非常简单且有用的模式如下:

build: pre-build
build: do-build
build: post-build

这将build 目标呈现为三个目标的组合,一个包含实际指令do-build,另外两个是挂钩,在do-build 之前和之后执行。许多为 BSD Make 编写的构建系统都使用这种模式,顺便说一句,它允许对目标进行编程定义,因此可以批量编写:

.for _target in configure build test install
.if !target($_target)
$_target: pre-$_target
$_target: do-$_target
$_target: post-$_target
.endif
.endfor

.if/.endif 块引入的条件允许用户使用它自己对任何$_target 的定义。

那个 sn-p 对 GNU Make 的翻译是什么?

【问题讨论】:

应该注意的是,在 GNU 中,您不会在目标先决条件之间获得有序保证(尽管没有-j,我相信假设您通常是安全的做)所以你的“钩子”不一定做你想做的事。 @EtanReisner 当然,但这与问题没有直接关系,为了简单起见,我省略了这一点。例如,在 BSD Make 中,有人说 .ORDER: pre-build do-build post-build 强制对上述目标进行串行处理。 无论有没有-j,make 都会以相同的顺序遍历先决条件列表。只是使用-j,在开始新的先决条件之前,make 不会等待以前的(非依赖的)先决条件完成(当然,由于先决条件未完成,这可能会导致某些作业以不同的顺序运行)。如果没有-j,则 确实 保证按顺序构建。 【参考方案1】:

这里的 FWIW 是 make 的等效语法

.for _target in configure build test install
.if !target($_target)
$_target: pre-$_target
$_target: do-$_target
$_target: post-$_target
.endif
.endfor

基本上,您希望 make 看到类似 sn-p 的内容:

build: pre-build
build: do-build
build: post-build

configuretestinstall 也是如此。这表明在某处带有eval 的循环:

define makerule =
  $1: pre-$1
  $1: do-$1
  $1: post-$1
endef

targets := configure build test install

$(foreach _,$targets,$(eval $(call makerule,$_)))

(要玩这个,请将eval 更改为info)。小心那些关闭!

FWIW,这是foreach 的扩展:

make 扩展要迭代的列表 $targets 变为 configurebuildtestinstall 我们有$(foreach _,configure build test install,$(eval $(call makerule,$_))) _ 设置为第一个值 configuremake 扩展 $(eval $(call makerule,configure)) 为了评估evalmake 扩展$(call makerule,configure) 它通过将1 设置为configure 并扩展$makerule 来生成3 行文本:configure: pre-configureconfigure: do-configureconfigure: post-configure $(eval) 开始工作,以 make 语法阅读此文本 注意$(eval) 的扩展是空的!它的所有工作都是作为副作用完成的。 洗涤、起泡、冲洗、重复。

请注意:我必须同意所有其他评论者的观点:您的模式是糟糕。如果您的 makefile 不是 -j 安全的,那么它是损坏的(缺少依赖项)。

【讨论】:

感谢您的有用回答!正如我在其他一些 cmets 中所说,我很清楚并行性问题,并在我的问题中省略了它们,因为我只对编程方面感兴趣。但是初学者阅读您的并行横幅很好! :D【参考方案2】:

首先,如果您想支持并行构建,则此结构无效;如果您使用-j 选项调用make,它将同时运行所有三个先决条件规则,因为虽然它们都必须在build 之前完成,但它们都不相互依赖,因此没有定义排序(即, 你不是说pre-build 必须在do-build 运行之前完成)。

其次,GNU make 为programmatically defining rules 提供了许多工具。 GNU make 目前没有的一件事是能够搜索已定义的目标,因此与 .if !target(...) 没有直接的类比。

但是,您可以使用.VARIABLES 变量来搜索变量是否已定义。因此,一种解决方法是,如果您想要自己的目标,则定义一个变量,然后让您的规则生成器进行检查。

【讨论】:

谢谢@MadScientist,这是一个有用的答案,尤其是解决方法。定义多行宏的能力似乎特别相关。

以上是关于如何在 GNU Make 中以编程方式定义目标?的主要内容,如果未能解决你的问题,请参考以下文章

GNU make:规则专题

如何在目标 c 中以编程方式添加约束

GNU make:删除生成的文件时如何重建哨兵目标?

调试 GNU make

GNU Make 中的仅订购目标 - 用于 Microsoft NMAKE?

如何为我在 Swift 中以编程方式创建的 UIViewController 子类创建 init?