我正在努力改进我们当前基于GNU Make的构建系统。它是一半非递归和半递归。
在[1]之后,它在目录树上是非递归的。虽然源是按逻辑模块中的目录组织的,但它们最终会被引入单个依赖图。
然而,它也是一个类似于[2]中描述的多架构构建(例如32位与64位,但也有RELEASE与DEBUG和内部与外部,导致大量可能的组合)和 方面是递归的:顶级makefile使用具有不同变量(apps
或CPU_WIDTH=32
)的给定=64
目标调用自身。
这会导致问题,因为某些目标只需要在顶层构建一次,而大多数其他目标需要在递归级别中使用适当的每个架构标记构建。我们经常发现一些构建一次的目标实际上是多次构建的,已经以某种方式被扫描到递归的依赖树中。
我如何设计一个Makefile,使其可以在Make的一个非递归调用中包含整个多架构依赖图?
我觉得关键功能是使用target-specific variables,它在关系图中传播至关重要。不幸的是,给定的命名目标只会构建一次,即使它应该使用不同的选项多次构建。解决这个问题的方法是在目标名称中添加一些特定于拱形的信息(例如,而不是目标foo.o
,使其成为32/foo.o
和64/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
答案 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也需要是一个可以输入参数的模板。为此,您需要在每个模板中保留一个变量,该变量将包含所有前面的参数,作为目标的前缀或后缀。
您可以使用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
dependency-rule
模板本身应该在目标定义中使用前缀,但非特定于构建的文件(如源文件)除外。等等递归。
您必须将影响构建的选项集划分为每个成员互斥的子集(子集将为arch
,debug/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.c
,make prod-arch32-foo.o
(例如)会绕过prod
和arch32
目标,因此不会设置相应的变量。
另一种方法是在使用模板构建依赖关系树的同时传播这些变量;但我还没有找到一种简单的方法。