我有一个顶级 Makefile,它定义了包含子 Makefile 的子目录列表。
每个子 Makefile 定义相同的目标:all, install, clean, test, verify, style...
如何让顶层 Makefile 调用具有相同目标的所有子目录 Makefile?
它适用于默认目标,像这样:
SUBDIRS = dir1 dir2 dir3
all: subdirs
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
.PHONY: subdirs $(SUBDIRS)
但是我如何将其扩展到其他非默认目标?
MAKECMDGOALS
变量 和 filter
函数:
SUBDIRS := dir1 dir2 dir3
TARGETS := all install clean test verify style
$(TARGETS): subdirs
@echo making top $@
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@ $(filter $(TARGETS),$(MAKECMDGOALS))
.PHONY: subdirs $(TARGETS) $(SUBDIRS)
%::
@echo making top $@
演示:
$ make --no-print-directory all install foobar
make -C dir1 all install
make -C dir2 all install
make -C dir3 all install
making top all
making top install
making top foobar
请注意,需要
filter
来避免将顶级目标传递给子品牌。
如果出于任何原因,这个基于
MAKECMDGOALS
的解决方案不适合您,这里有一些其他但更复杂的解决方案。
特定于目标的变量传播到先决条件。因此,您可以为每个顶级目标定义一个特定于模式的变量 (
SUBGOAL
),并在子目录的配方中使用它:
SUBDIRS := dir1 dir2 dir3
all: SUBGOAL := all
all: subdirs
@echo making top $@
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@ $(SUBGOAL)
.PHONY: subdirs $(SUBDIRS)
如果你有很多这样的顶级目标并且使用 GNU make 你可以使用
foreach
和 eval
GNU make 函数进行分解:
SUBDIRS := dir1 dir2 dir3
TARGETS := all install clean test verify style
$(foreach t,$(TARGETS),$(eval $(t): GOAL := $(t)))
$(TARGETS): subdirs
@echo making top $@
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@ $(GOAL)
.PHONY: subdirs $(SUBDIRS) $(TARGETS)
一个重要的缺点是,当您对多个顶级虚假目标(
dir1
,all
...)使用相同的虚假子目录目标(install
)时,您无法调用具有多个顶级目标的make:
$ make --no-print-directory all install
make -C dir1 all
make -C dir2 all
make -C dir3 all
making top all
making top install
只会在子目录中生成
all
。如果这是一个问题,您可以使用下一个解决方案。
all-dir1
)foreach-eval-call
组合(仅限 GNU make)以编程方式执行此操作:
SUBDIRS := dir1 dir2 dir3
TARGETS := all install clean test verify style
$(TARGETS): subdirs
@echo making top $@
TARGETS_X_SUBDIRS :=
# $(1): top target
# $(2): subdirectory target
define TARGETS_X_SUBDIRS_RULES
$(1): $(1)-$(2)
$(1)-$(2):
$(MAKE) -C $(2) $(1)
TARGETS_X_SUBDIRS += $(1)-$(2)
endef
$(foreach t,$(TARGETS),$(foreach s,$(SUBDIRS),$(eval $(call TARGETS_X_SUBDIRS_RULES,$(t),$(s)))))
.PHONY: $(TARGETS) $(TARGETS_X_SUBDIRS)
演示:
$ make --no-print-directory all install
make -C dir1 all
make -C dir2 all
make -C dir3 all
making top all
make -C dir1 install
make -C dir2 install
make -C dir3 install
making top install
Renaud 的回答更完整,但我也发现这种技术对我的用例很有效:
all:
%:
make -C dir1 $@
make -C dir2 $@
make -C dir3 $@
在这种情况下,我没有变量中的子目录列表,但它们在通用目标
%:
的命令中被拼写出来。它需要在每个子目录前面添加一个额外的make -C
,并在后面添加一个@
,但在其他方面非常易于维护且易于阅读。
但是如果顶层 Makefile 有任何其他特定目标,它可能无法工作。
如果有人来到这里寻找一个通用的包装器 Makefile,它将给它的任何目标转发到一组(包含目录)Makefile 并且并行工作良好,我最近有机会设计这样一个包装器。它的优点是不需要了解被调用的 Makefile 支持的目标,对我来说至关重要的是,当提供
-j
标志时,将并行构建转发的目标。
我添加这个答案是因为其他答案要么对可以转发的有效目标做出假设,要么串行而不是并行执行包装的 Makefiles。
# Define the directories and Makefiles to forward the targets to
# here for illustrative purposes. Could just as well be passed in
# on the command-line, wildcarded, or found (shell find).
MAKE_DIRS := some/path/containing/a/Makefile some/other/path/containing/some/other/Makefile
MAKE_FILES := some/path/to/a/Makefile some/other/path/to/another/Makefile
# Define all the passed-in targets as depending on
# the directories and Makefiles
$(MAKECMDGOALS): $(MAKE_DIRS) $(MAKE_FILES)
# Generate one recipe for each directory that makes a recursive call
# to the directory with the requested targets. The '+' will make sure
# it is executed, even when -n, -q or -t is given
$(foreach MAKE_DIR,$(MAKE_DIRS),$(eval $(MAKE_DIR): ; +$(MAKE) -C $(MAKE_DIR) $(MAKECMDGOALS)))
# Do the same for each Makefile with -f instead of -C.
$(foreach MAKE_FILE,$(MAKE_FILES),$(eval $(MAKE_FILE): ; +$(MAKE) -f $(MAKE_FILE) $(MAKECMDGOALS)))
# Define everything in this file as phony so it always executes.
.PHONY: $(MAKECMDGOALS) $(MAKE_DIRS) $(MAKE_FILES)
这对我在大型 CMake 项目中包含五个编译变体的项目非常有帮助。 CMake 项目包含数以千计的小片段和一个巨大的 blob,编译这些 blob 本身需要大约 4 分钟。代码更新后,并使用
-j5
运行上述 Makefile 的变体,小事情会很快完成,我可以坐下来放松四分钟,同时并行编译五个大 blob。将周转时间从 20 分钟以上减少到 4 分钟以上绝对值得设置它。