GNU make:根据目标覆盖变量

4
我有一个非递归构建系统,其中解析只发生一次。在解析期间,我为二进制文件<-库<-对象创建了一个依赖链。
同一源代码将编译多个硬件变体。
有all目标,它依赖于variant/all。 对于每个需要variant/all的硬件,都有variants/variant_name/all。 variant/variant_name/all依赖于所有二进制文件。
由于解析已经构建了其余的依赖关系,所以简单地make all就可以完成任务。
现在的要求是: 我们有不止一个客户,这些客户的工具链针对相同的硬件变体是不同的。如何仅进行一次解析,并一次性运行所有客户的构建,将特定于客户的二进制文件、库等放入他们自己的目录中。
以下是我考虑过的选项。
1.多次解析(==客户数),并多次调用make。将variant/all拆分为两个(==客户数)all_customer1、all_customer2,variant/all依赖于all_customer1和all_customer2。这几乎可以实现! 但是,我对解析一次并达到目的感兴趣。
2.Make的目标特定变量功能。但是,在我的情况下无法使用此功能,因为我不知道所有目标,因此依赖图未预定义,因此我无法真正使用此功能。
我们的系统可能比我解释的更复杂。但是,从我听到的评论中,我希望得到更多帮助。感谢您的时间。

1
你能更清楚地解释一下当你说目标特定变量不起作用时的意思吗?为什么你认为拆分客户目标需要多次解析和多次调用make?如果你拆分目标,那么它们应该能够拥有自己的变量,这样仍然可以让你只运行一次make,对吧? - Etan Reisner
因为二进制文件<-库<-对象的依赖图是动态构建的。在解析时,这些目标文件的路径是动态构建的。我无法想出一种通过特定于目标的变量初始化“目标”文件本身的方法。也就是说,我必须编写类似于binary:$(variable) := customer的内容。但是,在解析开始之前,我实际上并没有对象文件和库的列表,这是我看到的更大的挑战。 - venkrao
此外,正如我之前所想,影响依赖项的特定目标变量似乎相当困难。https://dev59.com/XHM_5IYBdhLWcg3whzrx 但是,似乎还有其他方法可以解决这个问题。我正在继续阅读... - venkrao
你为什么需要一个特定目标的变量作为先决条件呢?你也在构建你的工具链吗?我原以为你只是需要覆盖像$(CC)、$(AR)等代表现有工具的东西。 - Etan Reisner
如果您可以在依赖链中的“虚假目标”上设置它们,那么您就不需要在二进制文件上使用特定于目标的变量。因此,如果您有一个variant/hw1_customer1/all目标,您可以在那里设置特定的变量,然后在构建该目标的依赖项时,它们将覆盖默认变量值等。 - Etan Reisner
Etan,如果我的构建仅针对目标文件运行怎么办?Phony目标需要构建二进制、库和对象。但是,如果我只构建了对象呢?那会发生什么?我不太清楚。我觉得模拟我的构建环境很困难,而且我时间紧迫。因此,我把Makefile分成了几个部分,并在主Makefile中使用了客户特定的变量名,客户特定的文件通过软链接指向主文件。这对我来说效果很好。也许我的解释不是很清楚。基本上,我是在使用目标特定的变量和Makefile的软链接。 - venkrao
1个回答

2

make继承特定于目标的变量,只要它们具有唯一的路径,因此以下内容将起作用:

.PHONY: all
all: all-customer1 all-customer2

.PHONY: all-customer1
all-customer1: CFLAGS=-ggdb3 -O0
all-customer1: $(outdir)customer1/bin/prog

.PHONY: all-customer2
all-customer2: CFLAGS=-O3
all-customer2: $(outdir)customer2/bin/prog

$(outdir)customer1/bin/prog: $(outdir)customer1/src/main.o
    $(CC) -o $@ $(CFLAGS) $<

$(outdir)customer2/bin/prog: $(outdir)customer2/src/main.o
    $(CC) -o $@ $(CFLAGS) $<

$(outdir)customer1/src/main.o: src/main.c
    $(CC) -c -o $@ $(CFLAGS) $^

$(outdir)customer2/src/main.o: src/main.c
    $(CC) -c -o $@ $(CFLAGS) $^

意思是每个顶级目标都使用一致的标志进行构建,而下面的代码则不是这样:
.PHONY: all
all: all-customer1 all-customer2

.PHONY: all-customer1
all-customer1: CFLAGS=-ggdb3 -O0
all-customer1: $(outdir)customer1/bin/prog

.PHONY: all-customer2
all-customer2: CFLAGS=-O3
all-customer2: $(outdir)customer2/bin/prog

$(outdir)customer1/bin/prog: src/main.o
    $(CC) -o $@ $(CFLAGS) $<

