终端匹配任何模式规则,多个先决条件

Terminal match-anything pattern rule, multiple prerequisites

我正在尝试使用 Paul Smith 的 Advanced VPATH Method for Multi-Architecture Builds(稍微修改过的版本)在不同的子目录中构建我们的应用程序,每个子目录具有不同的编译器标志,基于传递给 make 在命令行上。

以下Makefile是我们正在做的简化示例:


RELEASEDIR := _release
DEBUGDIR := _debug

OBJDIRS :=
ifdef debug
    OBJDIRS += $(DEBUGDIR)
endif
ifdef release
    OBJDIRS += $(RELEASEDIR)
endif

ifneq ($(MAKECMDGOALS),clean)
ifndef OBJDIRS
    $(error Must define "debug" and/or "release" in the environment, e.g. "make debug=1")
endif
endif


#----- Begin section to determine what subdir(s) to change into
ifeq (,$(filter _%,$(notdir $(CURDIR))))
.SUFFIXES:

MAKETARGET = $(MAKE) --no-print-directory -C $@ -f $(CURDIR)/Makefile \
                 SRCDIR=$(CURDIR) $(MAKECMDGOALS)

.PHONY: $(OBJDIRS)
$(OBJDIRS):
        +@[ -d $@ ] || mkdir -p $@
        +@$(MAKETARGET)

Makefile : ;
%.mk :: ;

% :: $(OBJDIRS) ;

.PHONY: clean
clean:
        rm -rf _*
else
#----- End subdir(s) section


VPATH = $(SRCDIR)

# do special things (e.g. set compiler flags) based on the subdirectory
CURRENT_SUBDIR := $(notdir $(CURDIR))

ifneq (,$(findstring $(RELEASEDIR),$(CURRENT_SUBDIR)))
    $(info ...set compiler flags specific to release build...)
endif
ifneq (,$(findstring $(DEBUGDIR),$(CURRENT_SUBDIR)))
    $(info ...set compiler flags specific to debug build...)
endif

output : prereq
        cp -f $< $@

# user can specify v=1 to turn verbosity up
$(v).SILENT:


#----- End the "subdirs" if-statement
endif

如果我只指定两个变量之一,这会像我期望的那样工作:

$ make debug=1 v=1
...set compiler flags specific to debug build...
cp -f /home/me/work/prereq output

和:

$ make release=1 v=1
...set compiler flags specific to release build...
cp -f /home/me/work/prereq output

因此文件 output_debug_release 子目录中创建。

问题是当我尝试指定两个变量时,它只在 _debug 子目录中构建目标:

$ make clean
rm -rf _*
$ make debug=1 release=1 v=1
...set compiler flags specific to debug build...
cp -f /home/me/work/prereq output

我不明白这种行为。在这种情况下,OBJDIRS 等于 "_debug _release",因此我的终端匹配规则 (% :: $(OBJDIRS) ;) 具有 both _debug_release 作为先决条件。因此,我希望 GNU Make 检查(并构建)这两个先决条件,但它只构建第一个 (_debug)。

有趣的是,如果我在命令行上明确指定要构建的目标,它会同时构建:

$ make clean
rm -rf _*
$ make debug=1 release=1 v=1 output
...set compiler flags specific to debug build...
cp -f /home/me/work/prereq output
...set compiler flags specific to release build...
cp -f /home/me/work/prereq output

我假设我对终端 match-anything rules 的工作方式有一些误解。我不明白为什么明确指定目标时和未明确指定目标时的行为不同。

我在 CentOS 7 中使用 GNU Make 3.82。

编辑:我在 Arch Linux.

上的 GNU Make 4.2.1 中也看到了这种行为

我不得不承认我没有完全分析您的 makefile,但以下内容对我来说很突出。阅读 9.2 Arguments to Specify the Goals:

By default, the goal is the first target in the makefile (not counting targets that start with a period). Therefore, makefiles are usually written so that the first target is for compiling the entire program or programs they describe. If the first rule in the makefile has several targets, only the first target in the rule becomes the default goal, not the whole list.

因此,在没有明确提及目标的情况下执行 make debug=1 release=1 v=1,只会导致 $(OBJDIRS) 值中的第一个条目成为目标,而不是整个列表。这将是 $(DEBUGDIR),而不是 $(DEBUGDIR) $(RELEASEDIR)。这似乎可以解释您的观察结果。

使用此代码段模拟您的情况的实验证明了以下行为:

RELEASEDIR := _release
DEBUGDIR := _debug

OBJDIRS :=
ifdef debug
    OBJDIRS += $(DEBUGDIR)
endif
ifdef release
    OBJDIRS += $(RELEASEDIR)
endif


.PHONY: $(OBJDIRS)
$(OBJDIRS):
        @echo OBJDIRS = $(OBJDIRS), Target = $@

output: $(OBJDIRS)
        @echo Creating the output target

结果如下:

$ make debug=1
OBJDIRS = _debug, Target = _debug
$ make release=1
OBJDIRS = _release, Target = _release
$ make release=1 debug=1
OBJDIRS = _debug _release, Target = _debug
$ make release=1 debug=1 _release
OBJDIRS = _debug _release, Target = _release
$ make release=1 debug=1 output
OBJDIRS = _debug _release, Target = _debug
OBJDIRS = _debug _release, Target = _release
Creating the output target

更新:以上确定了问题,是一个解决方案(由OP找到)

对我的问题是正确的。我的解决方案是检查 $(OBJDIRS) 是否包含多个单词,如果是,则使单词 2 到 N(我只是选择了 100 的任意高 N)单词 1 的先决条件:

ifneq (1,$(words $(OBJDIRS)))
$(firstword $(OBJDIRS)): $(wordlist 2,100,$(OBJDIRS))
endif

因此 Makefile 的那部分现在看起来像这样:

.SUFFIXES:

MAKETARGET = $(MAKE) --no-print-directory -C $@ -f $(CURDIR)/Makefile \
                 SRCDIR=$(CURDIR) $(MAKECMDGOALS)

.PHONY: $(OBJDIRS)
$(OBJDIRS):
        +@[ -d $@ ] || mkdir -p $@
        +@$(MAKETARGET)

ifneq (1,$(words $(OBJDIRS)))
$(firstword $(OBJDIRS)): $(wordlist 2,100,$(OBJDIRS))
endif

Makefile : ;
%.mk :: ;

% :: $(OBJDIRS) ;

.PHONY: clean
clean:
        rm -rf _*