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

Makefile 终端匹配任何模式规则,多个先决条件,makefile,gnu-make,Makefile,Gnu Make,我正试图使用Paul Smith的(稍加修改的版本)在不同的子目录中构建我们的应用程序,根据在命令行上传递给make的环境变量,每个子目录都有不同的编译器标志 下面的Makefile是我们所做工作的简化示例: 如果用户调用make debug=1,则输出目标将内置到带有特定编译器标志的\u debug子目录中 如果用户调用makerelease=1,则输出目标将内置到带有特定编译器标志的\u release子目录中 以及: 因此,在\u debug和\u release子目录中都会创建文件

我正试图使用Paul Smith的(稍加修改的版本)在不同的子目录中构建我们的应用程序,根据在命令行上传递给
make
的环境变量,每个子目录都有不同的编译器标志

下面的
Makefile
是我们所做工作的简化示例:

  • 如果用户调用
    make debug=1
    ,则
    输出
    目标将内置到带有特定编译器标志的
    \u debug
    子目录中
  • 如果用户调用
    makerelease=1
    ,则
    输出
    目标将内置到带有特定编译器标志的
    \u release
    子目录中

以及:

因此,在
\u debug
\u release
子目录中都会创建文件
输出

问题是,当我尝试指定这两个变量时,它只在
\u 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

我不理解这种行为<在这种情况下,code>OBJDIRS
等于
“\u debug\u release”
,因此我的终端匹配任何规则(
%::$(OBJDIRS);
)都以
\u debug
\u release
为先决条件。因此,我希望GNU Make检查(并构建)这两个先决条件,但它只是构建第一个先决条件(
\u 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

我想我对终端的工作原理有些误解。我不明白为什么明确指定了目标和没有指定目标时的行为有所不同

我在CentOS 7中使用GNU Make 3.82


编辑:我在Arch Linux上的GNU Make 4.2.1中也看到了这种行为。

我必须承认,我还没有完全分析您的
生成文件
,但以下几点让我很突出。阅读:

默认情况下,目标是makefile中的第一个目标(不计算在内) 以句点开头的目标)。因此,makefile通常是 编写时,第一个目标是编译整个程序 或者他们描述的程序。如果makefile中的第一条规则 多个目标,只有规则中的第一个目标成为默认目标 目标,而不是全部

因此,在没有明确目标的情况下执行
makedebug=1release=1v=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设置为单词1的先决条件2到N(我只是选择了任意高的N值100):

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 _*

如果您还没有,我绝对建议您阅读我给出的关于Paul的“高级VPATH方法”的第一个链接。我了解了一些我从未遇到过的Make特性,所以你说的很有道理,我想这绝对是我的问题。有两件事:1)在这种情况下,我不能使用仅限订单的先决条件,因为每个单独的
OBJDIRS
目标都会调用sub-
make
,以便在该目录中构建我的所有源代码……如果它是仅限订单的先决条件,那么一旦目录存在,就不会再次构建它(对吗?);为了缓解这种情况,我尝试移动
%::$(OBJDIRS)目标在
$(OBJDIRS)
目标之上,使其成为所选目标(从而构建两个
OBJDIRS
目标,这是它的先决条件),但它仍然只构建
\u debug
@villapx是的,我将阅读它,感谢指针。我喜欢他的帖子,一位伟大的老师。你关于“仅目标”先决条件的评论是正确的。这是一个相当有创意的解决方案。我想为任何需要保持这一点的人补充几句解释,除了你自己:——)确实如此。确实在我们的实际项目中写了一条评论不幸的是,我刚刚意识到这个解决方案有一个缺点:由于第一个解决方案之外的任何
OBJDIR
都是第一个解决方案的先决条件,因此在使用
-j
进行并行构建时,会丢失一些并行性,由于
make
无法构建第一个
OBJDIR
,直到后者完成。找到了更好的解决方案:在
$(OBJDIRS)
目标上方创建一个目标
all:$(OBJDIRS)
。这样,当命令行上没有显式设置目标时,
all
目标被选中,使得所有的
OBJDIRS
都被构建,现在它们都可以完全并行地构建。
$ 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
ifneq (1,$(words $(OBJDIRS)))
$(firstword $(OBJDIRS)): $(wordlist 2,100,$(OBJDIRS))
endif
.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 _*