具有多种架构的非递归制作?

时间:2018-03-08 02:12:32

标签: makefile gnu-make

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

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

然而,它也是一个类似于[2]中描述的多架构构建(例如32位与64位,但也有RELEASE与DEBUG和内部与外部,导致大量可能的组合)和 方面是递归的:顶级makefile使用具有不同变量(appsCPU_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

2 个答案:

答案 0 :(得分:1)

对于目标特定变量的警告(使用re:对您的情况而言)显示为手册中以下句子的结果:“这些值仅在目标配方的上下文中可用”

在读取和评估所有(或几乎所有)变量替换,函数调用以及最重要的: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))

答案 1 :(得分:0)

最终,您需要为每个体系结构复制构建目标。手工完成这将是乏味的,但还有另一种方法:使整个构建成为可以将体系结构作为参数的模板。如果你有其他影响构建类型的变量(调试与生产等),那么整个arch-build也需要是一个可以输入参数的模板。为此,您需要在每个模板中保留一个变量,该变量将包含所有前面的参数,作为目标的前缀或后缀。

您可以使用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等)。然后你将决定你的树层次结构(arch目标依赖性debug/prod目标,或者反过来。)

我更喜欢使用单个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目标,因此不会设置相应的变量。

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