$(outdir)customer2/bin/prog: src/main.o
    $(CC) -o $@ $(CFLAGS) $<

src/main.o: src/main.c
    $(CC) -c -o $@ $(CFLAGS) $^

因为你只编译了一次 src/main.o,这不是你想要的。所以,从根本上讲,你必须为每个工具链/选项集创建不同的物理目标。
关于解析方面,它需要多长时间与实际构建运行时间相比?我的经验是,即使是非常大的代码库也可以在极短的时间内将其解析为单个层次结构的构建树(即使使用庞大的构建服务器...)。
因此,考虑到这两个事实,你确定这是你想做的吗?鉴于你基本上正在运行几个构建,肯定有意义去这样做,特别是因为这样会更容易扩展;如果你有一个新的客户,你只需添加一个新的构建作业,如果运行所有这些构建需要很长时间,你可以在不同的服务器上运行它们,例如 Jenkins 的从节点。
当然,这并不是很“干净”,也可能导致它“适用于某些人,但对另一些人则不适用”,但无论如何,这总是一个管理问题...
如果你想以一种好的方式支持多个客户配置文件,你可以使用设置如下的配置文件:
# Build config for "Customer X"
# Costomer identifier: customer1
customer1_cflags=-O0 -ggdb3

.PHONY: all-customer1
all-customer1: CFLAGS=$(customer1_cflags)
all-customer1: all

然后,在你的Makefile中:

# While convenient, Wildcards are harmful - they make your builds more fragile.
# This, for example, will fail fi there are no configs...
-include $(wildcard configs/*.mk)

这将使您能够运行make all以获取您的标准/参考构建,或者运行make all-customer1以运行针对您的客户定制的构建 - 如果您使用变量来指定工作/输出目录,则可以在命令行上覆盖该变量:
make outdir=customer1_build/ all-customer1

注意:变量覆盖是基于“最近”的覆盖 - 如果有人直接在规则上覆盖了一个变量,那么这种方法将会失效。
要超越这个限制,创建“一棵构建树来统治它们所有”的方式,你需要建立一个源列表,然后使用eval动态地创建目标。下面的示例演示了原理,但需要进行一些额外的工作才能使其达到生产就绪状态。
编辑:我已经在define...endef之间包含了注释,但在代码正确运行之前,这些注释需要被删除。
主要的Makefile:
BUILD=build/
outdir=$(or $(filter %/,$(strip $(BUILD))),$(strip $(BUILD))/)

program=bin/hello

include platforms/all.mk
include configs/all.mk

# This creates all the necessary constructs for building a
# config/platform combination.
define CreateBuildConfig =
all: all-$(1)-$(2)

.PHONY: all-$(1)-$(2)
all-$(1)-$(2): $(outdir)$(1)/$(2)/$(3)

# Create implicit rule for building sources to objects
$(outdir)$(1)/$(2)/%.o: %.c
    $$(CC) -c -o $$@ $$(CFLAGS) $$(DEFINES) $$<

# Set the variables for this config/platform combo...
$(outdir)$(1)/$(2)/$(3): DEFINES=$$(foreach def,$$(platform_$(2)_defines),-D$$(def))
$(outdir)$(1)/$(2)/$(3): CFLAGS=$$(config_$(1)_cflags)

# The rule for creating the executable.
$(outdir)$(1)/$(2)/$(3): $(foreach obj,$(4:.c=.o),$(outdir)$(1)/$(2)/$(obj))
    $$(CC) -o $$@ $$(CFLAGS) $$(DEFINES) $$<

# Cleanup after ourselves.
clean: clean-$(1)-$(2)

clean-$(1)-$(2):
    -rm -f $(outdir)$(1)/$(2)/$(3)
    find $(outdir)$(1)/$(2) -name '*.o' -delete
endef

# Some top-level targets, for documentation purposes.
.PHONY: all
all:

.PHONY: clean
clean:

# Build the list of sources.
sources+=src/main.c

# Create the actual build targets, for each platform/config pair.
$(foreach platform,$(all_platforms),$(foreach config,$(all_configs),$(eval $(call CreateBuildConfig,$(config),$(platform),$(program),$(sources)))))

平台配置文件platforms/all.mk:
all_platforms:=model1 model2

include platforms/model1.mk
include platforms/model2.mk

示例平台文件 platforms/model1.mk:


_platform=model2

platform_$(_platform)_defines:=NAME=$(_platform)
platform_$(_platform)_defines+=MAX_FOO=16

_platform=

配置文件是 config/all.mk:
all_configs:=gcc48 customer1 customer2

include configs/gcc48.mk
include configs/customer1.mk
include configs/customer2.mk

示例配置文件customer1.mk

_config=customer1

config_$(_config)_cflags=-O3

_config=

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接