Makefile 具有多个体系结构的非递归make?

Makefile 具有多个体系结构的非递归make?,makefile,gnu-make,Makefile,Gnu Make,我正在努力改进我们目前基于GNU Make的构建系统。它是半非递归和半递归的 在[1]之后,它在目录树上是非递归的。虽然源在逻辑模块中跨目录组织,但它们最终被引入到单个依赖关系图中 然而,它也是一个类似于[2]中所述的多体系结构构建(例如32位vs 64位,但也有发布vs调试和内部vs外部,导致大量可能的组合),并且这一方面是递归的:顶级makefile使用具有不同变量的给定apps目标调用自己(CPU\u WIDTH=32或=64) 这通常会导致问题,因为某些目标只需要在顶层构建一次,而大多数

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

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

然而,它也是一个类似于[2]中所述的多体系结构构建(例如32位vs 64位,但也有发布vs调试和内部vs外部,导致大量可能的组合),并且这一方面是递归的:顶级makefile使用具有不同变量的给定
apps
目标调用自己(
CPU\u WIDTH=32
=64

这通常会导致问题,因为某些目标只需要在顶层构建一次,而大多数其他目标则需要在递归层中使用每个架构的适当标志进行构建。我们经常发现,一些构建一次的目标实际上被构建了多次,以某种方式被卷入了递归的依赖关系t雷

我如何设计一个Makefile,使其能够在一次非递归的Make调用中包含整个多体系结构依赖关系图

我觉得有一个关键的特性正在使用,它在依赖关系图中起着至关重要的作用。不幸的是,一个给定的命名目标只会构建一次,即使它应该使用不同的选项多次构建。解决这个问题的方法是在目标名称中添加一些特定于arch的信息(例如,将其设置为
32/foo.o
64/foo.o
,而不是target
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]

[2]

目标特定变量的警告(根据您的情况)出现在手册中的以下句子中:“这些值仅在目标配方的上下文中可用”

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

再次阅读您的帖子,我有点不确定您想要解决的问题(据我所知)是否是针对target
baz-$(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))

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

您可以通过使用
call
eval
来实现这一点:

## $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
依赖项规则
模板本身应在目标定义中使用前缀,但非生成特定文件(如源文件等)除外

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

我更喜欢使用单个
eval
,因为它更容易调试(只需将其替换为
info
,您就可以看到哪些内容没有按应有的方式展开。但是,您应该注意在模板定义中正确使用空行;如果您没有这样做,则可能会展开这些内容,但它们之间没有换行符,这是行不通的

这样做,您的实际目标名称将如下所示:
debug-32-prog

配方和目标特定变量 对于特定于特定构建类型的配方选项,您可以使用特定于目标的变量

第一种选择是,您可以在其应用的顶级目标中设置特定于目标的变量。我会使用计算变量名称来实现这一点。您可以为此创建另一个函数:

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

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

$(foreach arch,$2,$(调用规范标志,$1,arch,arch标志))
。 这将设置每个
arch
目标所需的值,所有先决条件都将继承这些值

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


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

我认为放弃构建同一棵树会更容易-使用VPATH方法
define spec_flags # $1 = prefix, $2 arch (or other), $3 flag name.
$1-$2: $3: $($3_$2)

endef