【问题标题】:non-recursive make with multiple architectures?具有多种架构的非递归make?
【发布时间】:2018-03-08 02:12:32
【问题描述】:

我正在尝试改进我们当前基于 GNU Make 的构建系统。它是半非递归和半递归的。

按照[1],它在目录树上是非递归的。虽然源是在逻辑模块中跨目录组织的,但它们最终会被引入单个依赖关系图。

但是,它也是类似于 [2] 中描述的多架构构建(例如 32 位 vs 64 位,还有 RELEASE vs DEBUG 和 Internal vs External,导致大量可能的组合),并且那个方面是递归的:顶级makefile使用给定的apps目标和不同的变量(CPU_WIDTH=32=64)调用自身。

这经常会导致问题,因为某些目标只需要在顶层构建一次,而大多数其他目标需要在递归级别中为每个架构使用适当的标志构建。我们经常会发现一些构建一次的目标实际上被构建了多次,不知何故被扫入了递归的依赖树。

我如何设计一个 Makefile,使其在一次非递归的 Make 调用中包含整个多架构依赖图?

我觉得一个关键特性是使用target-specific variables,它在依赖关系图中至关重要地传播。不幸的是,一个给定的命名目标只会被构建一次,即使它应该使用不同的选项构建多次。解决此问题的一种方法是在目标名称中添加一些特定于架构的信息(例如,而不是目标foo.o,使其成为32/foo.o64/foo.o),但在以下天真示例中这不起作用:

.PHONY: all exe32 exe64 baz-$(ARCH)

all: exe32 exe64

exe32: ARCH = 32
exe32: baz-$(ARCH)
    @echo in $@ ARCH is $(ARCH)

exe64: ARCH = 64
exe64: baz-$(ARCH)
    @echo in $@ ARCH is $(ARCH)

baz-$(ARCH):
    @echo in $@ ARCH is $(ARCH)

这会导致错误的输出:

in baz- ARCH is 32
in exe32 ARCH is 32
in exe64 ARCH is 64

[1]Recursive Make Considered Harmful

[2]Multi-Architecture Builds

【问题讨论】:

  • 我认为放弃在同一棵树中构建会更容易 - 使用第二个参考中描述的 VPATH 方法。然后您可以更简单地构建:make -C arm64 -f ../src/Makefile && make -C arm32 -f ../src/Makefile(甚至是顶级 Makefile 中的 all: $(ARCHES)%:⤶↦$(MAKE) -C $@ 的某些版本)。

标签: makefile gnu-make


【解决方案1】:

目标特定变量的警告(针对您的情况)是手册中以下句子的结果:“这些值仅在目标配方的上下文中可用” .

在阅读和评估所有(或几乎所有)变量替换、函数调用以及最重要的是:target names 之后,配方在 make 的第二阶段进行评估。因此,在 make 从baz-$(ARCH): 创建一个目标时,变量$(ARCH) 尚未提供值,因此计算为空字符串,总体上产生目标baz-。无法从另一个目标配方流程中创建目标。因此,在目标生成过程之前区分 make 中的不同配置需要您在第一次使用之前在 makefile 的普通、非目标、非规则行中设置这些变量。

再次阅读您的帖子,我有点不确定您要解决的问题(据我所知)是否只有一个规则用于目标baz-$(ARCH) 的双重编译步骤:

.PHONY: all exe32 exe64 baz-$(ARCH)

all: exe32 exe64

exe32: ARCH = 32
exe32: baz-32
    @echo in $@ ARCH is $(ARCH)

exe64: ARCH = 64
exe64: baz-64
    @echo in $@ ARCH is $(ARCH)

baz-32:
    @echo in $@ ARCH is $(ARCH)

baz-64:   # this rule's code is a duplicate and you want to have only one copy
    @echo in $@ ARCH is $(ARCH)

我知道的唯一方法是通过变量/函数动态生成这样的规则:

# create dynamic target; $1=target name, $2 = prerequisite list, $3 = recipe
define dyn-target =
.PHONY: $1
$1: $2 ;
    $3
endef

# instantiate all targets of given variants; $1=target name, $2 = prerequisite list, $3 = recipe, $4 = variant list
inst-targets = $(foreach var,$4,$(eval $(call dyn-target,$1-$(var),$(addsuffix -$(var),$2),$3))$(if $(make-debugout),$(info $(call dyn-target,$1-$(var),$(addsuffix -$(var),$2),$3))))

ALL-ARCHS = 32 64

all: exe32 exe64

exe32: ARCH := 32
exe64: ARCH := 64

exe32: foo-32 baz-32
exe64: foo-64 baz-64


define multiline-cmd =
    @echo in $$@
    @echo ARCH is $$(ARCH)
endef

$(call inst-targets,baz,,@echo in $$@ ARCH is +$$(ARCH)+,$(ALL-ARCHS))
$(call inst-targets,foo,,$(multiline-cmd),$(ALL-ARCHS))

【讨论】:

  • 可以避免一些重复,例如exe%: foo-% baz-%
  • 谢谢,我确实没有吸收手册中那句话的意思。我确实希望尽量减少规则并避免添加每个“架构”规则(在问题中,为了简单起见,我只提到了 32/64,但实际上我们还有其他几个因素会导致组合爆炸)。
【解决方案2】:

最终,您需要为每个架构复制构建目标。手工操作会很乏味,但还有另一种方法:让您的整个构建成为一个模板,该模板可以将架构作为参数。如果您有其他影响构建类型的变量(调试与生产等),那么整个架构构建也需要是一个可以输入参数的模板。为此,您需要在每个模板中保留一个变量,该变量将保存所有前面的参数,无论是作为目标的前缀还是后缀。

您可以使用calleval 来实现:

## $1 will be the prefix holding parameters
define base-build

$1: $1-prog

.PHONY: $1

$1-prog: $(foreach dependency,$(dependencies),$1-dependency))
    $$(RECIPE)

$(foreach dependency, $(dependencies), $(call dependency-rule, $1, dependency))

endef

# $1 contains the prefix, $2 contains the list of arch
define arch-build
$(foreach $2,arch, $(call base-build,$1-$(arch))

$1: $(foreach arch,$2, $1-(arch))
.PHONY: $1

endef

# Same parameters use as arch-build
define build-type
$(foreach $2,type,$(call arch-build,$1-(type), $(ARCH_LIST))

$1: $(foreach type,$2, $1-(type))
.PHONY: $1

endef

TYPE_LIST := debug prod
ARCH_LIST := 32 64

$(eval $(call build-type,-,$(TYPE_LIST)))
all: -

.PHONY: all
.DEFAULT_GOAL:= all

dependency-rule 模板本身应在目标定义中使用前缀,但源文件等非特定于构建的文件除外。以此类推。

您必须将影响构建的选项集划分为每个成员互斥的子集(子集为archdebug/prod 等)。然后你应该决定你的树层次结构(是debug/prod目标的arch目标依赖,或者相反)。

我更喜欢使用单个eval,因为它更容易调试(只需将其替换为info,您就会看到它没有像应有的那样扩展。但是,您应该小心正确使用空行模板定义;如果你不是,它们可能会在它们之间没有换行符的情况下展开,这是行不通的。

这样做,您的实际目标名称将类似于: debug-32-prog.

配方和目标特定变量

对于特定于特定构建类型的配方选项,您可以使用target-specific 变量。

第一选择,您可以在它所应用的顶级目标中设置特定于目标的变量。我会通过使用计算变量名称来做到这一点。你为此创建另一个函数:

define spec_flags # $1 = prefix, $2 arch (or other), $3 flag name.
$1-$2: $3: $($3_$2)

endef

(它也可以以类似的方式用于其他级别)。 然后您将以下行添加到 arch 的模板中

$(foreach arch,$2,$(call spec_flags,$1,arch,ARCH_FLAG))。 这将为每个 arch 目标设置所需的值,并且所有先决条件都将继承这些值。

但是依赖该继承有一个缺点:您不能直接指定位于依赖关系树中较低位置的目标。 假设您在构建中的某处有文件foo.cmake prod-arch32-foo.o(例如)将绕过prodarch32 目标,因此不会设置适当的变量。

另一种方法是在使用模板构建依赖关系树的同时传播这些变量;但我还没有找到一个简单的方法来做到这一点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-06
    • 1970-01-01
    • 2012-10-31
    • 1970-01-01
    • 1970-01-01
    • 2010-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多